pbs-projects/Tech/Projects/homelab-mcp-server.md

6.5 KiB

project type status path tags created updated
homelab-mcp-server project-plan active Tech/Projects
homelab
mcp
obsidian
n8n
authentik
cloudflare
2026-05-05 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

  • Single MCP server, organized by domain modules internally
  • Streamable HTTP transport (not SSE — being deprecated)
  • Authentik for OAuth
  • Cloudflare tunnel → mcp.herbylab.devus-test-authy
  • Python implementation
  • Vault writes go direct (filesystem + git commit/push); n8n becomes downstream consumer for fan-out
  • Read-only on Travis's side, enforced by not building edit/delete tools
  • Execution stays out of MCP entirely — Dispatch's job
  • Vault repo lives on us-test-authy; tower opens a local clone that pulls
  • 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