fb642e47c8
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>
70 lines
3.0 KiB
Markdown
70 lines
3.0 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 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:
|
|
|
|
1. Client POSTs to `/<service>/token` with `client_id` + `client_secret`
|
|
2. Server returns `access_token` (valid 24h)
|
|
3. Client sends `Authorization: Bearer <token>` with MCP requests
|
|
4. User is identified by `client_id` (matches `tokens.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 contextvars
|
|
- `tokens.json` — client_id/secret per user (generate with `python3 -c "import secrets; print(secrets.token_urlsafe(48))"`)
|
|
- `mail/server.py` — Maildir reader, search across subject/from/to/body
|
|
- `calendar/server.py` — CalDAV REPORT queries, event/task CRUD, vobject serialization
|
|
- `contacts/server.py` — CardDAV addressbook-query, vCard parsing, contact CRUD
|
|
- `files/server.py` — WebDAV PROPFIND/GET, recursive search, text file reading
|
|
- `notes/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
|
|
|
|
1. Create `<name>/server.py` with `FastMCP` instance + `create_app()` using the same pattern
|
|
2. Import `get_current_user`, `OAUTH_ROUTES`, `BearerAuthMiddleware` from `common.py`
|
|
3. Add nginx location block for `/<name>/`
|
|
4. Create systemd unit `mcp-<name>.service`
|
|
5. Update `tokens.json` if 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
|