Two-layer structure: Sources (raw notes) + Wiki (compile output) Four domains: Dev (40), Venture (3), Homelab (23), Reference (0) Includes CLAUDE.md spec, index pages at all levels, compile log Co-Authored-By: Lovebug <lovebug@herbylab.dev>
185 lines
6.5 KiB
Markdown
185 lines
6.5 KiB
Markdown
---
|
|
created: 2026-05-05
|
|
path: Sources/Homelab
|
|
project: homelab-mcp-server
|
|
status: active
|
|
tags:
|
|
- homelab
|
|
- mcp
|
|
- obsidian
|
|
- n8n
|
|
- authentik
|
|
- cloudflare
|
|
type: project-plan
|
|
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 |