feat(ingestion): add OAuth 2.0 client credentials flow to brain MCP server #5
Closed
opened 2026-05-10 20:57:28 +00:00 by mathias
·
2 comments
No Branch/Tag Specified
main
fix/pipeline-writes-158
fix/edit-path-io-192
fix/create-regular-188
fix/guard-special-179
fix/linkcheck-keeps-write-178
fix/write-dir-redirect-173
fix/tunnel-detect-174
fix/pipeline-root-157
w3/154-mcp-auth-wiring
fix/tunnel-traversal
test/in-brain-targets-171
fix/brain-scan-root-157
fix/graph-wiki-root-157
fix/vectorstore-root-157
fix/notes-root-157
fix/search-root-157
fix/brainwide-root-157
fix/raw-symlinks-153
feat/symlink-tripwire-157
fix/rest-auth-149
fix/123-brain-edit-envelope-check
feat/106-page-aware-extraction
fix/105-source-drift-on-main
fix/104-surface-merge-conflicts-on-main
agent/b1-compile-on-ingest
agent/104-surface-merge-conflicts
agent/105-source-drift
agent/107-loud-unconfigured-alerting
feat/brain-read-edit
fix/capture-i1-refusal-names-target
renovate/configure
feat/consolidate-single-harness
icebox/cmd-routing
feat/brain-pending-promote
fix/close-session-classification-refresh
fix/capture-summarywriter
v0.24.0
v0.23.1
v0.23.0
v0.22.0
v0.21.1
v0.21.0
v0.20.1
v0.20.0
v0.19.1
v0.19.0
v0.18.4
v0.18.3
v0.18.2
v0.18.1
v0.18.0
v0.17.0
v0.16.1
v0.16.0
v0.15.0
v0.14.0
v0.13.2
v0.13.1
v0.13.0
v0.12.1
v0.12.0
v0.11.2
icebox/cmd-routing-2026-07-01
v0.11.1
v0.11.0
v0.10.0
v0.9.0
v0.8.0
v0.7.0
v0.6.0
v0.5.0
v0.4.1
v0.4.0
v0.3.0
v0.2.0
v0.1.0
Labels
Clear labels
agent/ready
blocked/client
blocked/supplier
blocked/upstream
risk/high
risk/low
risk/medium
size/l
size/m
size/s
size/xs
status:dispatch
status:dispatched
status:review
status:reviewing
type/chore
type/decision
type/docs
type/feat
type/fix
type/ops
type/research
type/test
Meets the agent-ready contract; authorizes unattended execution
Waiting on the client
Waiting on a supplier
Waiting on an upstream project or vendor
Irreversible, security-relevant, or control plane
Reversible, local, test-covered
Touches shared state, deploys or data
Too big - split it
Under two days
Under half a day
Under an hour
Maintenance, no behaviour change
Human decision required; never agent/ready
Documentation only
New capability
Corrects broken behaviour
Infrastructure, deploy, runtime
Investigate; output is a finding, not code
Test coverage only
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mathias/hyperguild#5
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
claude.ai only supports OAuth 2.0 for remote MCP servers — no static Bearer token option in the UI (only client ID + client secret fields). The brain MCP endpoint at
https://brain-mcp.d-ma.be/mcpcurrently uses a static Bearer token (BRAIN_MCP_TOKEN), which claude.ai cannot use.Result: "Couldn't reach the MCP server" in claude.ai — the server returns
401with noWWW-Authenticateheader, so claude.ai has no way to authenticate.Solution
Implement a minimal OAuth 2.0 client credentials flow on the ingestion server. claude.ai exchanges a client ID + secret for an access token, then uses that token as the Bearer on
/mcp. The existingBearerAuthmiddleware stays unchanged — the issued token is justBRAIN_MCP_TOKEN.What to build
New package:
ingestion/internal/oauth/metadata.go— servesGET /.well-known/oauth-authorization-server:token.go— servesPOST /oauth/token:grant_type=client_credentials+client_id+client_secret(form-encoded or Basic auth)OAUTH_CLIENT_ID+OAUTH_CLIENT_SECRETenv vars{"access_token": "<BRAIN_MCP_TOKEN>", "token_type": "bearer"}400/401on bad grant type or wrong credentialsRoutes added to
ingestion/cmd/server/main.goExisting
/mcproute withBearerAuthunchanged.New env vars
OAUTH_CLIENT_IDOAUTH_CLIENT_SECRETBoth required at startup (exit 1 if missing, same pattern as
BRAIN_MCP_TOKEN).Setup after shipping
OAUTH_CLIENT_ID+OAUTH_CLIENT_SECRETin the ingestion pod Secret (SOPS-encrypted in infra repo)https://brain-mcp.d-ma.be/mcp, client ID + secret matching aboveOut of scope
BRAIN_MCP_TOKENis sufficient)Superseded by #6. Dex is already deployed at
auth.d-ma.be— the correct approach is JWT validation middleware with static token fallback, not a bespoke client credentials server in the ingestion pod. Closing in favour of #6.Shipped in
58c5741. CI/CD auto-rebuilds the ingestion image and updates infra.What landed
New package
ingestion/internal/oauth:MetadataHandler(issuer)—GET /.well-known/oauth-authorization-serverTokenHandler(cfg)—POST /oauth/tokenwithclient_credentialsgrant onlyWiring in
cmd/server/main.go: routes mount only when bothOAUTH_CLIENT_IDandOAUTH_CLIENT_SECRETare set. Setting one alone → exit 1 (deliberate fail-fast on misconfiguration).MCP_RESOURCE_URLdoubles as the issuer.The access token returned is
BRAIN_MCP_TOKEN— no JWT machinery downstream, so the dual-auth middleware shipped in #9 already accepts it.Acceptance criteria
Tests (
ingestion/internal/oauth/{metadata,token}_test.go):client_secret_post: form-encodedclient_id/client_secret→ 200 +{access_token, token_type: "bearer"}client_secret_basic: HTTP Basic header → 200client_secret→ 401 +{"error":"invalid_client"}grant_type(e.g.password) → 400 +{"error":"unsupported_grant_type"}:→ 401Setup before claude.ai can use it
OAUTH_CLIENT_ID=brain-mcp-claudeai,OAUTH_CLIENT_SECRET=$(openssl rand -hex 32)infra/k3s/apps/supervisor/secrets.enc.yamlvia SOPSsecrets-revisionannotation bump or pod restart)https://brain-mcp.d-ma.be/mcp, client ID + secret matching above/.well-known/oauth-authorization-server→ exchanges creds → uses Bearer on/mcpClosing.