feat(mcp): opt-in HTTP (streamable) transport so clients auto-reconnect (#440) - #441
Merged
Merged
Conversation
…ct (#440) The MCP server was stdio-only and the documented deploy runs it as `docker compose exec app /brainiac-mcp` — a subprocess inside the app container. Every `docker compose up -d` / `brainiac update` recreates that container and kills the subprocess in every client; MCP clients do not auto-restart stdio servers (they do auto-reconnect HTTP/SSE ones), so each session had to manually reconnect. Serve the same tool surface over streamable HTTP at POST/GET /mcp on the app's chi router (mcp.NewStreamableHTTPHandler wrapping mcpserver.New). Clients register an HTTP transport that reconnects on its own across app restarts. - Opt-in: HTTP.mcp / MCP_HTTP env (+ compose passthrough). Off by default; stdio stays the default transport. - Gated by the existing bearerAuth(AUTH_TOKEN) — the endpoint exposes write tools and the app binds localhost. - Fail-closed config: MCP_HTTP requires AUTH_TOKEN, and is Layer-1 only (rejected with configured principals — a single-principal HTTP surface would bypass the per-request isolation the /api routes enforce). - v1 omits the ingest tool over HTTP (nil importFn; stdio/CLI keep it). Tests: /mcp bearer gate (401 without/with bad token, initialize past the gate) and not-mounted-by-default; config validation guards. SYSTEM.md + .env.example updated. Follow-ups (#440): per-connection principal from the bearer token; ingest parity. Register: claude mcp add --transport http brainiac http://localhost:8088/mcp --header "Authorization: Bearer $AUTH_TOKEN" Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #440.
Problem
MCP is stdio-only; the documented deploy runs
docker compose exec app /brainiac-mcp— a subprocess inside the app container. Everydocker compose up -d/brainiac updaterecreates the container, killing that subprocess in every connected client. MCP clients do not auto-restart stdio servers (they do auto-reconnect HTTP/SSE ones), so every routine update forced a manual reconnect in every session.Change
Serve the same tool surface over streamable HTTP at
POST/GET /mcpon the app's existing chi router (mcp.NewStreamableHTTPHandlerwrappingmcpserver.New). Clients register it as an HTTP transport and reconnect automatically across restarts.HTTP.mcpconfig /MCP_HTTPenv (+ compose passthrough). Off by default; stdio stays the default transport — no behaviour change unless enabled.bearerAuth(AUTH_TOKEN)— the endpoint exposes write tools and the app binds127.0.0.1.MCP_HTTPrequiresAUTH_TOKEN, and is Layer-1 only — rejected when hard-isolation principals are configured, since a single-principal HTTP surface would bypass the per-request isolation/apienforces (Layer 2 (future): hard visibility isolation for teams (read-scope + security) #120/Layer 2 foundation: per-token principal + enforced read-wall + write-pin (#120) #185).ingesttool omitted over HTTP (nilimportFn; stdio MCP + CLI keep it).Verification (live, built from source)
Startup logs
MCP over HTTP ON: …. Unit tests:/mcpbearer gate (401 without/with bad token;initializereturnsserverInfowith a valid token), not-mounted-by-default, and config fail-closed guards. Full suite green (config/server/mcpserver/core/store).Register it
→ survives
brainiac updatewith no manual/mcp.Follow-ups (in #440, out of scope here)
ingesttool parity over HTTP MCP.🤖 Generated with Claude Code