--- project: homelab-mcp-server type: project-plan status: active path: Tech/Projects tags: - homelab - mcp - obsidian - n8n - authentik - cloudflare created: 2026-05-05 updated: 2026-05-05 --- # Homelab MCP Server ## Goal Replace the email → n8n → Obsidian pipeline for project and session notes with a custom MCP server that LLMs can call directly. The MCP server becomes the canonical surface for "things I want LLMs to know and record" across the homelab. ## Why - The email-as-transport pattern works but feels old-fashioned and forces schema/template duplication across every LLM project. - Schema changes (e.g., flattening folder structure) currently require editing every project's instructions. With an MCP server, the contract lives in one place — change the server, every LLM picks it up. - Establishes the auth and deployment pattern for future homelab tooling exposed to LLMs. - Mobile-friendly: once added on claude.ai, the connector works from the phone app. ## Architecture Claude (web/mobile/desktop) ↓ HTTPS Cloudflare Tunnel → mcp.herbylab.dev ↓ Authentik (OAuth) ↓ validated token MCP server (Python, us-test-authy) ↓ ┌────┴────┐ ↓ ↓ Vault git n8n webhook (commit + (downstream fan-out: push) tasks, Drive mirror, etc.) **Trust separation:** MCP = read + scoped write. Dispatch and future harnesses handle execution. The two surfaces never merge. ## Locked Decisions - [x] Single MCP server, organized by domain modules internally - [x] Streamable HTTP transport (not SSE — being deprecated) - [x] Authentik for OAuth - [x] Cloudflare tunnel → `mcp.herbylab.dev` → `us-test-authy` - [x] Python implementation - [x] Vault writes go direct (filesystem + git commit/push); n8n becomes downstream consumer for fan-out - [x] Read-only on Travis's side, enforced by not building edit/delete tools - [x] Execution stays out of MCP entirely — Dispatch's job - [x] Vault repo lives on `us-test-authy`; tower opens a local clone that pulls - [x] Gitea remote may move; not a blocker (one-line `git remote set-url`) ## Code Structure mcp_server/ tools/ vault.py # save_note, list_projects, get_project, get_schema memory.py # placeholder for OpenBrain / LLM wiki tasks.py # generic task tools, n8n forwards core/ auth.py # Authentik validation git_writer.py # commit + push logic schema.py # canonical schema dict server.py # registers tools from each module ## Phase 1 Tool Surface - `save_note(title, content, project, type, tags, status?)` — write markdown, assemble frontmatter, commit, push. Returns file path + git SHA. - `list_projects()` — enumerate existing project slugs with metadata. Prevents slug collisions. - `get_project(slug)` — fetch a project's notes for context loading. - `get_schema()` — return canonical valid values for `type`, `status`, `tags`, folder paths. **Solves the folder-flattening problem.** Also includes `knowledge_sources` field describing what each connected system is for (vault, openbrain, llm_wiki). ## Phase 2 Catalog (mapped, not built) **Vault depth:** - `search_notes(query, filters)` — full-text + tag/type/date filters - `list_recent_notes(days, filters)` - `get_note(path)` - `list_tags()` - `summarize_project(slug)` **Tasks (generic, n8n routes):** - `create_task(project, title, description?, frontmatter?, due?)` — n8n decides destination based on frontmatter - `list_tasks(project?, status?, filters?)` - `modify_task(task_id, updates)` — generic update covers complete/reopen/edit/reassign - Task IDs opaque to MCP server; n8n owns the ID space **Cross-system glue:** - `mirror_to_drive(note_path)` — Obsidian → Google Docs via n8n - `record_session_artifact(session_id, type, location)` — track session-generated files without putting them in the vault **Memory & knowledge (placeholder, design pending):** OpenBrain and LLM wiki integration deferred until workflow patterns establish. Open questions: - Unit of retrieval (note? fact? synthesized answer?) - Authoritative source (vault → memory, memory → vault, or peers?) - Read-only or read-write from LLM's side? - API-shaped or markdown-on-disk? Likely tool shapes (names will change): - `query_knowledge(question, sources?)` — federated semantic query - `get_context(topic, depth?)` — background assembly before starting work - `record_to_memory(content, system, metadata)` Architectural commitment: `tools/memory.py` exists from day one as empty module. Boundary stays. ## Implementation Order - [ ] Hello-world MCP server in Python (Streamable HTTP, single dummy tool) - [ ] Cloudflare tunnel + DNS for `mcp.herbylab.dev` - [ ] Verify end-to-end: connector added on claude.ai, tool callable from chat - [ ] Authentik OAuth integration - [ ] Phase 1 tools: `save_note`, `list_projects`, `get_project`, `get_schema` - [ ] Git commit/push integration with deploy key - [ ] Migrate from email flow; retire email trigger in n8n **Sequencing principle:** working transport before auth before real tools. Don't debug "is it the auth, the network, or my code?" all at once. ## Key Learnings From Design Session - **MCP is one of multiple LLM-facing surfaces.** MCP for knowledge/state, Dispatch for execution. Don't conflate trust classes. - **n8n stops being the front door but stays the integration hub.** Server calls n8n for fan-out (tasks, Drive mirroring); n8n keeps Google/Trello tight integration without owning the entry point. - **Schema-as-API is the unlock.** `get_schema()` makes the MCP server the canonical contract. Schema changes propagate without touching project instructions. - **Read-only-from-Travis eliminates the concurrent-write problem class.** MCP server is sole writer; Travis is consumer. No merge conflicts, no `.obsidian/workspace.json` thrash. - **Single server is correct until proven otherwise.** Code structured by domain so future split is mechanical, not a rewrite. - **Streamable HTTP, not SSE.** SSE deprecation is in motion — build on the durable transport from day one. - **Anthropic connects from their cloud, not from the user's device.** Tailscale alone won't work — server needs public HTTPS reachability. Cloudflare tunnel handles this. ## Open Items / Future Travis - Sharing model (collaborator access to specific tools) — defer until needed - Decision on Gitea hosting location - OpenBrain and LLM wiki workflow patterns before Phase 2 memory tools - Whether `save_note` auto-fans-out tasks from `## Tasks` sections, or stays explicit (current lean: explicit) ...sent from Jenny & Travis