# 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 ` 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//`. 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/test_all.py` — 54 integration tests (pytest), daily via `mcp-tests.timer` ## 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-.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) - Documents (PDF, docx, xlsx) / other binary → `EmbeddedResource` with `BlobResourceContents` (base64 + mimeType) — claude.ai processes these client-side ## Adding a New Service 1. `/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 `.mcp.home.slohmaier.de` + Certbot 4. systemd unit `mcp-.service` 5. Backend creds in `config.json` 6. Tests in `tests/test_all.py`, update dashboard + this file ## 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