Skip to content

feat: serve toolset-backed mcp_servers under the wrapper's configuration - #5918

Merged
daviddanialy merged 3 commits into
mainfrom
daviddanialy/aim-21-wrapper-governed-hosted-serving
Sep 1, 2026
Merged

feat: serve toolset-backed mcp_servers under the wrapper's configuration#5918
daviddanialy merged 3 commits into
mainfrom
daviddanialy/aim-21-wrapper-governed-hosted-serving

Conversation

@daviddanialy

@daviddanialy daviddanialy commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

AIM-21

Summary

  • When a request resolves through an mcp_endpoints row to a toolset-backed mcp_servers row, hosting configuration now comes from the wrapper: a new hostedServing resolved-config value carries visibility, the user-session issuer, the RBAC resource id, and the tool-variation-group override into the toolset serving path, replacing the previous boolean parameters. toolsets.mcp_is_public and toolsets.user_session_issuer_id are no longer consulted when a wrapper is present; tools, resources, prompts, environment, tool selection mode, and external OAuth still come from the toolset.
  • RBAC: mcp:connect checks key on mcp_servers.id for wrapper-governed requests — connection-level, per-tool (tools/call and tools/list), and the consent tool picker. Meta-member and internal agent-workflow paths deliberately keep toolset-keyed checks.
  • Audience: wrapper-backed endpoints bind the JWT audience to the issuer URN with the route base taken from the inbound surface (the hardcoded x/mcp default is removed). Bearer validation accepts the legacy toolset-URN audience for toolset-backed wrappers only, on audience mismatch only, counted by mcp.legacy_audience_accepted{user_session_issuer.id}; stored consent tool-selection resources get the same legacy acceptance so restricted sessions are not forced into reauth.
  • Endpoint authority: mcpendpoints.BySlugAndCustomDomain distinguishes an address miss from a resolvable-but-unavailable address (disabled visibility, dangling backend) via a new ErrEndpointUnavailable. Only a true miss falls through to the legacy toolset lookup; a disabled wrapper can no longer resurrect its slug through the toolset, on any surface.
  • Fallback observability: new counter mcp.toolset_slug_fallback{gram.mcp.entry_point} increments at all seven legacy resolve points (ServePublic, GET/DELETE proxy, both well-known handlers, the OAuth family via LoadResolvedMcpEndpointBySlug, the install page resolver, cached-challenge resume), only when the legacy lookup resolves a live toolset so scanner probes cannot keep it nonzero. The hosted serving path also stamps gram.mcp_server.id on the request span when a wrapper is present.

No production behavior changes on deploy: no toolset-backed mcp_servers rows exist yet, so the wrapper-governed paths are dormant until the backfill populates them.

Motivation

Hosted (toolset-backed) MCP servers are migrating under mcp_servers/mcp_endpoints. This PR establishes the runtime contract that migration depends on: the wrapper governs hosting configuration, the audience and RBAC conventions converge on the server id with a counted compatibility window for outstanding legacy bearers, and the two counters introduced here are the merge gate for later removing the toolsets.mcp_slug fallback entirely. It also closes the resolver hole behind INC-420, where a disabled server could keep serving through the legacy fallback.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TCzhsvUYz5VMieXYaQUC2Z


Summary by cubic

When a request resolves through an mcp_endpoints row to a toolset-backed mcp_servers row, hosting configuration now comes from the wrapper: visibility, issuer gating, the RBAC resource id for mcp:connect, and the variation-group override. The toolset's mcp_is_public and user_session_issuer_id columns are no longer consulted on that path, and the JWT audience now binds to the issuer URN with the route base taken from the inbound surface instead of the hardcoded x/mcp default.

Migration compatibility

  • Bearer validation on toolset-backed wrappers accepts the legacy toolset-URN audience on mismatch, counted by mcp.legacy_audience_accepted, so pre-backfill sessions aren't forced into reauth.
  • Stored consent tool-selection resources get the same legacy acceptance.
  • All seven legacy toolsets.mcp_slug resolve points increment mcp.toolset_slug_fallback by entry point, but only when the lookup resolves a live toolset.

