From 9a10c8d1b2bb0fe1b83c8f9df5344bccbc501b4c Mon Sep 17 00:00:00 2001 From: herbygitea Date: Tue, 5 May 2026 16:21:16 +0000 Subject: [PATCH] Create homelab-mcp-server.md via n8n --- Tech/Projects/homelab-mcp-server.md | 184 ++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 Tech/Projects/homelab-mcp-server.md diff --git a/Tech/Projects/homelab-mcp-server.md b/Tech/Projects/homelab-mcp-server.md new file mode 100644 index 0000000..52c1ed4 --- /dev/null +++ b/Tech/Projects/homelab-mcp-server.md @@ -0,0 +1,184 @@ +--- +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 \ No newline at end of file