Public: anyone can read this; the crew can change it.
OAuth for the MCP, step one: discovery documents, dynamic client registration, and a 401 that names them #579
Check · Review · Land · Live done — Landed and live in production.
The forge now serves both OAuth discovery documents, accepts dynamic client registration, and its `/mcp` 401 names the resource metadata. `test/oauth-discovery.test.ts`, `test/mcp.test.ts` and `test/check-list.test.ts` pass. I could not run `tsc --noEmit`, which `forge.json` runs: there is no `node_modules`, so it fails on the missing `bun` type definitions.
shipped production is serving it deploy cc205a8
Sign in to join this conversation.
new above
offline
The forge now serves both OAuth discovery documents, accepts dynamic client registration, and its
/mcp401 names the resource metadata.test/oauth-discovery.test.ts,test/mcp.test.tsandtest/check-list.test.tspass. I could not runtsc --noEmit, whichforge.jsonruns: there is nonode_modules, so it fails on the missingbuntype definitions.What changed
GET /.well-known/oauth-protected-resource(RFC 9728) andGET /.well-known/oauth-authorization-server(RFC 8414), both built from the request's public origin. They give the issuer, the authorization, token and registration endpoints,S256only, and scopesroandrw.POST /mcpnow carriesWWW-Authenticate: Bearer realm="forge", resource_metadata="<origin>/.well-known/oauth-protected-resource". Bearer tokens work as before.POST /oauth/register(RFC 7591) returns 201 with aclient_idand stores anoauth_clientrow.- It refuses
redirect_uristhat are missing, empty or over 10, over 2000 characters, or not https (http only for localhost,127.0.0.1or[::1]). It also refuses a URI carrying credentials or a fragment. - It refuses aclient_nameover 100 characters or containing control characters, anytoken_endpoint_auth_methodother thannone, and bodies over 16 KB. - Refusals are 400s withinvalid_redirect_uriorinvalid_client_metadata.RateLimiterfromsrc/core/ratelimit.tsand answering 429 withRetry-After. The address is the socket peer. Behind a proxy on loopback it is the firstX-Forwarded-Forhop, so the header cannot be spoofed by an outside caller.oauth_clientwithCREATE TABLE IF NOT EXISTS. The test runs it twice.test/oauth-discovery.test.tsis added toscripts/gate.ts, whichtest/check-list.test.tsrequires.src/core/oauth.tsandsrc/http/oauth.ts, wired throughsrc/core/schema.ts,src/http/mcp.tsandsrc/http/server.ts.What I left out
/oauth/authorizeand/oauth/tokenare named in the documents but not served yet; that is the next step.access-control-allow-origin: *, butOPTIONSis not routed.How to try it
Tests that start the server need to run outside this agent sandbox. It blocks the unix socket the server listens on.
🤖 Generated with Claude Code
Closes #573