--- 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.