wiki-vault/Sources/Homelab/2026-05-06-homelab-mcp-server-stand-up.md
Travis Herbranson 34a268d8dc migration: copy 62 notes from pbs-projects and homelab-projects
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>
2026-05-08 17:21:13 -04:00

69 lines
5.3 KiB
Markdown

---
created: '2026-05-06'
path: Sources/Homelab
project: homelab-mcp-server
status: active
tags:
- mcp
- authentik
- cloudflare
- obsidian
- n8n
type: session-notes
updated: '2026-05-06'
---
# Homelab MCP Server Stand-Up
First successful end-to-end deployment of the homelab MCP server. This very note is the inaugural write through the new pipeline — vault saved via `save_vault_note` instead of email-to-n8n.
## Outcome
Working remote MCP connector reachable from Claude (web and mobile), authenticated via Authentik OAuth, fronted by Cloudflare Tunnel. Four Phase 1 tools live: `get_vault_schema`, `list_vault_projects`, `get_vault_project`, `save_vault_note`.
## What got built
Lovebug had the stack scaffolded before the session started — Python MCP server (FastMCP, Streamable HTTP), Authentik docker stack, cloudflared placeholder in compose, schema/git-writer modules. This session was the configuration and wiring pass.
- Authentik admin account + OAuth2 provider + application created via the wizard
- Cloudflare tunnel `homelab-mcp` with two public hostnames: `mcp.herbylab.dev``homelab-mcp:8080`, `auth.herbylab.dev``authentik-server:9000`
- All containers on the auto-created default Docker network; cloudflared reaches services by container name, not localhost
- `.env` populated with public Authentik URLs for issuer/JWKS
- Connector registered on claude.ai with OAuth client ID/secret from Authentik
## Issues hit and resolved
- **Wrong tab in Cloudflare ZT dashboard.** Started in "Private Network" (which is the WARP-only feature) instead of "Public Hostname" / "Published applications." Cleared up by going into the tunnel detail page directly and using the right tab.
- **502 from initial curl.** Cloudflare tunnel URLs were set to `localhost:8080` and `localhost:9000`, but cloudflared runs as its own container with no host networking. Fix: change tunnel URLs to use container names (`homelab-mcp:8080`, `authentik-server:9000`) since all services share the default compose network.
- **Authentik external URL "problem" was a non-problem.** Authentik 2025.4 derives its issuer URL from the request Host header automatically. No brand setting change needed; the discovery URL through `auth.herbylab.dev` returned the correct public issuer immediately.
- **Missing OAuth Protected Resource Metadata.** Claude expects `/.well-known/oauth-protected-resource` per RFC 9728. Initial server only served `/.well-known/oauth-authorization-server`. Lovebug added the protected-resource document.
- **Invalid Host header rejection.** FastMCP's transport security check defaulted to localhost-only and was rejecting `mcp.herbylab.dev` with 421. Lovebug added `mcp.herbylab.dev` to the allowed hosts list.
- **`list_vault_projects` errors on directories ending in `.md`.** First test surfaced a bug — tool assumes every `*.md` match is a file and `os.open()`s it. Real vault has at least one folder named `pbs-membership-loe1-phase3.md`. Needs `os.path.isfile` filter. Open for Lovebug.
## Architecture decisions worth recording
- **Streamable HTTP transport, not SSE.** SSE deprecation is in motion; built on the durable transport.
- **Single MCP server, organized by domain modules internally.** Multiple servers were considered for trust scoping (vault read/write vs server-side execution). Decided trust separation lives between MCP and Dispatch instead — MCP for knowledge/state, Dispatch for execution. Code structured by domain so future split is mechanical, not a rewrite.
- **n8n stops being the front door but stays the integration hub.** Server writes vault directly + commits + pushes; n8n becomes downstream consumer for fan-out (tasks, Drive mirroring) when those tools come online.
- **Read-only-from-Travis enforcement by absence.** No edit/delete tools exist. Eliminates the concurrent-write problem class entirely. Vault repo on dev server, tower opens a local clone that pulls.
- **TLS terminates at Cloudflare's edge.** Internal cloudflared → service traffic stays HTTP since both are on the same host network. Standard tunnel pattern; no internal TLS plumbing needed.
## Schema additions Lovebug shipped beyond the spec
- `domains` taxonomy (Dev / Content / Homelab / Reference) layered above folder paths
- `knowledge_sources` populated for all three (vault, openbrain, llm_wiki) — placeholder design held up
- `frontmatter_templates` with required-field declarations and concrete examples per note type
## What's next
- Save the project plan as a separate `project-plan` note (use the draft from the design session)
- Fix `list_vault_projects` directory bug
- Retire the email-to-n8n trigger once a few real notes have flowed through
- Phase 2 catalog: search tools, generic task tools (n8n routes), cross-system glue, memory/OpenBrain integration when workflow patterns establish
## Key learnings
- Most "missing UI option" Authentik problems are version drift; check the discovery URL through the public domain *first* before hunting settings.
- For tunnels: `cloudflared` container's `localhost` is the container, not the host. Use container names when all services share a compose network.
- OAuth metadata for MCP requires *both* RFC 8414 (Authorization Server) *and* RFC 9728 (Protected Resource). Many implementations only ship the first.
- "Failed" tool calls are still a successful end-to-end test — every layer below the failure had to work to surface the bug.