- 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>
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).
- claude.ai discovers
/.well-known/oauth-authorization-server GET /authorize→ auto-approves valid client_id, redirects with codePOST /token(authorization_code + code_verifier, optional client_secret) → access_tokenAuthorization: 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. Seeconfig.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 defaultDAV:namespace (nod:prefix) andCR: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 loadermail/server.py— Maildir reader; tools: list_accounts, list_folders, search_mail, read_mail (lists attachments), read_attachment, create_draftcalendar/server.py— CalDAV REPORT + vobject; events/tasks CRUD, travel time, geocoding, reminderscontacts/server.py— CardDAV; search/get/create, contact photosfiles/server.py— WebDAV; list/read/write/delete/move/search, images + documentsnotes/server.py— Joplin Data API; list/search/read/create notes, list_note_resources, read_resourcetests/test_all.py— 54 integration tests (pytest), daily viamcp-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 →
EmbeddedResourcewithBlobResourceContents(base64 + mimeType) — claude.ai processes these client-side
Adding a New Service
<name>/server.pywithFastMCPinstance +create_app()(copy mail/server.py pattern)- Import
get_current_user,OAUTH_ROUTES,BearerAuthMiddleware,load_configfromcommon.py - nginx vhost
<name>.mcp.home.slohmaier.de+ Certbot - systemd unit
mcp-<name>.service - Backend creds in
config.json - 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