SuperAPI AI Directory

migrate-mcp-between-projects

Deployment & operations

View repository

Updated 11-02 22:48

About this skill

Migrate an MCP between projects

If this connection lists call_platform_tool instead of the tools this workflow names, find each named tool with search_platform_tools, describe it with describe_platform_tool, then run it with call_platform_tool and the same arguments. Whether a tool exists for you is decided by describe_platform_tool with its exact name, not by tools/list or one page of search results: a name returned in not_found is unavailable to you, so say so rather than guess. The call itself still checks your access. When execute is also listed, a multi-step read may run in execute with tools.search, tools.describe and tools.call on platform--<tool name> paths. End each execute run at a confirmation point and ask the user before any write.

Use this workflow only through the authenticated Speakeasy AI Control Plane (AICP) Platform MCP connected as an external administrator. There is no move tool, so it follows the same guarded outcome a user would complete manually in the AICP dashboard: read the source registration, re-add the same reviewed source in an explicit target project, finish secure setup there, verify readiness, distribute to one exact target plugin, and only then optionally disable the source. The source stays enabled and untouched until the target is registered, connected, verified ready, and distributed. For a first-time addition with no source, use add-mcp-from-catalog or add-mcp-from-remote-url instead. It is not available to managed project assistants: an assistant acts only inside its own project and cannot name a second one, and the plugin inventory, access reads, forced readiness probes, and distribution this workflow depends on are external-client tools. Installing this package grants no organization or project access.

Safety rules

  • Secrets never enter chat. Never ask the user to paste credentials, tokens, secret headers, or OAuth state into chat or tool arguments, and never claim that any secret, header value, or authorization travelled from the source. Secret entry and provider sign-in happen only in server-returned dashboard and authorization URLs.
  • Show non-secret setup, provider, and authorization URLs only when a Platform MCP tool returns them; never reconstruct or invent a link.
  • Keep the source project, source MCP, target project, and target plugin explicit. Never infer any of them from prior context, a display name, or the Default project, never silently substitute another target, and never let the source and target be the same project.
  • Migration re-creates rather than moves: the target gets a new registration, a new endpoint address, and its own identity-provider attachment. State per attribute whether registration copied it, it is re-created only after the user confirms it, the user must re-enter it, or it cannot be reproduced. Nothing is dropped silently.
  • Registration-scoped reads and lifecycle mutations on the source work only for the administrator who registered it. A not-found, invalid-request, or unsupported-target refusal on the source means the caller cannot probe or disable it; report that attribute as unreadable and hand off to the AICP dashboard rather than working around it.
  • Registration in the target is private until a plugin carries it. Distribute only after fresh readiness from the same connection, and never disable the source until the target's live state has been verified and the user confirms retirement. Treat server-returned evidence as bounded and report every gap.
  • A denial or conflict returns to fresh reads and user review. Never retry a mutation automatically or with another project, MCP, or plugin, and never disable the source to make the target succeed.
  • Use send_platform_mcp_feedback only after asking for consent, and never include identifiers, URLs, credentials, payloads, headers, logs, or attachments.

