Files
mcp-home/CLAUDE.md
T
Stefan LohmaierandClaude Opus 4.8 3ccfb80115 docs(CLAUDE): Test-Schichten, PDF/Dokument-Realitaet, Robustheit-Limits
- tests: 54 -> Schichten (Smoke nightly, Edge/Stress/Funktional on-demand)
- Content Types: PDFs server-seitig extrahiert/gerendert; claude.ai nimmt keine
  PDF/Dokument-Blobs aus Tool-Results nativ (nur Text+Bild)
- neue Sektion Robustheit/Limits (Deadlines/Caps pro Connector)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 21:20:57 +02:00

102 lines
6.1 KiB
Markdown

# mcp-home
Self-hosted MCP (Model Context Protocol) servers for Claude. Provides access to personal data services via remote HTTP endpoints with OAuth (Authorization Code + PKCE, and client_credentials).
## Architecture
5 independent MCP servers, each on its own subdomain + port:
- **Mail** (`mail.mcp.home.slohmaier.de`, Port 5100) — searches IMAP backup Maildirs, reads attachments, creates drafts
- **Calendar** (`calendar.mcp.home.slohmaier.de`, Port 5101) — CalDAV against Radicale (events + tasks/reminders, travel time, geocoding, alarms)
- **Contacts** (`contacts.mcp.home.slohmaier.de`, Port 5102) — CardDAV against Radicale (contacts + photos)
- **Files** (`files.mcp.home.slohmaier.de`, Port 5103) — WebDAV against oCIS (full CRUD, images inline, documents as blobs)
- **Notes** (`notes.mcp.home.slohmaier.de`, Port 5104) — Joplin Data API (notes + attachments)
Each subdomain is its own nginx vhost proxying to the local port. All servers share `common.py` for OAuth + user resolution.
## Auth Flow
claude.ai uses OAuth 2.0 **Authorization Code + PKCE**. client_credentials is also supported (used by tests).
1. claude.ai discovers `/.well-known/oauth-authorization-server`
2. `GET /authorize` → auto-approves valid client_id, redirects with code
3. `POST /token` (authorization_code + code_verifier, optional client_secret) → access_token
4. `Authorization: Bearer <token>` on MCP requests
Access tokens are stored in a shared file `.active_tokens.json` so all 5 services validate tokens issued by any service. client_secret is verified at token exchange when provided.
## User Separation
`tokens.json` (not in git, chmod 600) maps client_id (`stefan`, `kati`, `test`) → secret. `USER_ALIASES` in `common.py` maps `test``stefan`. Each user sees only their own data; shared CalDAV calendars are visible to both.
## Config
- `tokens.json` — OAuth client_id/secret per user (gitignored)
- `config.json` — all backend credentials (Radicale, oCIS, Joplin, IMAP), gitignored. See `config.json.example`.
- `.active_tokens.json` — runtime OAuth tokens (gitignored)
## Backends
- **Mail**: reads Maildirs under `/mnt/ssd/Backup/{stefan,kati}/imap/`. Drafts via IMAP APPEND (1blu / Gmail).
- **Calendar/Contacts**: Radicale CalDAV/CardDAV on `127.0.0.1:5232`. Note: Radicale uses default `DAV:` namespace (no `d:` prefix) and `CR:` for CardDAV — regex must match both.
- **Files**: oCIS WebDAV on `127.0.0.1:9200/remote.php/dav/files/<user>/`. oCIS username may differ from MCP user (mapping in config).
- **Notes**: Joplin **Data API** via joplin-cli sync clients on `127.0.0.1:41184` (stefan) / `41185` (kati). See `/opt/joplin-mcp/README.md`. NOT the Joplin Server sync API (that has no clean REST/search).
## Files
- `common.py` — OAuth (authorize/token/metadata), Bearer middleware, user resolution (contextvars), config loader
- `mail/server.py` — Maildir reader; tools: list_accounts, list_folders, search_mail, read_mail (lists attachments), read_attachment, create_draft
- `calendar/server.py` — CalDAV REPORT + vobject; events/tasks CRUD, travel time, geocoding, reminders
- `contacts/server.py` — CardDAV; search/get/create, contact photos
- `files/server.py` — WebDAV; list/read/write/delete/move/search, images + documents
- `notes/server.py` — Joplin Data API; list/search/read/create notes, list_note_resources, read_resource
- `tests/` — Schichten: `test_smoke.py` (NIGHTLY via `mcp-tests.timer`, nur up+Auth+1 Read/Connector), `test_edge.py`+`test_stress.py`+`test_all.py` (ON-DEMAND via `run_full_tests.sh`). Doku `tests/MCPTEST.md`
## Dependencies
```
pip install mcp[cli] httpx vobject python-dateutil pytest
```
Python 3.12+, venv at `./venv/`. joplin-cli (npm global) for Notes backend.
## Deployment
Each server is a systemd unit `mcp-<name>.service` (uvicorn). Each subdomain is an nginx vhost with rate-limited `/authorize` + `/token`. TLS via Certbot (shared cert on mail.mcp...). Joplin: `joplin-cli-{stefan,kati}.service` + `joplin-sync.timer` (15 min).
## Content Types (returning binary)
- Text → `TextContent`
- Images (jpg/png/gif/webp) → `ImageContent` (base64 inline)
- PDFs → server-seitig zu Text extrahiert bzw. gescannte Seiten als PNG+OCR gerendert (`pdfutil.py`). claude.ai nimmt KEINE PDF/Dokument-Blobs aus Tool-Results nativ entgegen (nur Text + Bild). Andere Binaerdateien → `EmbeddedResource`/`BlobResourceContents` (Blob; wird von claude.ai nicht als Dokument verarbeitet)
## Robustheit / Limits
Alle Such-/List-Tools sind gegen grosse Datenmengen abgesichert (single-threaded Server darf nicht haengen): Mail per-Mail try/except + `DEADLINE_S`/`MAX_SCAN`; Files `search_files` `SEARCH_DEADLINE_S`/`SEARCH_MAX_DIRS`; Calendar/Contacts `PARSE_DEADLINE_S` + `MAX_RESULTS`-Output-Cap. Details + Messwerte: `tests/MCPTEST.md`.
## Adding a New Service
1. `<name>/server.py` with `FastMCP` instance + `create_app()` (copy mail/server.py pattern)
2. Import `get_current_user`, `OAUTH_ROUTES`, `BearerAuthMiddleware`, `load_config` from `common.py`
3. nginx vhost `<name>.mcp.home.slohmaier.de` + Certbot
4. systemd unit `mcp-<name>.service`
5. Backend creds in `config.json`
6. Tests: Smoke-Read in `test_smoke.py` + Edge in `test_edge.py` + Stress in `test_stress.py` (Funktional in `test_all.py`); Dashboard + dieses File aktualisieren
## Tool Design Guidelines
- Docstring is the first thing Claude sees — make it actionable ("Call this first", "Use the UID from search results")
- `Annotated[str, Field(description="...")]` with example values for every parameter
- Mark optional params ("Leave empty for all")
- Reference other tools ("Use read_attachment with the index from read_mail")
- Return structured text, not JSON
- Include IDs/keys in output so Claude can chain to detail tools
## Test-/Dev-User mcptest
Die Test-Suite laeuft auf einem isolierten `mcptest`-User (NICHT Stefans Daten):
`USER_ALIASES = {"test": "mcptest"}`. Eigene Backends: Radicale-User mcptest, oCIS-User
mcptest, lokales Joplin-Profil (:41186, `joplin-cli-mcptest.service`), Test-Maildir
`tests/testdata/maildir/`. Dient auch als Dev-Sandbox. Vollstaendige Doku + Provisioning:
`tests/MCPTEST.md`. Creds: `/root/.mcptest-creds` (600) + `config.json`.