feat: mirror hosted MCP configuration between toolsets and mcp_servers - #5989
Conversation
…et-backed wrappers by endpoint Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TCzhsvUYz5VMieXYaQUC2Z
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TCzhsvUYz5VMieXYaQUC2Z
…osted-identity-by-endpoint
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TCzhsvUYz5VMieXYaQUC2Z
…irror-hosted-mcp-config
…pper-per-toolset' into daviddanialy/aim-19-mirror-hosted-mcp-config
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 3052ac5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
|
Running ultrareview automatically — This cross-cutting change adds transactional dual-writes across toolsets, MCP servers, endpoints, visibility, OAuth, and deletion, with new locking and uniqueness invariants whose subtle failures could cause inconsistent serving, access-control exposure, or data loss.. I'll post findings when complete. |
|
|
||||||||||||||||
|
|
||||||||||||||||
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…daviddanialy/aim-19-mirror-hosted-mcp-config
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Ultrareview completed in 16m 8s
Review completed against the latest diff
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Linked issue analysis
Linked issue: AIM-19: feat: mirror hosted MCP configuration between toolsets and mcp_servers
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Toolset lifecycle and hosting-field writes project to the toolset's MCP wrapper and primary endpoint. | Toolset create, update, delete, clone, issuer, and variations-group paths invoke mirror logic; the new toolset mirror handles wrapper and endpoint projection. |
| ✅ | Wrapper and endpoint writes project visibility, authentication, variation-group, slug, and custom-domain state back onto the toolset. | MCP server and endpoint services lock backing toolsets and invoke reverse projection for update, create, update, and delete operations. |
| ✅ | Both projection directions run transactionally with the toolset locked before dependent resources. | The PR adds explicit toolset locking and documents and implements the toolset → custom domain → server → slug lock order in both directions. |
| ✅ | Visibility round-trips between the four toolset flag states and the three MCP server visibility states, clearing publicness when disabled. | The shared hosted MCP package defines the visibility mapping, and mirror tests cover the round-trip behavior. |
| ✅ | Deleting a toolset or its hosted wrapper tombstones endpoints child-first and keeps the toolset/wrapper semantics consistent. | Toolset deletion and mcpServers.delete use dedicated deletion logic, emit endpoint deletion before server deletion, and clear hosting columns without deleting the toolset. |
| ✅ | Each toolset has at most one live MCP server wrapper. | The schema and migration add a partial unique index over non-deleted wrappers, with conflict handling and tests for duplicate wrappers. |
| ✅ | Assistant attachment enables hosted MCP through the mirror rather than directly updating toolset flags. | The raw EnableMCPForToolsets query was removed, attachment now uses hostedmcp.EnableToolsetMCP, and a test verifies disabled wrappers are lifted. |
| ✅ | Demo seed data provisions matching wrappers and endpoints and verifies the hosting invariant. | The demo seed was updated with hosted slugs, wrappers, endpoints, and a postflight invariant check. |
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
Every mirror address write takes the slug lock and unified availability check; custom domains lock org-scoped; EnableToolsetMCP reconciles a stale wrapper; setters re-check the authorized toolset id after locking and reconcile cleared domains; issuer changes resync the derived remote session issuer; delete tombstones children first and clears toolset hosting before the issuer cascade. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 14 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
…p front Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…suer Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
End-to-end verificationLive stack built from this PR's head ( Forward (toolset writes → wrapper/endpoint): create, every Backward (server/endpoint writes → toolset): visibility sync incl. disabled clearing Assistant: attaching a disabled toolset lifts the wrapper to private and the endpoint serves. Dashboard (Playwright, real dialogs): Disable/Enable/Public/Private on the legacy toolset page and Disable/Private on the unified server page each produced paired Concurrency (real): 20 parallel slug renames vs 20 parallel endpoint re-keys on one server → 40/40 200, zero 5xx, zero deadlocks, final Audit: forward writes emit FAIL (pre-existing, not a regression): toolset rename does not update NOT RUN: first-toolset auto-enable (needs a fresh org), custom-domain bind (activation is reconciler-only), unified page → Public (offered only for tunneled sources by design). Two unrelated API gaps noticed and ticketed at low priority: 🤖 Generated with Claude Code |
AIM-19
Stacked on #5951 (the one-wrapper-per-toolset index); merge that first.
Summary
mcp_serverswrapper and primarymcp_endpointsrow; everymcpServers/mcpEndpointswrite on a toolset-backed server projects back onto the toolset's hosting columns. Both directions run in the caller's transaction with the toolset row locked first (hostedmcp.LockToolsets), order toolset → custom domain → server → slug advisory lock.mcp_enabled=falseisdisabled; projecting back fromdisabledclearsmcp_is_public). Endpoint slug is the toolset'smcp_slugverbatim in its scope; alias twins follow renames.mcpServers.updaterejects changing a hosted server's backing toolset or backend kind (CodeInvalid);mcpServers.deletetombstones endpoints then wrapper and clears the toolset's hosting columns without deleting the toolset. Second wrapper for a toolset →CodeConflict(index from mig: enforce one live mcp_servers wrapper per toolset (temporary, dropped by AIM-172) #5951).hostedmcp.EnableToolsetMCP) instead of a raw flag write. Backward visibility sync applies the OAuth-detach rule; wrapper renames are durable (name syncs only when the toolset name changed).Motivation
While toolsets and
mcp_serverscoexist, the two representations of a hosted server must stay identical or the wrapper-governed runtime (#5918) and the dashboard's toolset toggles disagree about what is serving. This is the dual-write half of the migration; the backfill that wraps existing servers follows and requires this to be deployed first.Deviations from spec, deliberately: default-plugin attachment stays toolset-keyed until AIS-638 (the server-keyed plugin query lacks env-config support); a toolset bound to a soft-deleted custom domain projects no endpoint.
🤖 Generated with Claude Code