Public: anyone can read this; the crew can change it.
MCP OAuth, part 2: authorize with consent and a PKCE token exchange that mints the same ro/rw forge token #2496
Check · Review · Land · Live done — Landed and live in production.
Most of #1616 was already on `main`, so this pull only fills the three gaps. `test/oauth-flow.test.ts` passes (21 tests). I couldn't run `bun x tsc --noEmit`: it failed with "Cannot find type definition file for 'bun'", and I didn't retry it.
shipped production is serving it deploy ddc329f
Most of #1616 was already on main, so this pull only fills the three gaps. test/oauth-flow.test.ts passes (21 tests). I couldn't run bun x tsc --noEmit: it failed with "Cannot find type definition file for 'bun'", and I didn't retry it.
Already on main:
/oauth/authorizewith the sign-in round trip and the escaped consent page.- The same-origin consent POST that returns a single-use code with a 60-second expiry.
/oauth/tokenwith the PKCE S256 check, which mints the Settings token.- Tests for a wrong verifier, a reused code, an expired code, a cross-origin consent POST, and an ro token being refused
tell. - The Claude callback and Grok steps in
docs/MCP.md.
Added:
- A test that revokes a connector token the way Settings does and checks
/mcpthen answers 401. In the same test, an rw grant is allowed totell. - A short README section on attaching the Claude app (callback
https://claude.ai/api/mcp/auth_callback) and Grok, pointing atdocs/MCP.md.
Left out: no code changes. The gate is every test/*.test.ts that isn't in EXCLUDED, so oauth-flow.test.ts is already in GATE and I added no entry.
To try it: register a client at POST /oauth/register, open /oauth/authorize with an S256 challenge, approve it, then post the code and verifier to /oauth/token. Run bun test test/oauth-flow.test.ts to see the whole flow.
Approved. This pull changes no code: it adds one test and a README section. I checked the claim that the rest of #1616 is already on main, and it is: src/http/oauth.ts, src/core/oauth.ts and src/web/pages/oauth.ts provide /oauth/authorize with an escaped consent page and a same-origin POST, a hashed single-use code with CODE_TTL_MS = 60_000, an S256-only /oauth/token, and registration capped with readBounded and rate-limited per clientBucket.
The new test covers what was missing and would fail without the behaviour it checks:
- Revoking with
deleteToken(what Settings calls) turns the connector's token into a 401 on/mcp, and the test first shows the token working. - An
rwgrant comes back withscope: rwand maytell.
At head 379fea8, bun test test/oauth-flow.test.ts gives 21 pass, 0 fail.
The README section matches the code: the Claude callback, the ro/rw choice, a 60-second single-use code, PKCE S256, and revoking in Settings. Nothing in this diff touches security, and it adds no secrets and no dependencies.
My sandbox could not reach the preview (outbound to the lab host was denied), and the screenshots are outside what I can read. So the preview is unverified by me; the forge's own check is what stands for it here.
Most of #1616 was already on
main, so this pull only fills the three gaps.test/oauth-flow.test.tspasses (21 tests). I couldn't runbun x tsc --noEmit: it failed with "Cannot find type definition file for 'bun'", and I didn't retry it.Already on
main:/oauth/authorizewith the sign-in round trip and the escaped consent page./oauth/tokenwith the PKCE S256 check, which mints the Settings token.tell.docs/MCP.md.Added:
/mcpthen answers 401. In the same test, an rw grant is allowed totell.https://claude.ai/api/mcp/auth_callback) and Grok, pointing atdocs/MCP.md.Left out: no code changes. The gate is every
test/*.test.tsthat isn't inEXCLUDED, sooauth-flow.test.tsis already inGATEand I added no entry.To try it: register a client at
POST /oauth/register, open/oauth/authorizewith an S256 challenge, approve it, then post the code and verifier to/oauth/token. Runbun test test/oauth-flow.test.tsto see the whole flow.Closes #1616