Skip to content

feat: integrate /x/mcp with user_session_issuer_id#2926

Merged
bflad merged 2 commits into
mainfrom
bflad-age-2395-integrate-xmcp-with-user_session_issuer_id-HhdA
May 20, 2026
Merged

feat: integrate /x/mcp with user_session_issuer_id#2926
bflad merged 2 commits into
mainfrom
bflad-age-2395-integrate-xmcp-with-user_session_issuer_id-HhdA

Conversation

@bflad
Copy link
Copy Markdown
Member

@bflad bflad commented May 19, 2026

https://linear.app/speakeasy/issue/AGE-2395/integrate-xmcp-with-user-session-issuer-id

The mcpServers.create and mcpServers.update management endpoints now accept an optional user_session_issuer_id, and the McpServer result type carries it on read. When set on an mcp_server, /x/mcp requests are issuer-gated: callers without a valid Authorization receive 401 + WWW-Authenticate pointing at /.well-known/oauth-protected-resource/x/mcp/{slug}, and the full Gram-hosted OAuth surface — RFC 7591 dynamic client registration, authorize, IDP callback, consent, token, revoke — is mounted under /x/mcp/{slug}/... against the same JWT-validation machinery /mcp uses. Issued tokens are bound to urn.NewUserSessionIssuer(...) as their audience so they stay portable between toolset-backed and remote-backed mcp_servers under the same issuer. Both well-known metadata routes under /x/mcp now return the issuer-gated metadata shape for any addressed mcp_server with an issuer set, including remote-backed servers (previously 404).

The /mcp OAuth surface is reorganised but unchanged in behaviour. A new ResolvedMcpEndpoint type captures the backend-neutral resolved-endpoint shape (project, org, issuer, audience, route base, slug, custom domain, plus the backend-specific ToolsetID or McpServerID) and owns URL construction via RootURL, ProtectedResourceURL, ConsentURL, IDPCallbackURL, and the consolidated AuthorizationServerURLs helper. A new ChallengeEndpointRef replaces LegacyMcpEndpointRef plus the top-level RouteBase and McpServerID fields on AuthnChallengeState, capturing only what's needed to re-resolve a cached in-flight challenge through the right addressing path (toolset-keyed for /mcp, mcp_endpoint-keyed for /x/mcp). The seven issuer-gated /mcp OAuth handlers (HandleRegister, HandleAuthorize, HandleConsent, HandleToken, HandleRevoke, HandleGetProtectedResource, HandleGetAuthorizationServer) each split into a chi handler that resolves a slug through the toolsets path plus a public Serve* post-resolution variant taking *ResolvedMcpEndpoint; the Serve* methods are what xmcp's per-route adapters dispatch to. ApplyIssuerGate is exposed as a public wrapper so xmcp can pre-gate before backend dispatch, and ServeToolsetResolved gains skipIssuerGate plus extraUpstreamTokens parameters so xmcp's toolset-backed branch doesn't double-gate. HandleIDPCallback dispatches resolution on the cached ChallengeEndpointRef.McpServerID so the global callback resumes a challenge on the route surface it was minted under, even when both /mcp and /x/mcp address the same underlying mcp_server. The remote-session leg gets the same surface-awareness: remotesessions.ParentChallenge and the cached RemoteLoginState now thread a RouteBase ("mcp" or "x/mcp"), and HandleRemoteLoginCallback bounces to /<RouteBase>/{slug}/connect instead of a hard-coded /mcp/ path, with empty values falling back to "mcp" so in-flight states minted before the field landed still resume cleanly.

@bflad bflad requested a review from a team as a code owner May 19, 2026 17:43
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 19, 2026

AGE-2395

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gram-docs-redirect Ready Ready Preview, Comment May 20, 2026 3:18pm

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 19, 2026

🦋 Changeset detected

Latest commit: 5ffee94

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
server Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

atlas migrate lint on server/migrations

Status Step Result
No migration files detected  
ERD and visual diff generated View Visualization
No issues found View Report
Read the full linting report on Atlas Cloud

