Skip to content

CL-6391: unify MCP OAuth state onto ConnectStateStore - #160

Merged
TheGreatAxios merged 2 commits into
mainfrom
cl-6391-mcp-oauth-state
Aug 20, 2026
Merged

TheGreatAxios merged 2 commits into
mainfrom
cl-6391-mcp-oauth-state

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

What

The MCP-server OAuth connect flow (packages/connections/src/mcp-oauth-routes.ts) carried its own sealed-state machinery — TTL, nonce minting, AAD, cipher calls — in parallel to the one-shot ConnectStateStore that pkce.ts already provides for the fixed-registry OAuth connectors.

  • createConnectStateStore is now parameterized over a caller-shaped payload: the store owns the hardened envelope (user binding, TTL, per-flow AAD domain separation, single-use replay guard), while each connect flow declares and parses its own payload schema at the trust boundary.
  • oauth-routes.ts seals its verifier payload through the same shape (the CL-6394 empty-verifier handling for non-PKCE flows is preserved).
  • mcp-oauth-routes.ts drops its duplicate machinery entirely and consumes the shared store one-shot; the PKCE codeVerifier carry from CL-6394: route OAuth connects through the tenant-scoped mount; one shared persist-and-seed sequence #149 is intact.

Why

Hardening and dedup: two implementations of security-sensitive state handling meant two places to audit and keep aligned. The MCP flow now inherits the shared store's single-use consumption — a replayed callback is rejected at the state check instead of reaching the token endpoint a second time — plus the same replay-guard sweep and domain-separated AAD the other connect flows already had.

Tests

Store-level: payload round-trip, one-shot consumption (replay rejected), TTL expiry, cross-provider AAD rejection, parser-refused payloads, restart/key-rotation behavior. Route-level: a replayed MCP callback redirects with state_expired without a second token exchange; the full connect-flow suite (stub RFC 9728/8414/7591 authorization server) is unchanged and green.

Refs CL-6391 (slate ticket — further items remain).

Cover the sealed-state round trip carrying a caller-shaped payload,
one-shot consumption (a replayed MCP callback is rejected before a
second token exchange), TTL expiry, cross-provider AAD rejection, and
payloads the caller's parser refuses.
The MCP-server connect flow carried its own sealed-state machinery
(TTL, nonce, AAD, cipher calls) beside the store ./pkce.ts already
provides. The store now seals a caller-shaped payload inside its
hardened envelope (user binding, TTL, single-use replay guard,
per-flow AAD), the fixed-registry OAuth routes seal their verifier
through the same shape, and the MCP routes consume it one-shot -- a
replayed callback now dies at the state check instead of reaching the
token endpoint a second time.
@TheGreatAxios
TheGreatAxios merged commit adcb509 into main Aug 20, 2026
0 of 2 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-6391-mcp-oauth-state branch August 25, 2026 15:29
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