Workflow

  1. Call list_projects with limit: 100 to verify that the Platform MCP is authenticated and obtain the eligible projects. If authenticated discovery is unavailable, stop and ask the user to complete or repair AICP OAuth. If truncated: true, report that project discovery is incomplete and hand off to the AICP dashboard. Present the projects and ask the user to choose one exact source project and one exact, different target project in this organization; keep each project's ID and slug paired internally.
  2. Call find_mcp for only the source project with limit: 100, following every next_cursor using cursor with the same project and no query until the list is complete. Present only entries with model: platform_managed; a dashboard-managed, hosted, tunnelled, or legacy server cannot be migrated with these tools, so report that and stop. Ask the user to choose one exact source MCP.
  3. Call get_mcp with the source project ID and that exact mcp_id. Continue only when it returns a registration ID, registration.status: registered, registration.components_complete: true, visibility: private, and effective_enabled: true. A disabled source was deliberately turned off, and a public source cannot be retired with these tools; in either case stop and report that state rather than re-creating it, because the target is always registered private and enabled regardless of the source. Retain its name, slug, visibility, source.kind, source.provider, source.reference, distributions, and registration ID internally as the source shape.
  4. Call get_mcp_access for the source and retain its stored tool names and the roles that reach it for later comparison.
  5. Call get_mcp_client_admission with the source project slug and registration ID and retain the returned mode and custom client ID metadata URLs. If it answers invalid_request, the admission setting cannot be read by this caller, most often because another administrator registered the source: mark admission as unreadable and skip every source-scoped probe and lifecycle step.
  6. Call list_plugins for the source project ID, following every next_cursor until the list is complete, then get_plugin for each plugin, matching servers[].mcp_slug against the source slug, because distributions lists only Platform-flow memberships. Record each carrying plugin's name, policy, and assignment set; these memberships are project-scoped and are not copied.
  7. Present the migration inventory before any write. Copied by registration: backend kind, catalogue entry or canonical remote URL, and display name; the target is always created private and enabled, whatever the source's state. Re-created only after the user's explicit confirmation in later steps: the client admission mode, plugin membership, and audience, each by name. Re-collected from the user: the declared non-secret configuration values, which no tool reads back from the source. Cannot be copied: secret configuration, the identity-provider attachment and every user's authorization, the endpoint address and custom domains, custom client ID metadata URLs, and access-role rules pinned to the source MCP. Ask the user to acknowledge this inventory explicitly.
  8. Re-derive the candidate from the persisted source shape and branch by source.kind:
    • For reviewed_catalogue, call inspect_mcp_candidate with provider_key set to source.provider and catalog_ref set to source.reference. Present the declared configuration fields and collect only the non-secret values from the user; stored source values are not readable and must be supplied again.
    • For user_supplied_url, call inspect_mcp_candidate with remote_url set to source.reference and no catalogue selectors, and present the canonical URL and the source name for confirmation. If the entry no longer exists or inspection reports an error, stop without registering. Compare the returned tool names with the source access read and report differences as evidence rather than failure.
  9. Present the bounded target change: the exact target project, the re-derived candidate, the non-secret configuration, and everything that must be redone. After explicit confirmation, for reviewed_catalogue call register_catalog_mcp with the exact target project slug, provider_key, catalog_ref, non_secret_config, and a fresh idempotency key; for user_supplied_url call register_remote_mcp with the exact target project slug, remote_url, the source name as display_name, and a fresh idempotency key. Registration is private and does not distribute the MCP. Read next_action: ready and start_setup continue to the readiness check; continue_dashboard_setup and secure_dashboard_setup_required return a dashboard_setup_url to present as-is in step 11. connect_sign_in means the server's OAuth client is set up automatically: continue to the readiness check, whose upstream_identity_provider_not_configured route in step 11 connects it in chat, with the returned dashboard_setup_url as the fallback. An ineligible_project refusal means the target project cannot receive it; a feature_unavailable refusal means registration is unavailable right now; a plain conflict means the idempotency key was already used with different input. In every case report the refusal and stop; do not retry against another project or with altered input.
  10. Call find_mcp for the target project and select only the entry whose registration.id equals the returned registration ID, then call get_mcp with the target project ID and that mcp_id to obtain its version, source.provider, and source.reference. If the target name differs from the source name and the user wants it kept, call update_mcp_metadata with the target project slug, registration ID, mcp_id, the source name, the immediately preceding version as expected_version, and a fresh idempotency key.
  11. Call get_mcp_readiness with the target project slug and registration ID. Registration writes no readiness, so this first read checks the MCP once and returns that provider evidence. Only when it comes back unchecked (readiness_unavailable, freshness unavailable) because that check was refused or failed, call get_mcp_readiness once with force: true; an unchecked result is not a reason to stop. A refused first check shares the forced-check limit, so if the forced call returns rate_limited, wait at least a minute before forcing again. Then route secure setup from the exact target evidence:
    • For upstream_identity_provider_not_configured, explain that AICP can attach the one authorization server discovered from the persisted source. Ask for explicit confirmation, then call attach_platform_mcp_identity_provider with confirmed: true, present its exact authorization_url as a clickable link, and wait for the user to use Connect or Authorize. If registration returned connect_sign_in and the user prefers the dashboard or attachment is refused, present the exact dashboard_setup_url from registration instead.
    • For upstream_authorization_required, call attach_platform_mcp_identity_provider again with confirmed: true to retrieve the current server-issued authorization_url and wait for Connect or Authorize.
    • For identity_chaining_configured, the organization's identity provider is configured to supply this server's access, which it does on the user's first call through the server; this is not provider evidence yet, so do not report it as ready. Call get_my_mcp_connection_status for the target MCP, give the user its connection_url, and ask them to make one call through it, then continue with step 12: once that call has obtained access, readiness reports ready with evidence identity_chaining_active. Fall back to the upstream_authorization_required route only if the user asks to connect manually or their call fails.
    • For a continue_dashboard_setup or secure_dashboard_setup_required registration result or required_header_missing evidence, present the exact dashboard_setup_url. If none was returned, call get_setup_handoff with the target project slug, registration ID, and the target's source.provider as provider_key and source.reference as catalog_ref exactly as the step-10 get_mcp returned them (for a remote URL these are the platform's direct-remote provider key and the canonical URL), never values retyped from the source project or supplied by the user, and present only its exact setup_url. The user re-enters every secret there, never in chat; never request the resulting value.
  12. After the user completes any secure handoff or first chained call, call get_mcp_readiness with force: true again from the same connection. Do not rely on stale or inferred readiness. If the state is still not ready, return to the routing in step 11 with the new evidence and repeat until it is ready or the user stops. Forced probes are limited to three per minute for one registration, so never loop them to wait out a handoff. A rate_limited refusal means that budget is spent: call get_mcp_readiness without force to read the last stored check, which is already fresh from the prior probe, and force again only after a minute has passed.
  13. When readiness is current and ready, report the server-returned evidence. Call get_mcp_client_admission with the target project slug and registration ID and present the target's current mode next to the source mode. If the source mode was readable, differs from the target's, and appears in the target's allowed_modes, explain what it admits and refuses, ask for explicit confirmation, then call set_mcp_client_admission with that exact mode and confirmed: true. A source mode absent from allowed_modes cannot be written: report it and leave the target's mode unchanged. Known clients (presets) refuses an unlisted client at authorization with no fallback, so confirm the user accepts that before selecting it. Custom client ID metadata URLs are re-added only in the AICP dashboard.
  14. Call list_plugins for the target project ID, following every next_cursor until the list is complete, present its plugins alongside the source carrying plugins by name, and ask the user which one exact target plugin should carry this MCP. Do not choose for them and do not assume the default plugin. If none fits, such as when the source plugin has no counterpart in the target project, offer to create one: agree its name, choose one idempotency key for this create, and call create_plugin for the target project ID with that key and confirmed: false. Nothing is created and no receipt is stored; the confirmation_required refusal carries the exact slug the plugin would get. Show the user that slug as the plugin's permanent install name; never work a slug out yourself. Say whether the target is the organization's default project (where a new plugin reaches every member) or not (where it reaches no one until people are assigned to it). Only after the user confirms the name and slug, call create_plugin again with the same input, the same idempotency key, and confirmed: true. If it refuses, no plugin was created and this step has not finished: on slug_taken, call list_plugins for the target project again and ask whether to use the existing plugin with that slug or a different slug, then preview and confirm a different slug only as a new create, with a fresh idempotency key used for both its calls; on idempotency_key_reused, repeat the original request exactly with its original key. Continue only once the user has chosen an existing plugin or create_plugin has returned one, then call get_plugin for that plugin. If the user wants the source audience mirrored, call list_plugin_assignments for the target project, present the complete replacement set, state that it changes who receives every MCP server in that plugin, ask for explicit confirmation, then call set_plugin_assignments with the immediately preceding assignment_version as expected_assignment_version, a fresh idempotency key, and confirmed: true.
  15. Confirm this exact distribution with the user. If more than a minute has passed since the fresh ready probe, call get_mcp_readiness with force: true again from the same connection, then call distribute_mcp_to_plugin with the target project slug, the target's mcp_id from step 10, and that exact plugin. Report not_found, mcp_not_found, ambiguous_target, not_ready, or approval_required as-is and ask again rather than retrying with a different plugin or project. On success, report message. When awaiting_manual_sync is true, the target plugin's auto sync is off, so people get the target MCP only after the target project is synced by hand: offer republish_plugin for that project, explaining that it republishes every plugin in it. After explicit confirmation, call republish_plugin with project_id set to the retained target project ID, plugin set to the exact target plugin selected in step 14, confirmed: true, and a caller-generated idempotency_key; reuse that key only to retry the same request. If the call refuses or fails, report its reason and any returned dashboard URL, keep the source enabled and the cutover incomplete, and stop this cutover attempt without polling. For an enqueued or already_current outcome, re-read get_plugin for that exact target plugin. Publication is asynchronous: continue only when publication_evidence.fresh is true. Otherwise report the pending, failed, unknown, or unavailable evidence, keep the source enabled and the cutover incomplete, and stop; resume verification on a later user request.
  16. Access-role rules pinned to the source MCP do not cover the target. Report the roles the source access read listed and use the manage-mcp-access workflow in the target project to re-author them; do not claim access parity.
  17. After distribution, call get_plugin for that plugin and get_mcp for the target again. Report the live attachment, publication_state, and readiness separately. Do not claim that users have the MCP unless the returned live state supports that conclusion. The source is still enabled at this point; do not claim the cutover is complete.
  18. Retiring the source is a separate decision. Before any source retirement, re-read get_plugin for the exact target plugin and require publication_evidence.fresh to be true, regardless of whether distribution reported awaiting_manual_sync. If freshness is false, unknown, or unavailable, keep the source enabled and the cutover incomplete; do not call disable_mcp while delivery cannot be confirmed. Once freshness is confirmed and the user explicitly asks to retire the source, call get_mcp for the source again and present its live state. Explain that disabling makes its endpoint unreachable for users and clears custom-domain roots while keeping the registration, plugins, and roles for rollback. After explicit confirmation, call disable_mcp with the source project slug, registration ID, mcp_id, the immediately preceding expected_version, and a fresh idempotency key. A feature_unavailable answer with reason unsupported_lifecycle_target on a Platform-managed source means another administrator registered it; do not repeat its message verbatim, and tell the user to retire it in the AICP dashboard, where its registration keeps its project slot until removed. A conflict means the source changed since the last read, for example it became public or was already disabled: call get_mcp again, present the live state, and renew confirmation; a public source is retired only in the AICP dashboard. Never delete anything.
  19. After disabling, call get_mcp for the source and for the target again and report both live states. Rollback stays available: enable_mcp on the source with its fresh expected_version and a fresh idempotency key restores it, and remove_mcp_from_plugin with the target project slug, the target's mcp_id, and plugin detaches the target membership this workflow created.

Provider authorization, secret re-entry, and dashboard-only attributes are the expected out-of-agent stops. Source and target selection, the migration inventory acknowledgement, non-secret configuration, and the registration, admission, distribution, and source retirement confirmations remain explicit conversation checkpoints. All selectors and mutation-control fields remain agent-operational.