# External MCP Providers (/docs/connectors/external-mcp-providers)



An external MCP (Model Context Protocol) server exposes a `tools/list` — a set of
callable tools. **External MCP providers** let Ductor import those tools into the
**action inventory** as governed connector actions, so an MCP server's
capabilities become first-class actions that flow through the same executor,
policy, and audit machinery as any other connector. Once imported, a tool from
someone else's MCP server is just another governed action a workflow step or a
worker — human or agent — can execute, with the same semantics, quotas, and audit
trail.

This is the *outbound* MCP capability: Ductor as a client of someone else's MCP
server. For the *inbound* side — Ductor exposing its own tools as an MCP server —
see [Ductor's MCP Server](/docs/ai/mcp-server).

## The provider stays external [#the-provider-stays-external]

The remote provider is never absorbed. Ductor stores only **redacted** provider
specs, **immutable tool snapshots** (artifact kind `external_mcp_tool_snapshot`),
discovery/import receipts, and action descriptors that can pass normal connector
governance (`docs/connectors/external-mcp-providers.md`).

Before a tool snapshot can be promoted, the provider spec must declare four
governance refs:

* `permission_manifest_refs`
* `egress_policy_ref`
* `entitlement_key`
* `certification_requirements`

Promotion preflight resolves the snapshot and **fails closed** unless all hold:
the snapshot is available, the spec still carries all four refs, a successful
import receipt covers both the snapshot id and the action key, and artifact pins
match the immutable schema and tool hashes.

## Metadata import vs. executable import [#metadata-import-vs-executable-import]

Importing a tool's *description* and making it *executable* are deliberately
separate steps.

* **Metadata import** (`PrepareToolImport`) records a manifest hash and, when
  receipt recording is on, an immutable import receipt. The metadata becomes
  available for permission and egress-policy previews.
* **Executable import** additionally requires an explicitly **redacted recorded
  transcript** with exactly one `server/discover`, one `tools/list`, and one
  `tools/call` exchange per imported action. HTTP headers and credential material
  are forbidden in the transcript. Ductor replays it through the real bounded
  outbound MCP client against an **ephemeral local replay server** and publishes an
  executed-v1 recorded-replay scorecard.

<Callout type="info" title="Missing evidence leaves metadata, not an action">
  If the transcript, tool, schema, semantics, runner, scorecard, or implementation
  evidence is missing, expired, or mismatched, the metadata stays available but no
  executable action is registered. You cannot get an executable external MCP action
  without a clean recorded-replay scorecard.
</Callout>

## Stateless provider contract [#stateless-provider-contract]

Ductor's outbound client speaks MCP &#x2A;*`2026-07-28`**. It does not initialize a
connection or retain an MCP session. Every discovery, listing, and call request
includes the protocol version, client capabilities, method, optional tool name,
and trace context needed to process that request independently.

The provider must return a complete discovery result that advertises
`2026-07-28`, its capabilities, server identity, and valid cache hints. Tool
catalog results must also include complete result and cache fields. Ductor
excludes a tool if its input schema does not have an object root or contains an
unsafe `x-mcp-header` annotation.

When a schema marks an input with `x-mcp-header`, Ductor copies that argument to
the matching `Mcp-Param-*` header and preserves the value in the JSON body. This
lets providers route on selected parameters without making headers a second
source of truth.

A provider can return `input_required` with typed `inputRequests` and opaque
`requestState`. Ductor exposes those fields in the connector result. The caller
can then retry the original action with a reserved `_ductor_mcp` control object:

```json
{
  "id": "provider-argument",
  "_ductor_mcp": {
    "client_capabilities": {
      "elicitation": {}
    },
    "input_responses": {
      "approval": {
        "action": "accept"
      }
    },
    "request_state": "opaque-provider-state"
  }
}
```

Ductor strips `_ductor_mcp` before validating or sending the provider's tool
arguments. The control fields become MCP request metadata and continuation
fields instead. No replica affinity or hidden continuation state is required.

## Every call is re-gated [#every-call-is-re-gated]

Certified registrations are durable: each Ductor process opens its own
registration epoch, seeds an outbox from active registrations, and claims pages
with `FOR UPDATE SKIP LOCKED`; registration is idempotent for exact hashes and
rejects drift, reporting `registration_pending` before a tenant-scoped
registration is available. The durable worker is
`connector.external_mcp_registration_worker` (bounded interval, batch, lease,
retry, and max-attempt settings). The whole feature is gated behind
`connector.external_mcp.enabled` — an enterprise activation.

Importing a provider registers metadata; it never opens a side door. Both paths
below converge on the same executor, so an imported MCP tool clears exactly the
gates a first-party action does:

```mermaid
flowchart LR
  imp[import: register metadata] -.->|no shortcut| exec
  call([tool call]) --> exec[connector action executor]
  exec --> g[scope, semantics, entitlement, egress, admission]
  g --> http[shared HTTP client] --> ext([external MCP server])
```

<Callout type="warn" title="No trust is inherited from import">
  Every outbound call still enters the normal connector action executor. That
  single boundary re-checks tenant/provider-config scope, action semantics,
  entitlement/readiness facts, egress placement and dispatch policy, and active
  fair-admission capacity before the shared connector HTTP client issues the
  request. And external providers **never** import or recursively expose Ductor's
  own inbound MCP server.
</Callout>

## Already shipping [#already-shipping]

Many `*_mcp` connectors already ship as external MCP providers — including
`hubspot_mcp`, `linear_mcp`, `notion_mcp`, `slack_mcp`, and `vercel_mcp` — visible
in the [catalog](/docs/connectors/catalog) like any other provider. Their promotion
evidence emits redacted metadata only (provider config identity, remote tool name,
Ductor action key, schema/tool hashes, import receipt id, governance refs, and a
single connector operation), which feeds the existing permission preview and egress
placement policy preview.

## Where to go next [#where-to-go-next]

* [Ductor's MCP Server](/docs/ai/mcp-server) — the inbound side: Ductor exposing its own tools over MCP.
* [Marketplace & Deployments](/docs/connectors/marketplace-and-deployments) — how imported actions are promoted as immutable artifacts.
* [Policies, Quotas & Audit](/docs/connectors/policies-quotas-audit) — the entitlement, egress, and admission gates each call re-checks.
