feat: migrate or delete every external MCP toolset from the admin page
GRW-298
Summary
Follow-up to #7612. The External MCP migration page now covers every toolset still backed by an external MCP attachment, so each one ends up migrated or deleted.
- Missed fix from #7612. The review fixes pushed just after the merge: client and upstream issuer lookups scoped to the project and its organization, a blocker for another enabled toolset on the same sign-in provider, validated pass-through header sources, the migration only wired when an encryption key is configured, metadata re-keyed by the right ids, and failed outcomes no longer reporting a rolled-back remote source.
- Fewer blockers. Servers with no sign-in provider, or whose provider has no upstream client, now migrate and skip the sign-in steps. An OAuth proxy, its stored tokens or an external OAuth server no longer block; the server migrates without that sign-in. Tools and resources beyond the external MCP ones are dropped. Headers users send are passed through. Organization collections no longer block. Candidates carry
warnings, shown as Ready with warnings. - Per-tool toolsets. Toolsets built from individually picked external MCP tools (
directdefinitions), not only the single proxy tool, are listed and migrate to a remote MCP server for the same upstream. Their repeated header declarations are merged. - Delete action. Each candidate now has an
action,migrateordelete, withdelete_reasons. A toolset is deleted when MCP is off and it has no server row, its attachment is no longer on the active deployment, it proxies more than one attachment, URL or transport, or it was already migrated in place. Deleting takes the toolset delete API's steps in one transaction: the toolset's server with its addresses, plugin entries, gateway memberships and assistant links, the toolset itself, toolset-keyed plugin entries, a plugin republish when needed, and audit entries under the staff member. It refuses a server that roots a custom domain or has lifecycle-bound risk policies, since those need clean-up steps that run outside the admin server, and a toolset still backing a server with another id. Outcomes reportactionand the newdeletedstatus. - Deleting a migrated toolset through the product.
hostedmcp.Deletetreats a server that kept the toolset's id but moved to a remote MCP backend as not the toolset's, so deleting that toolset from the dashboard, CLI or API succeeds and leaves the server untouched instead of failing with "hosted MCP identity belongs to another server". A server row pointing at a different toolset still conflicts.
Admin MCP parity: unchanged from #7612; no tool. This is a short-lived operations migration.
Motivation
A production check showed the page reaching only a small part of the toolsets still backed by external MCP: most use per-tool definitions, many reference attachments that left their deployment, and nearly every remaining row was blocked by conditions that need no handling (no sign-in provider, a stale OAuth proxy, extra platform tools, collections). The goal is an end state where every such toolset is migrated or deleted, so the page now resolves each one to one of those actions instead of blocking it.
Gutternote review steps
- Setup: the seeded demo data has no external-MCP-backed toolsets. Seed a project with a completed deployment, an
external_mcp_attachmentsrow with aproxyordirecttool definition, and an MCP-enabled toolset whose latest version references it. For a delete row, add a newer completed deployment so the attachment is no longer on the active one, or turn MCP off on a toolset with no server row. - Navigate: admin sidebar ā Platform Management ā External MCP migration (
/external-mcp-migration). - Expect: each row's Status shows Ready, Ready with warnings (amber, with what is dropped), Delete (red outline, with why) or Blocked.
- Act: Select ā To delete, then the run button, which reads "Delete N servers" (or "Migrate N servers and delete M" for a mixed selection).
- Expect: the preview lists each row as Will delete or Will migrate with its actions. Confirm, and rows show Deleted or Migrated. A migrated row comes back as Delete with "already migrated"; deleting it leaves the server working.
Also check: Ready to migrate selects no delete rows, and a delete that cannot run (for example a custom-domain root) shows Failed with the reason in the preview.
š¤ Generated with Claude Code
Summary by cubic
The admin External MCP migration page now resolves every toolset still backed by an external MCP attachment to a migrate or delete action, so each one ends up migrated or deleted instead of blocked.
- Migrates per-tool (direct) attachment definitions, not just proxy tools, and deduplicates their repeated header declarations.
- Unblocks servers without a sign-in provider or upstream client, with an OAuth proxy, external OAuth server, extra tools, or user-supplied headers ā those parts are dropped and surfaced as warnings.
- Deletes toolsets left with nothing to migrate (MCP off with no server, attachment off the active deployment, multiple upstreams, or already migrated) in one transaction covering the server, addresses, plugin entries, gateway and assistant links, and audit entries.
- Deleting a migrated toolset through the dashboard, CLI, or API now succeeds and leaves the remote MCP server untouched.
- Wires the migration only when an application encryption key is configured and scopes issuer lookups to the project and organization.
š Preview Environment (PR #7706)
Preview URL: https://pr-7706.dev.getgram.ai
| Component | Status | Details | Updated (UTC) |
|---|---|---|---|
| ā Database | Ready | Existing database reused | 2026-10-18 21:49:33. |
| ā Images | Available | Container images ready | 2026-10-18 21:51:36. |
Gram Preview Bot