@github-actions
Copy link
Copy Markdown
Contributor

atlas migrate lint on server/clickhouse/migrations

Status Step Result
No migration files detected  
ERD and visual diff generated View Visualization
No issues found View Report
Read the full linting report on Atlas Cloud

@bflad bflad force-pushed the bflad-age-2395-integrate-xmcp-with-user_session_issuer_id-HhdA branch from 5a5ad4b to b69c3db Compare May 19, 2026 18:38
Copy link
Copy Markdown
Contributor

@qstearns qstearns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this come through the PR queue earlier, but looks great. Will try to get started with the client tomorrow

Comment thread server/internal/xmcp/service.go
Comment thread server/internal/mcp/authnchallenge.go
@bflad
Copy link
Copy Markdown
Member Author

bflad commented May 20, 2026

@qstearns working on rebasing and addressing your comments. 👍

@bflad bflad force-pushed the bflad-age-2395-integrate-xmcp-with-user_session_issuer_id-HhdA branch from b69c3db to 73745da Compare May 20, 2026 13:34
@bflad bflad force-pushed the bflad-age-2395-integrate-xmcp-with-user_session_issuer_id-HhdA branch from 73745da to 047e388 Compare May 20, 2026 13:48
bflad added 2 commits May 20, 2026 11:15
https://linear.app/speakeasy/issue/AGE-2395/integrate-xmcp-with-user-session-issuer-id

The mcpServers.create and mcpServers.update management endpoints now
accept an optional user_session_issuer_id, and the McpServer result
type carries it on read. When set on an mcp_server, /x/mcp requests
are issuer-gated: callers without a valid Authorization receive 401 +
WWW-Authenticate pointing at /.well-known/oauth-protected-resource/x/mcp/{slug},
and the full Gram-hosted OAuth surface — RFC 7591 dynamic client
registration, authorize, IDP callback, consent, token, revoke — is
mounted under /x/mcp/{slug}/... against the same JWT-validation
machinery /mcp uses. Issued tokens are bound to urn.NewUserSessionIssuer(...)
as their audience so they stay portable between toolset-backed and
remote-backed mcp_servers under the same issuer. Both well-known
metadata routes under /x/mcp now return the issuer-gated metadata
shape for any addressed mcp_server with an issuer set, including
remote-backed servers (previously 404).

The /mcp OAuth surface is reorganised but unchanged in behaviour. A
new ResolvedMcpEndpoint type captures the backend-neutral resolved-endpoint
shape (project, org, issuer, audience, route base, slug, custom
domain, plus the backend-specific ToolsetID or McpServerID) and owns
URL construction via RootURL, ProtectedResourceURL, ConsentURL,
IDPCallbackURL, and the consolidated AuthorizationServerURLs helper.
A new ChallengeEndpointRef replaces LegacyMcpEndpointRef plus the
top-level RouteBase and McpServerID fields on AuthnChallengeState,
capturing only what's needed to re-resolve a cached in-flight
challenge through the right addressing path (toolset-keyed for /mcp,
mcp_endpoint-keyed for /x/mcp). The seven issuer-gated /mcp OAuth
handlers (HandleRegister, HandleAuthorize, HandleConsent, HandleToken,
HandleRevoke, HandleGetProtectedResource, HandleGetAuthorizationServer)
each split into a chi handler that resolves a slug through the
toolsets path plus a public Serve* post-resolution variant taking
*ResolvedMcpEndpoint; the Serve* methods are what xmcp's per-route
adapters dispatch to. ApplyIssuerGate is exposed as a public wrapper
so xmcp can pre-gate before backend dispatch, and ServeToolsetResolved
gains skipIssuerGate plus extraUpstreamTokens parameters so xmcp's
toolset-backed branch doesn't double-gate. HandleIDPCallback dispatches
resolution on the cached ChallengeEndpointRef.McpServerID so the global
callback resumes a challenge on the route surface it was minted under,
even when both /mcp and /x/mcp address the same underlying mcp_server.
The remote-session leg gets the same surface-awareness: remotesessions.ParentChallenge
and the cached RemoteLoginState now thread a RouteBase ("mcp" or "x/mcp"),
and HandleRemoteLoginCallback bounces to /<RouteBase>/{slug}/connect
instead of a hard-coded /mcp/ path, with empty values falling back to
"mcp" so in-flight states minted before the field landed still resume
cleanly.
https://linear.app/speakeasy/issue/AGE-2395/integrate-xmcp-with-user-session-issuer-id

