Skip to content

fix(auth): register peer links with authorization_code only - #120

Merged
chrisohearn merged 1 commit into
integration/mcp-events-20261109from
fix/peer-link-register-grant-types
Nov 14, 2026
Merged

chrisohearn merged 1 commit into
integration/mcp-events-20261109from
fix/peer-link-register-grant-types

Conversation

@chrisohearn

Copy link
Copy Markdown
Owner

What broke

Upstream T3 Code builds cannot link to a fork server. Upstream decodes the MCP OAuth register answer with AuthMcpRegisteredClient, whose grant_types only accepts "authorization_code". Since refresh-token renewal landed on the fork, /oauth/mcp/register answered every client with ["authorization_code", "refresh_token"]. Upstream's strict schema rejects that, so the link fails with "answered in a shape this version does not understand".

The fix

apps/server/src/auth/mcpOAuthHttp.ts: a registration that carries the peer-link software_id now gets grant_types: ["authorization_code"] only. Peer links never renew, and the token endpoint does not check a client's registered grant types, so a peer link registered this way still completes its authorization_code exchange. The rest of the link flow (decision redirect, token answer, and the token errors that path can return) already decodes with upstream's contract.

Refresh-token renewal for other MCP clients is unchanged. Every registration without the peer-link software_id still gets ["authorization_code", "refresh_token"].

Tests added

In apps/server/src/auth/McpOAuth.test.ts:

  • New test "registers a peer link in the shape upstream builds decode, and signs it in". It decodes the peer-link register answer against a local copy of upstream's AuthMcpRegisteredClient, checks that a non-peer registration does not match that schema (it still lists refresh_token), then runs the link sign-in through pairing-code approval and the token exchange to a token /mcp accepts as a peer link.
  • The existing loopback/https registration test now asserts that ordinary clients still get ["authorization_code", "refresh_token"].

Checks that ran

Node v26.13.1 (engines says 26.12.1); dependencies installed with npx -y pnpm@12.14.0 install --frozen-lockfile, which exited 0.

  1. Test fails on the base: with only mcpOAuthHttp.ts reverted, vp test run apps/server/src/auth/McpOAuth.test.ts gave 1 failed, 11 passed. The new test failed with expected [ 'authorization_code', …(1) ] to deeply equal [ 'authorization_code' ].
  2. With the fix, vp test run apps/server/src/auth/McpOAuth.test.ts: 12 of 12 passed (and again after the lint fix below).
  3. vp test run apps/server/src/auth apps/server/src/peer: 17 files, 217 tests, all passed.
  4. In packages/contracts, vp test run: 32 files, 176 tests, all passed (contracts unchanged).
  5. In apps/server, vp run typecheck exited 0 with no errors (only the existing effect(schemaClassMissingBrand) suggestions).
  6. vp fmt on the 2 changed files: clean.
  7. vp lint --report-unused-disable-directives on the 2 changed files: first run flagged t3code(no-inline-schema-compile) for an inline Schema.decodeUnknownEffect; the decoder and Schema.is moved to module scope and the re-run was clean.
  8. vp run check:changed origin/integration/mcp-events-20261109 exited 0: format, lint, knip 1/2 and 2/2, graphql (apps/server), typecheck (3 packages) and test (2 files, 14 tests) all passed. (Without a ref it picked origin/main as the base, 43753 files, and crashed with spawn E2BIG.)
  9. The pre-commit hook passed vp fmt, vp lint and knip:check.

🤖 Generated with Claude Code

Upstream T3 Code builds decode the MCP OAuth registration answer with
grant_types limited to "authorization_code". Since refresh-token renewal
landed, the register endpoint answered every client with
["authorization_code", "refresh_token"], so an upstream build linking to
a fork environment failed to decode the answer and reported "answered in
a shape this version does not understand".

A registration that carries the peer-link software_id now gets
["authorization_code"] only. Other clients still get refresh_token. Peer
links never renew, and the token endpoint does not check a client's
registered grant types, so a link registered this way still completes
its authorization_code exchange. The rest of the link flow (decision
redirect, token answer, and the token errors that path can return)
already decodes with upstream's contract.

The regression test checks the peer-link answer against a local copy of
upstream's AuthMcpRegisteredClient and runs the link sign-in through to
a working token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-14T22:48:48.988937Z be7a213 PR opened
🔒 Security Review ✅ Completed 2026-11-14T22:49:47.348207Z be7a213 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chrisohearn
chrisohearn merged commit 11a166e into integration/mcp-events-20261109 Nov 14, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant