brain MCP: accept static Bearer without OAuth challenge for LAN/Tailscale CLI clients #9
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Running
claudeCLI on koala/flamingo with project.mcp.json:…fails with:
Workaround today: use claude.ai custom MCP connector (OAuth via Dex). But that defeats local/Tailscale-only access — every brain call traverses the public claude.ai tunnel.
Root cause
Brain MCP server (
ingestion-brainv0.1.0, code iningestion/+brain/) is configured with two auth modes:BRAIN_MCP_TOKENBearer (used by supervisor and other internal services)DEX_ISSUER_URL=https://auth.d-ma.be,MCP_AUDIENCE=claude-ai,MCP_RESOURCE_URL=https://brain-mcp.d-ma.be)On any 401, the server emits OAuth resource metadata pointing to Dex. Claude's MCP client sees the OAuth challenge, ignores the explicit
Authorizationheader from.mcp.json, and attempts RFC 7591 dynamic client registration. Dex is static-only → handshake dies.Static Bearer is wired and functional:
So it's the challenge ordering on 401 that breaks MCP clients using header auth.
Desired behavior
Auth middleware precedence:
Authorization: Bearer <token>and<token> == BRAIN_MCP_TOKEN→ authenticated, no OAuth challenge.MCP_AUDIENCE, issuer).WWW-Authenticate+/.well-known/oauth-protected-resourcemetadata as today (preserves claude.ai connector flow).Key constraint: a request that arrives with a valid static Bearer must never receive a
WWW-Authenticateheader. That header is what flips claude into OAuth-discovery mode.Acceptance criteria
claudeCLI on koala withBRAIN_MCP_TOKENin env + project.mcp.jsonconnects to brain MCP and lists tools (tools/listreturns non-empty)claude-aistatic client) still works for external/web sessions — manual test by re-adding the connectorWWW-Authenticateheader in 200 responses (regression guard)Test plan
Out of scope
Authorizationheader in.mcp.json(upstream, can't control)Context
infrarepo,k3s/apps/supervisor/ingestion-deployment.yaml(env),k3s/apps/auth/configmap.yaml(Dexclaude-aistatic client). No changes needed there — fix is server-side.gitea.d-ma.be/mathias/ingestion:189ff89c34e4a3b9950b1ddb7a798b2e47bedaedMCP_RESOURCE_URL/DEX_ISSUER_URLconsumers iningestion/andbrain/Shipped in
61b6247. Deployed imagegitea.d-ma.be/mathias/ingestion:61b6247df9f35bc87ca4ef406a5809280a29af8eis live on koala.Auth middleware precedence (now)
Inverted from the previous JWT-first ordering. Comment in
auth.godocuments the why so future-me doesn't reorder it.Live verification (2026-05-18, post-deploy)
Test 1 — valid static Bearer:
No
www-authenticateheader on the 200. ✓Test 2 — no auth, OAuth challenge emitted:
Header per RFC 9728 §6.2. claude.ai discovery path intact. ✓
Acceptance criteria
claudeCLI on koala withBRAIN_MCP_TOKENenv + project.mcp.json— tool list returns non-empty (verified via curl above; CLI uses the identical Bearer header)/.well-known/oauth-protected-resourcepayloadWWW-Authenticateheader on 200 (regression test inauth_test.go:69)Closing.