Follow-up to 047e388. Closes coverage gaps in the /x/mcp surface,
renames the runtime tests onto a consistent
Test<Surface>_<Variant>_<Scenario> taxonomy, and extracts the
issuer-gated seed plumbing into a reusable helper.

New coverage:
- GET method through ServeMCP + proxy (the SSE-listen leg).
- tools/call end-to-end through the full interceptor pipeline.
- authorize / consent / token / revoke OAuth route adapters mounted
  by xmcp.Attach (previously only register was smoke-tested).
- Full register -> authorize -> consent -> token -> ServeMCP dance
  through the chi mux on a public-visibility endpoint.
- handleRemoteLoginCallback (zero coverage before) against a live
  devidptest upstream, asserting the /x/mcp RouteBase threads through
  to the post-callback redirect.
- Custom-domain mismatch resolution: an endpoint scoped to a custom
  domain must not resolve on the platform domain.
- Dangling-issuer-FK race on HandleWellKnownOAuthServerMetadata,
  symmetric to the existing ServeMCP coverage.
- Issuer-gated toolset backend on private visibility.
- Issuer-gated well-known metadata on a custom domain and on the
  toolset backend.

Renames TestServeRuntime_* -> TestServeMCP_PublicRemoteBackend_* /
TestServeMCP_PrivateRemoteBackend_*, TestServeMCP_IssuerGated_* ->
TestServeMCP_IssuerGatedRemoteBackend_*, the register smoke test to
TestAttach_OAuthRegisterRoute_MintsClientID, and fixes the truncated
_Returns suffix on the IDP-callback mismatch test. Relocates the two
issuer-gated well-known tests out of serveruntime_test.go into
wellknown_test.go alongside the existing well-known coverage.

Adds a test-internal createIssuerGatedMcpServer helper that wires up
the full /x/mcp resolution chain plus the upstream-IDP plumbing
(user_session_issuer + remote_session_issuer + DCR-registered
remote_session_client) in one call, mirroring
oauthtest.CreateIssuerGatedToolset. oauthtest.IssuerGatedToolsetOpts
grows a RouteBase field so the DCR-registered callback URL matches
the surface under test - /mcp callers keep their existing behaviour
via the default. seedIssuerGatedRemoteMCPEndpointOnDomain mirrors
the existing seedToolsetMCPEndpointOnDomain pattern for the
remote-backed case.
@bflad bflad force-pushed the bflad-age-2395-integrate-xmcp-with-user_session_issuer_id-HhdA branch from 047e388 to 5ffee94 Compare May 20, 2026 15:18
@bflad bflad enabled auto-merge May 20, 2026 15:18
@github-actions github-actions Bot added the preview Spawn a preview environment label May 20, 2026
@bflad bflad added this pull request to the merge queue May 20, 2026
@speakeasybot
Copy link
Copy Markdown
Collaborator

speakeasybot commented May 20, 2026

🚀 Preview Environment (PR #2926)

Preview URL: https://pr-2926.dev.getgram.ai

Component Status Details Updated (UTC)
✅ Database Ready Created and validated 2026-05-20 15:25:09.
✅ Images Available Container images ready 2026-05-20 15:24:26.

Gram Preview Bot

Merged via the queue into main with commit 978d13f May 20, 2026
31 checks passed
@bflad bflad deleted the bflad-age-2395-integrate-xmcp-with-user_session_issuer_id-HhdA branch May 20, 2026 15:29
@github-actions github-actions Bot locked and limited conversation to collaborators May 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

preview Spawn a preview environment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants