burntai.com

RSS

Connecting Claude to the Homelab: Building an MCP Server

June 4, 2026 · 3 min read

ClydeNexus — the command center for this homelab — has always had a chat interface. You open a browser, ask it something, and it queries the fleet, pulls logs, checks spend, runs tasks. That works fine from a browser. But I wanted to flip it: instead of going to ClydeNexus, I wanted to bring the fleet tools into wherever I'm already working.

That turned out to be MCP.

What MCP Is

The Model Context Protocol is an open standard for connecting AI assistants to external tools over HTTP. Claude, Claude.ai, and a growing number of clients support it. You register a server URL, and the assistant gets a list of tools it can call — the same way you'd define tools in the API directly, except they live on your server instead of in the prompt.

The protocol is JSON-RPC 2.0 over HTTP, with an optional SSE stream for server-initiated events. From the server's perspective, you implement three methods: initialize (handshake), tools/list (return your tool schemas), and tools/call (execute a tool and return the result).

The Setup

ClydeNexus already had 36 tools defined in tools.py — everything from get_fleet_status and search_fleet_logs to submit_task and save_memory. These were wired into the WebSocket chat handler. The MCP server is a separate FastAPI app (mcp_server.py) that imports those same tool implementations directly and wraps them in the MCP protocol. No duplicated logic — same code, different transport.

The server runs on port 7856 as clydenexus-mcp.service, exposed publicly at https://mcp.burntai.com via the DMZ nginx proxy.

Tool subset is 24 of 36. The excluded ones are destructive or internal: deploy_cc_agent, provision_and_onboard, and a few others that shouldn't be reachable from a public endpoint even with auth.

The Auth Problem

Bearer token auth was the obvious starting point. Generate a token, check it on every request, done. Except claude.ai's connector doesn't send a Bearer token — it uses OAuth Dynamic Client Registration (RFC 7591) to get one first.

The flow looks like this:

  1. Claude.ai fetches /.well-known/oauth-authorization-server to discover endpoints
  2. It POSTs to /register to register itself as a client
  3. It GETs /authorize to get an authorization code
  4. It POSTs to /token to exchange the code for an access token
  5. It uses that token on subsequent requests

So I implemented all five endpoints. They auto-approve everything — this is a homelab, the authorization model is "is this the claude.ai connector I registered." Tokens are in-memory and lost on restart; claude.ai just re-registers on reconnect.

One additional quirk: if no Authorization header is present at all, the server allows the request. This handles clients that call tools/list before completing the OAuth flow.

What It Can Do

Once registered, claude.ai gets 24 tools covering:

In practice this means I can open a new Claude conversation, ask "which hosts had errors in the last hour," and get a real answer pulled from /var/log/clydestack/ across the fleet — without opening ClydeNexus or SSHing into anything.

What's Interesting About This Pattern

The MCP server itself is about 300 lines. Most of it is OAuth boilerplate. The actual tool dispatch is a dict lookup and a function call. The hard part was already done: the tool implementations, the fleet clients, the memory layer. MCP just adds an HTTP adapter on top.

It also makes the tooling available from any MCP-compatible client, not just claude.ai. The same server works with any client that speaks the protocol.

The server is live at https://mcp.burntai.com/mcp — not useful to anyone outside this homelab, but the pattern (wrap existing internal tools in MCP, expose via reverse proxy, handle OAuth for claude.ai) is straightforward enough to be worth documenting.

← all posts

← olderDiagnosing a WiFi Retry Storm with UniFi API