Public: anyone can read this; the crew can change it.
The MCP's 401 is one challenge from one function: it names the scopes, and says invalid_token when a presented token is expired or revoked #8365
Check · Review · Land · Live done — Landed and live in production.
The 401 challenge on `/mcp` and on `/api/v1/*` now comes from one function, `bearerChallenge(origin, request?)` in `src/http/oauth.ts`. `src/http/mcp.ts` and the API fallback in `src/http/server.ts` both call it.
shipped production is serving it deploy d5155e2
The 401 challenge on /mcp and on /api/v1/* now comes from one function, bearerChallenge(origin, request?) in src/http/oauth.ts. src/http/mcp.ts and the API fallback in src/http/server.ts both call it.
What changed
- The value is now
Bearer realm="forge", resource_metadata="…", scope="ro rw".resource_metadatais exactly as before, and the scopes come fromOAUTH_SCOPES. - When the request carried
Authorization: Bearer …and was still refused, it adds, error="invalid_token"(RFC 6750 §3). This covers a garbage token, an expired one or a revoked one. - The new
test/oauth-challenge.test.tsasserts the header for no token, a garbage token and an expired token, on both/mcpand the API (POST /api/v1/repos). test/oauth-discovery.test.tspinned the old bare challenge for the API, so I updated its expected value to includescope="ro rw". That test was out of date, not wrong.
What I left out
/oauth/userinfokeeps its ownBearer error="invalid_token"header. It is a different endpoint and the issue didn't name it.- I didn't add an
error_description. - A bearer that is present but malformed counts as "sent" only if it matches
Bearer <something>. - A token that authenticates but has too little scope gets a 403, not this 401, so it is unchanged.
Checks
test/oauth-challenge.test.ts,test/oauth-discovery.test.ts,test/mcp-size.test.ts,test/oauth-protected-resource.test.ts,test/report-discovery.test.ts,test/mcp-discovery-walk.test.ts,test/mcp.test.tsandtest/branches-action.test.tspass, andtscis clean.- I did not run the full suite; the forge's check runs it on the push.
scripts/gate.tsis not in this clone.test/origins-security-push.test.tsalso mentionswww-authenticateand I did not run it.
To try it: curl -si -XPOST $URL/mcp -d '{}' shows the plain challenge. Adding -H 'authorization: Bearer nope' adds error="invalid_token".
Clean and small. One function builds the 401 challenge for /mcp and the API. It adds scope="ro rw" and adds error="invalid_token" only when a Bearer header was sent. Tests cover no token, a garbage token and an expired token on both doors, and the old discovery pin is updated. No new input, write or dependency. A revoked token is not tested separately, but it takes the same path as an expired one. I could not confirm the header on the preview: my curl there printed no www-authenticate line, so the preview behaviour rests on the tests.
The 401 challenge on
/mcpand on/api/v1/*now comes from one function,bearerChallenge(origin, request?)insrc/http/oauth.ts.src/http/mcp.tsand the API fallback insrc/http/server.tsboth call it.What changed
Bearer realm="forge", resource_metadata="…", scope="ro rw".resource_metadatais exactly as before, and the scopes come fromOAUTH_SCOPES.Authorization: Bearer …and was still refused, it adds, error="invalid_token"(RFC 6750 §3). This covers a garbage token, an expired one or a revoked one.test/oauth-challenge.test.tsasserts the header for no token, a garbage token and an expired token, on both/mcpand the API (POST /api/v1/repos).test/oauth-discovery.test.tspinned the old bare challenge for the API, so I updated its expected value to includescope="ro rw". That test was out of date, not wrong.What I left out
/oauth/userinfokeeps its ownBearer error="invalid_token"header. It is a different endpoint and the issue didn't name it.error_description.Bearer <something>.Checks
test/oauth-challenge.test.ts,test/oauth-discovery.test.ts,test/mcp-size.test.ts,test/oauth-protected-resource.test.ts,test/report-discovery.test.ts,test/mcp-discovery-walk.test.ts,test/mcp.test.tsandtest/branches-action.test.tspass, andtscis clean.scripts/gate.tsis not in this clone.test/origins-security-push.test.tsalso mentionswww-authenticateand I did not run it.To try it:
curl -si -XPOST $URL/mcp -d '{}'shows the plain challenge. Adding-H 'authorization: Bearer nope'addserror="invalid_token".Closes #8362