Endpoint authority

  • mcpendpoints.BySlugAndCustomDomain now returns ErrEndpointUnavailable for a resolvable-but-unavailable address instead of a plain not-found.
  • A disabled or dangling backend is a terminal not-found on every surface, including the install page, so it no longer falls back and resurrects its slug through the toolset.
  • Wrapper-governed requests stamp gram.mcp_server.id on the request span.

No production behavior changes on deploy: no toolset-backed mcp_servers rows exist yet, so the wrapper-governed paths stay dormant until the backfill populates them.

Written for commit d1c7863. Summary will update on new commits.

Review in cubic

@daviddanialy
daviddanialy requested a review from a team as a code owner August 31, 2026 22:07
@daviddanialy daviddanialy added enhancement New feature or request go Pull requests that update go code labels Aug 31, 2026
@linear-code

linear-code Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

AIM-21

@changeset-bot

changeset-bot Bot commented Aug 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d1c7863

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

@cubic-dev-ai

cubic-dev-ai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Running ultrareview automatically — This cross-cutting MCP hosting migration changes RBAC keying, audience validation, endpoint resolution fallback, and serving configuration across 25 files — authorization bypass or a broken fallback here would affect every hosted MCP server once the backfill lands.. I'll post findings when complete.

@blacksmith-sh

This comment has been minimized.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ultrareview completed in 7m 47s

All reported issues were addressed across 25 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread server/internal/mcp/serveendpoint.go
Comment thread server/internal/mcp/rpc_tools_call.go
Comment thread server/internal/mcp/authnchallenge.go
Comment thread server/internal/mcpmetadata/impl.go Outdated
Comment thread server/internal/mcp/mcpmetrics/legacyfallback.go
Comment thread server/internal/mcp/wrapper_governance_test.go Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 15 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread server/internal/mcpmetadata/serve_install_page_test.go
@daviddanialy

Copy link
Copy Markdown
Contributor Author

End-to-end verification (this PR's changes)

Context: all three Phase-1 branches (#5917, #5918, #5919) were merged into a scratch tree (zero conflicts), the full server suite ran on the merged tree — 14,334 tests, 1 failure: a pre-existing skills-subsystem clock flake, passes in isolation, unrelated to these PRs — and a local stack was booted from the merged tree with simulated backfill rows (a toolset-backed mcp_servers wrapper + mcp_endpoints row) to exercise the dormant paths over real HTTP.

Scenarios exercising this PR:

Scenario Result Evidence
Legacy fallback intact (unwrapped public toolset) PASS initialize/tools/list on the legacy slug → 200
Wrapper visibility governs serving PASS (strong form) toolset flags private, wrapper public → anonymous 200 through the endpoint slug — wrapper wins
Disabled endpoint backend: serving PASS POST → 404 mcp endpoint not found, not 500
Disabled endpoint backend: install page PASS 404 + branded "Server Not Found" page (this PR's fix, verified live)
Unknown visibility value PASS terminally 404; restore to public → 200 again
Private wrapper, anonymous client PASS 401 challenge

One expected nuance, not a bug: with the wrapper disabled but the toolset's own flags public, the toolset's legacy mcp_slug address (a different slug than the test endpoint) still serves — wrapper governance engages when a request resolves through an endpoint row. Disable-terminality on the legacy address arrives with the backfill's same-slug endpoints (AIS-636) plus the mirror (AIS-635), matching the milestone ordering.

No panics or unexpected errors in server logs. All test rows and flag changes were reverted after the run (verified by query).

🤖 Generated with Claude Code

@bflad bflad left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good to me 🚀

@daviddanialy
daviddanialy added this pull request to the merge queue Sep 1, 2026
Merged via the queue into main with commit 4988249 Sep 1, 2026
47 of 48 checks passed
@daviddanialy
daviddanialy deleted the daviddanialy/aim-21-wrapper-governed-hosted-serving branch September 1, 2026 17:05
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 1, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

enhancement New feature or request go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants