Files
Stefan Lohmaier a811e87029 Update docs: CLAUDE.md, README, config example
- CLAUDE.md: subdomains, OAuth Auth Code+PKCE, shared token store,
  all backends incl Joplin Data API, content types, current tool list
- README: full tool inventory per service, correct setup steps
- config.json.example: joplin_login + joplin_data_api fields documented

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-12 12:35:35 +02:00

5.0 KiB

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 teststefan. 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/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-<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)
  • Documents (PDF, docx, xlsx) / other binary → EmbeddedResource with BlobResourceContents (base64 + mimeType) — claude.ai processes these client-side

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 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