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.
Add selected supported remote servers from the agent client you are running in to a Speakeasy AI Control Plane (AICP) project without editing local client configuration, then optionally restrict them to the organization's Tailscale private network. Installation grants no access; live authentication, entitlement, membership and administrator checks remain authoritative. All management mutations use authenticated Platform MCP tools, never direct backend APIs.
1. Verify access and request discovery permission
Before any local discovery (including asking for manual inventory), successfully call list_projects with limit: 100 through your OWN Speakeasy connection in this client session. Dashboard state, install intent, another client's authentication, or a prior session's result is not proof of access. It accepts only limit (capped at 100), not cursor or search. If unavailable or denied, stop and offer the normal Speakeasy sign-in/setup flow. If truncated: true, project discovery is incomplete: stop and hand off project selection to the AICP dashboard rather than inventing pagination or guessing a destination.
If the user says they want the migrated servers kept on their Tailscale private network, call get_network_ingress once now and tell them what it reports (step 8 explains how to read it), so any dashboard setup can start while the import runs. A private network that is not ready never blocks the import; it only decides whether step 8 can finish in this session.
Name the client you are running in, and discover only that client's own MCP servers, never another client's. Use a listing command only when you know it exists:
| Client | Listing command | Known effects |
|---|---|---|
| Claude Code | claude mcp list |
health-checks approved/enabled servers, as described below |
| Any other client (for example Codex, Cursor, Gemini CLI or VS Code) | only a listing subcommand documented by that client's own --help |
unknown until checked: unless the client's help states that listing never starts or connects to servers, describe and treat it as having the same effects as claude mcp list |
Never invent a subcommand or flag. If the client has no documented listing command, or you cannot tell which client you are in, use the user-sanitized manual inventory. Never open a client's MCP configuration files yourself: they commonly hold headers, tokens and environment values.
Before running claude mcp list or another client's listing command locally, obtain explicit permission. Require explicit informed consent for the following effects, not merely permission to view a list. Explain that claude mcp list health-checks approved/enabled servers, can launch stdio processes and contact local/private-network endpoints BEFORE filtering out unsupported entries. Those processes may write files or contact services; do not promise zero process side effects or passive/read-only discovery. Approval of a server in the client is not consent to run this discovery now. Explain the scope: current CLI user, working directory and configuration scope, not every client, account or workspace. Connection status does not establish remote importability.
Offer a user-sanitized manual inventory instead (non-secret alias, safe endpoint, transport only), without running discovery, if the user declines those effects or local execution is unavailable. Never demand raw output. Do not inspect credential files, expand environment values, or forward raw output to any tool, service or report. Do not invent a no-connect flag. Never edit local configuration, approval settings or credentials to enable discovery. The no-edit rule constrains the agent; it cannot guarantee that the CLI or launched servers have no side effects.
If discovery succeeds but returns no entries, report that no servers were found in the inspected scope. Offer a user-sanitized manual inventory or the normal catalogue path (add-mcp-from-catalog) as explicit next choices; do not switch workflows without the user's choice. Do not claim import completion for an empty inventory.
2. Sanitize and classify locally
Retain only non-secret alias, exact safe endpoint, declared transport and provenance needed for selection. Never copy local credentials, including headers, tokens, passwords, environment secrets or OAuth state. Do not request secrets in chat.
Sort every entry into exactly one group:
- Candidates: public HTTPS Streamable HTTP endpoints. Only these can be added.
- Local servers, left unchanged: stdio commands and localhost/loopback, private-network or link-local endpoints. They cannot become AICP remote MCPs through this workflow. Report each one to the user by alias and transport only; for stdio, say the command is not shown, because commands, arguments and environment can carry secrets. Do not migrate, wrap, tunnel, disable or remove them, and do not offer to. They stay exactly as configured in the client.
- Blocked: unsupported transports (including legacy SSE), fragments, embedded credentials, credential-like query parameters and uncertain URLs.
Safe non-secret endpoint query parameters can be meaningful; preserve them. Never manufacture a safe URL by silently stripping credentials or changing its path. Block the entire uncertain item and ask for secure/manual resolution; do not echo sensitive URL components. Server validation remains authoritative, including network safety checks.
Exclude the connected Speakeasy management endpoint itself to prevent recursive import. Establish its identity from trusted connection endpoint metadata or other exact endpoint evidence, not display name alone. If that evidence is unavailable or ambiguous, flag the possible self-reference for manual resolution rather than importing it.
3. Confirm selection and check live inventory
Present sanitized candidates, local servers left unchanged, and blocked items. Confirm candidate selection and destination from the eligible projects returned above; keep that project's ID and slug paired. Never infer the destination from a local alias or the Default project.
Use find_mcp with the selected project_id and limit: 100, no query or readiness filter. Follow every next_cursor using cursor with the same project until exhausted. Never combine query and cursor; a name search is not an exhaustive inventory. If listing fails or pagination cannot finish, do not conclude a candidate is missing.
Use get_mcp with project_id and returned mcp_id to inspect possible matches. Compare exact upstream URL and server-issued source/registration evidence. Deduplicate aliases only by proven identity, not hostname or display name: different paths or meaningful query values may represent different servers. Preserve the alias-to-item mapping for the final report. If identity cannot be proven, block for manual resolution rather than guessing or creating a duplicate. An identity match alone is not an already-present success: apply the model-specific completion evidence in step 6 before classifying it. A matching pending or incomplete registration must not be reported as already present or complete, and must not trigger a duplicate registration.
4. Inspect missing candidates and confirm the exact batch
Prefer a suitable reviewed catalogue entry, even when its endpoint differs from the local server, but never silently substitute based on a name. Keep the original sanitized alias/endpoint and the effective confirmed target as separate identities.
- Search exact endpoint identity FIRST: call
search_mcp_catalogwith the safe full endpoint asquery. This is text search, not an exact URL lookup: its only inputs are optionalquery,provider_keyandcursor; there is no URL lookup flag orremote_urlsearch input. Follownext_cursorwith the samequeryandprovider_key(unlikefind_mcppagination). Results containprovider_key,catalog_ref, name, description, version, tool count and setup intent, not guaranteed endpoint evidence. Inspect plausible results withinspect_mcp_candidateusing the returnedprovider_keyandcatalog_ref, withoutremote_url. Claim exact endpoint identity only if returned evidence proves it; an absentcanonical_urlis unknown, not a match. - If no suitable exact match is established, search local non-secret alias/provider/name SECONDARY using
query. Do not invent aprovider_keyfrom a product name: it is a server-issued source filter. Deduplicate results by returnedprovider_keypluscatalog_ref. A name hit is only a proposed alternative, never proof of equivalence. A URL search miss does not rule out a reviewed alternative. - Inspect plausible reviewed alternatives using those exact returned identities. Present candidate name, description, version, source/reference, transport, tool names/count, declared configuration and setup needs. Compare the original endpoint with the candidate's returned endpoint where available; explain region, product and tools differences, and explicitly mark unknown differences. Do not infer regional or functional equivalence from a shared name. If ambiguous, present the alternatives and ask for one exact candidate; do not choose automatically.
- Catalogue substitutions require separate confirmation of the exact candidate, differences/unknowns, declared non-secret configuration and destination project. Only after explicit acceptance set the effective target to the returned
provider_key/catalog_refplus confirmed configuration and any returned endpoint. Recheck existing registrations after substitution: repeat the selected project's completefind_mcpinventory and inspect matches withget_mcp, comparing server-issued source/registration evidence for this confirmed catalogue target, not only the original URL. Deduplicate confirmed catalogue targets across aliases too, only when configuration identity is proven. Two configurations of one catalogue reference are not the same target. Inventory does not expose selected configuration values or a configuration fingerprint; source/reference alone cannot prove configuration equivalence. For any registration without server-backed proof of its exact effective configuration, keep configuration identity unverified and request manual resolution; do not claim already present or create a duplicate. If already present, apply step 6's completion checks and do not register again; pending/incomplete or uncertain identity blocks duplicate creation. - If the user declines, no suitable match exists, or catalogue search is unavailable, offer the original safe direct remote path; disclose search failure rather than claiming no match. Unresolved ambiguity must not trigger a catalogue write: ask the user to choose or explicitly continue direct remote. For a direct fallback, call
inspect_mcp_candidatewith the originalremote_url, withoutprovider_keyorcatalog_ref. Present itscanonical_url,transport,tool_names/tool_count,trust,authentication,oauth_discovery,automatic_client_registration,requires_dashboard_setup,setup_categoryandactionswhere returned. After declining a catalogue candidate, require explicit confirmation of the inspected direct target, destination project and exact direct batch beforeregister_remote_mcp; declining the candidate is not consent to the fallback. Never manufacture a direct URL from a catalogue name. Unsupported, denied or inconclusive candidates remain blocked pending returned guidance.
Distinguish observed tools from missing evidence; an empty tool list or authentication requirement is not proof of readiness. Obtain explicit confirmation of the exact inspected batch and project before any write: show each effective target, original alias/endpoint, chosen catalogue or direct path, evidence and outstanding setup needs, plus already-present and blocked items. If inspection changes the endpoint, disclose it and reconfirm. Changed project, candidate or configuration requires fresh inspection as applicable and renewed confirmation. Use only the catalogue inspection/registration portion here, not the entire add-mcp-from-catalog workflow: import does not require readiness or plugin distribution.
5. Add confirmed supported items independently
For each confirmed missing catalogue target, call register_catalog_mcp with project_slug, the exact returned provider_key and catalog_ref, only declared non_secret_config, and a caller-generated idempotency_key. Never create a custom direct-remote entry for a confirmed catalogue replacement. Correlate the exact submitted confirmed configuration, project and catalogue identity with the returned registration_id in the non-secret operation receipt; retain this mapping for final verification. This correlates a request with a returned registration ID only; it does not prove creation or persisted configuration. register_catalog_mcp can reuse an existing registration for the same source/reference with different configuration unchanged. Neither a new receipt nor replayed: false proves that the submitted configuration took effect.
For each confirmed missing direct remote item, call register_remote_mcp with project_slug, the confirmed remote_url, optional display_name, and a caller-generated idempotency_key. Keep one key per logical operation. Preserve all logical-operation inputs and the same idempotency key on retries, including after timeouts or uncertain write outcomes. Do not generate a new key merely because the result was lost. A changed operation requires renewed confirmation and a fresh key.
Apply returned repair/retry guidance and rate limits; do not retry permanent denial unchanged or bypass validation. Continue independent items after failure. Retain non-secret receipts, registration_id, canonical_url, next_action and any dashboard_setup_url for verification and handoff. A successful response alone is not completion.
6. Verify every selected item live
Re-read the selected project's complete inventory with find_mcp pagination and get_mcp for matching entries. Verify all selected items, including already-present entries and uncertain write outcomes, against the effective confirmed target identity and project. For catalogue replacements, match fresh inventory registration.id to the receipt's returned registration_id where available, and verify the same project and returned source.provider/source.reference against the accepted provider_key/catalog_ref, plus any returned endpoint. Receipt-ID correlation alone is not persisted configuration proof. Require server-backed evidence of the registration's exact effective confirmed configuration before reporting added, already present or complete. register_catalog_mcp, find_mcp and get_mcp do not expose persisted configuration values or a persisted configuration fingerprint: do not invent fields or claim current configuration was read back. With this contract, configuration equivalence remains unverified and requires manual resolution, not automatic reuse. This applies even when the returned ID matches the receipt and live status is registered with complete components. A concurrent registration after preflight, an unknown existing registration or an uncertain write outcome must not be treated as newly created or correctly configured from the receipt; keep it unverified, do not create a duplicate, and offer manual dashboard resolution.
Distinguish a configless candidate from an empty submitted non_secret_config: omitted values can use declared defaults, and a field may be optional or secret. Inspection's absent/empty configuration only describes the current candidate, not persisted settings of a reused registration; it is not a configless-success exemption. The current inspection does not return a catalogue canonical_url, and inventory does not bind persisted configuration to that inspected candidate version. If exact effective configuration cannot be proven server-side, even an apparently configless candidate remains unverified/manual resolution; never claim success from an empty request or absence of declared fields. In every case, do not verify against the original URL when the confirmed replacement differs. Preserve the original alias-to-confirmed-target mapping in the report. Do not use cached preflight results as final evidence. Reconcile uncertain writes before retrying with their original inputs/key; if still unverified, report uncertainty rather than success.
For model: platform_managed, require a returned registration ID, registration.status: registered and registration.components_complete: true in addition to the exact project and effective target identity match (endpoint for direct remote; confirmed source identity and server-backed exact effective configuration proof for catalogue; receipt-ID correlation is insufficient) before reporting added, already present or complete. A missing registration, pending status or incomplete components means blocked/unverified; use returned repair guidance or a dashboard handoff, not duplicate creation. For an exact matching remote entry with model: dashboard_managed and no registration, report already present (dashboard-managed), not newly registered by this workflow. Do not require or invent a registration record for dashboard-managed entries; their readiness.state: unsupported is not evidence of a failed import or of working authentication. Other models or inconclusive identity/evidence remain blocked for manual resolution.
Report added / already present / blocked / failed per item, with alias mappings and evidence or a reason. List local servers left unchanged separately so the user knows they were seen and deliberately not touched. Every selected supported server must be confirmed present in the selected project's live inventory to claim completion. Zero selections is not success. If any item remains blocked, failed or unverified, report partial completion and next steps, not unqualified success.
7. Keep authentication separate
Report registration and authentication/readiness separately: “added to the project” does not mean connected, authorized, working or distributed. Offer exact server-returned Speakeasy setup/authorization links as clickable links, including dashboard_setup_url; never reconstruct or invent them. If no link is returned, say so and offer a manual dashboard handoff.
Skip provider attachment for anonymous servers. Offer attachment only when inspection reports an authentication requirement and advertises a supported authorization server through its authentication/OAuth discovery evidence, and a registration ID is available. For direct remote inspection, require authentication: authentication_required and oauth_discovery: available_dcr or oauth_discovery: available_cimd before offering attachment; both are automatic client registration paths (automatic_client_registration names which), so describe them as automatic sign-in setup rather than manual setup. available alone or incomplete does not establish support for this automatic-registration flow. These are prerequisites, not a guarantee: the attachment tool still validates the supported provider and may return repair guidance. Authentication required with no supported provider evidence means a secure/manual setup handoff, not a speculative attachment call. Retain separate explicit consent for provider attachment. Only after those evidence checks and that consent, attach_platform_mcp_identity_provider takes project_slug, returned registration_id and confirmed: true; present its exact returned provider_url and authorization_url. Secret entry and provider sign-in belong in that secure browser flow, never in chat or tool arguments. Do not force readiness, provider attachment or distribution to finish import. Leave local config unchanged; do not migrate credentials or remove local entries.
8. Optionally restrict migrated servers to the Tailscale private network
Offer this once, after step 6, or run it when the user asked for private access in step 1. It is optional: declining leaves the import complete. Explain what it does before asking: private access limits who can reach each server's AICP endpoint to devices on the organization's tailnet. It does not move or hide the upstream MCP server, which stays wherever its provider hosts it. It never changes local client configuration.
- Eligible servers are only those this run proved present in step 6 (added or already present) with an exact MCP ID. Exclude anything blocked, failed, pending, incomplete or unverified, and any entry whose
get_mcpresult hasbackend_kind: unproxied: those support onlypublic_only. - Call
get_network_ingress. It requires organization administration; if it is denied, stop this step and say an organization administrator must run it. Ifready_for_private_accessis false, report itsnext_actionin plain words. Forconfigure_ingress_in_dashboard,add_credentials_in_dashboard,enable_ingress_in_dashboard, orrepair_ingress_in_dashboard, present its exactsetup_urlas a clickable link. Forwait_for_ingress, wait and callget_network_ingressagain; no dashboard step is needed.request_private_networkingmeans private networking is not switched on for the organization yet and must be requested from Speakeasy.contact_support_for_unsupported_providermeans the provider is unsupported by this server and needs Speakeasy support, not dashboard setup or repair. For these two support actions, do not present the dashboard setup URL. Tailscale OAuth client credentials are entered only in that dashboard, never in chat or tool arguments. Do not attempt any network change while it is not ready. After the user reports finishing setup, callget_network_ingressagain rather than assuming it is ready.ready_for_private_accessis a precondition, not proof that any device can connect. - For each eligible server, call
get_mcp_network_trafficwith the project ID,target_kind: mcpand the exact MCP ID astarget_id, andwindow: "7d". Report its observed public and private request counts and last-seen times before proposing any transition. Counts cover only observed requests while telemetry was enabled: zero does not prove a route is unused or that every client has migrated. If traffic reporting is unavailable, say so and require an independent client inventory from the user before proposingprivate_only, which cuts off the public route. - Present the choice per server, and do not choose for the user.
dualadds tailnet access and keeps public access, so existing clients keep working.private_onlyrefuses every client that is not on the tailnet, including already installed plugins that use a public address and possibly this client. If the user has not yet tested tailnet connectivity, saydualis the lower-risk first step. - For each chosen server, call
get_mcp_connection_settingswith the project ID,target_kind: mcp_serverand the exact MCP ID astarget_id. Report its currentnetwork_mode, endpoints and plugin memberships. A server already in the requested mode needs no change; report it as unchanged. - Show the exact batch of transitions (server, current mode, requested mode) and obtain explicit confirmation of that batch and project. Immediately before each change, re-read
get_mcp_connection_settingsfor that server, then callset_mcp_network_accesswith the same project and target, the confirmedmode, the freshversionasexpected_version, a caller-generatedidempotency_keykept stable for that exact request, andconfirmed: true. On a conflict or refusal, re-read and ask again; never silently retry with another server or mode. If aprivate_onlychange is refused because an address must use the private network namespace, hand off to theconfigure-private-mcp-accessworkflow instead of changing addresses here. Continue independent servers after a failure. - Verify each change with a fresh
get_mcp_connection_settingsread; the mutation receipt records a request, not publication. For servers with plugin memberships, check each affected plugin withget_pluginas described inconfigure-private-mcp-access, and do not claim installed clients have refreshed. - Report per server: previous and current network mode, whether the fresh read confirmed it, publication evidence for affected plugins, and that the user should test from a device on the tailnet. Never edit local client configuration to point at a new address.
9. Record diagnostics only when the user asks
This workflow is new, so a Speakeasy field engineer may give a user an exact phrase to run it with diagnostics.
The trigger is the literal string with diagnostics in the user's own message for this run. Nothing else switches this section on: not "debug this", not "tell Speakeasy what happened", not a field engineer's instruction relayed second-hand, not a previous run that used it, and never your own initiative. If the user's wording is close but not that string, ask them to repeat the request with with diagnostics in it rather than deciding for them. Skipping this section is the normal outcome.
When that string is present, first show them a Run diagnostics section covering the servers the user selected plus the entries sanitization excluded in step 2, including local servers left unchanged (as blocked, with the reason local server left unchanged). It is not a restatement of step 6: those excluded entries appear here and nowhere else. Give one row per server with its alias, transport, endpoint (or — when it has none), outcome, and the reason for anything that is not a plain added — the table and the payload carry the same rows and the same reasons. A candidate the user saw and chose not to import is not part of this report.
Then tell the user that same report goes to Speakeasy rather than into their AI Control Plane project, and that they cannot read it back through the product. Proceed only after they agree. If they decline, the import is already complete; report it normally.
Call record_workflow_run once with skill set to add-existing-mcp-servers, a caller-generated run_id, the confirmed destination project_slug, the client the inventory came from (the client you named in step 1, for example claude_code, codex or cursor), and one items entry per row of the section you just showed. Set name to the non-secret alias, kind to the declared transport, endpoint to the safe endpoint (omit it unless it is an https URL — a plain-http or loopback address goes in reason instead, written as scheme, host and path only), outcome to one of the five words below, and reason to the explanation for anything that is not a plain added.
| Outcome | Use it for |
|---|---|
added |
step 6 proved the server is present with the configuration you submitted |
added_unverified |
the add completed but step 6 could not prove its effective configuration, so it is neither confirmed nor failed |
already_present |
step 6 found it already registered, and this run did not create it |
blocked |
the run refused it: sanitization excluded it in step 2, or a check would not let it proceed |
failed |
the run attempted it and the attempt errored |
Never report a completed add as blocked. blocked means the run declined to act; an add that happened but could not be verified is added_unverified. A run that considered nothing still reports: send an empty items list rather than skipping the call.
Never send raw discovery output, credentials, headers, environment values, or local commands.
An address written into reason carries scheme, host and path only. Drop the query string and the fragment before writing it, every time, including for an entry step 2 excluded because its URL carried a credential — that entry is exactly the one whose query string must stay on this machine. reason is free text and reaches Speakeasy as written, so it is the one field where nothing is stripped for you.
Diagnostics never gate the import. If the tool reports that diagnostics are not switched on, or returns recorded: false, tell the user the report was not recorded and that their servers were still imported; do not retry, and do not treat it as an import failure.