brain-mcp hosting¶
brain-mcp is the HTTP transport of the same MCP server described in
MCP server — the untouched dispatch() from
mcp_server.py, plus the write tools registered by mcp_write_tools.py,
now reachable over the network instead of stdio. It's the write surface the
hourly Claude-driven brain routine calls (Option B; see
2026-08-30-brain-routine-option-b-design.md).
It runs as its own container in prod, behind the Cloudflare tunnel, and is
never reachable directly — TLS terminates at Cloudflare.
Endpoint contract¶
GET /healthz— unauthenticated, always200 ok(used by the container healthcheck and by cloudflared/uptime checks).POST /mcp(also accepted at/) — the JSON-RPC 2.0 endpoint. Every request must carryAuthorization: Bearer <JARVIS_BRAIN_MCP_TOKEN>; a missing, malformed, or wrong token gets401with no body. A validtools/listcall returns all 20 tools (6 read + 14 write).- Everything else (
GETon any other path, wrong method, oversized or malformed body) is rejected beforedispatch()ever runs.
The process refuses to start if JARVIS_BRAIN_MCP_TOKEN is unset or shorter
than 32 characters — there is no way to accidentally expose the write
surface with a weak or empty token.
Local run¶
Point it at the local dev stack (the docker compose up stack published on
:8091; see Local run recipe):
JARVIS_BRAIN_MCP_TOKEN=$(openssl rand -hex 24) \
PYTHONPATH="agent-runtime/src:modules/conversation/agent" \
python3 -m conversation_agent.agent serve-mcp-http
This binds 0.0.0.0:8090 by default (JARVIS_BRAIN_MCP_HOST /
JARVIS_BRAIN_MCP_PORT override it). Point POCKETBASE_URL at the local
stack's PocketBase and set DEV_USER_EMAIL / DEV_USER_PASSWORD the same
way you would for serve-chat or serve-mcp.
Prod deployment¶
brain-mcp ships as a service in compose.prod.yaml: same jarvis-embed:prod
image as chat-worker (for the fastembed model cache), same vault /
chat_data / model_cache volumes, no published host port. It's added to
REQUIRED_CONTAINERS in ops/jarvis-deploy.sh, so a deploy fails loudly if
the container isn't healthy afterward — including if the owner forgot to set
the token (the process exits immediately, the healthcheck never turns green,
and the deploy gate catches it).
Four steps are owner-only — none of them run from CI, and none of them run automatically as part of a deploy. Run each of these yourself, in order, before merging the PR that ships this service:
1. Add the token to the server's .env (idempotent — skips if a token is
already set, rather than appending a second, shadowed value):
ssh mailserver 'umask 077; grep -q "^JARVIS_BRAIN_MCP_TOKEN=" /opt/jarvis/.env 2>/dev/null && echo "JARVIS_BRAIN_MCP_TOKEN already set" || echo "JARVIS_BRAIN_MCP_TOKEN=$(openssl rand -hex 24)" >> /opt/jarvis/.env'
Then print the current value back, so you can copy it into the cloud credential store the routine will read from (PR ③):
ssh mailserver "grep JARVIS_BRAIN_MCP_TOKEN /opt/jarvis/.env"
2. Cloudflare dashboard — add the public hostname. The tunnel's hostname→service mapping is managed remotely in the Cloudflare dashboard, not in a repo config file. Add a public hostname rule, placed above any catch-all rule:
- Public hostname:
brain-mcp.richarddepierre.com - Service:
http://brain-mcp:8090 - Same tunnel that already serves
jarvis.richarddepierre.com
3. Refresh the forced-command deploy script. The active copy the SSH
forced command runs is /usr/local/bin/jarvis-deploy.sh, deliberately
outside /opt/jarvis so a deploy payload's rsync can never touch it (see
Backend auto-deploy). It has to be refreshed by hand —
this must happen before merging this PR, because the very next deploy
will otherwise run the old script, which doesn't know about
jarvis-brain-mcp and won't gate on it:
ssh mailserver "cp /opt/jarvis/ops/jarvis-deploy.sh /usr/local/bin/jarvis-deploy.sh && chmod +x /usr/local/bin/jarvis-deploy.sh"
4. After the next deploy ships, verify read-only:
curl -s -o /dev/null -w '%{http_code}\n' https://brain-mcp.richarddepierre.com/healthz
# expect: 200
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://brain-mcp.richarddepierre.com/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# expect: 401 (no token)
curl -s https://brain-mcp.richarddepierre.com/mcp \
-H "Authorization: Bearer $JARVIS_BRAIN_MCP_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | python3 -m json.tool
# expect: a result with 20 tools