Self-hosted MCP servers with OAuth client_credentials auth. Each server connects to a different backend: - Mail: reads Maildir IMAP backups - Calendar/Tasks: CalDAV against Radicale - Contacts: CardDAV against Radicale - Files: WebDAV against oCIS - Notes: Joplin REST API Credentials externalized to config.json (not in repo). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3.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 client_credentials auth.
Architecture
5 independent MCP servers, each on its own port, behind nginx reverse proxy at mcp.home.slohmaier.de:
- Mail (Port 5100) — searches IMAP backup Maildirs
- Calendar (Port 5101) — CalDAV against Radicale (events + tasks/reminders)
- Contacts (Port 5102) — CardDAV against Radicale
- Files (Port 5103) — WebDAV against oCIS
- Notes (Port 5104) — Joplin REST API
All servers share common.py for OAuth token handling and user resolution.
Auth Flow
OAuth client_credentials grant — no browser redirect needed:
- Client POSTs to
/<service>/tokenwithclient_id+client_secret - Server returns
access_token(valid 24h) - Client sends
Authorization: Bearer <token>with MCP requests - User is identified by
client_id(matchestokens.json)
OAuth metadata at /<service>/.well-known/oauth-authorization-server.
User Separation
tokens.json (not in git, chmod 600) maps client_id → user. Each user only sees their own data. Shared CalDAV calendars are visible to both users.
Files
common.py— OAuth endpoints, Bearer middleware, user resolution via contextvarstokens.json— client_id/secret per user (generate withpython3 -c "import secrets; print(secrets.token_urlsafe(48))")mail/server.py— Maildir reader, search across subject/from/to/bodycalendar/server.py— CalDAV REPORT queries, event/task CRUD, vobject serializationcontacts/server.py— CardDAV addressbook-query, vCard parsing, contact CRUDfiles/server.py— WebDAV PROPFIND/GET, recursive search, text file readingnotes/server.py— Joplin REST API wrapper (needs per-user API token in code)
Dependencies
pip install mcp[cli] httpx vobject python-dateutil
Python 3.12+, venv at ./venv/.
Deployment
Each server runs as systemd unit (mcp-<name>.service), managed by uvicorn. nginx at mcp.home.slohmaier.de proxies /<service>/ to the right port. TLS via Certbot.
Adding a New Service
- Create
<name>/server.pywithFastMCPinstance +create_app()using the same pattern - Import
get_current_user,OAUTH_ROUTES,BearerAuthMiddlewarefromcommon.py - Add nginx location block for
/<name>/ - Create systemd unit
mcp-<name>.service - Update
tokens.jsonif the new service needs different user mapping
Tool Design Guidelines
- Every tool docstring is the first thing Claude sees — make it actionable ("Call this first", "Use the UID from search results")
- Use
Annotated[str, Field(description="...")]with example values for every parameter - Indicate which parameters are optional ("Leave empty for all")
- Reference other tools in descriptions ("Use get_contact with the UID for full details")
- Return structured text, not JSON — Claude parses text faster
- Include UIDs/keys in output so Claude can chain to detail tools without asking the user