MCP-over-ACP Compatibility Bridge
agent-client-protocol-polyfill::mcp_over_acp::McpOverAcpPolyfill adapts the
native ACP MCP transport for a final agent that accepts HTTP MCP
servers. MCP adaptation is explicit and is not built into the conductor.
The component-facing side of the bridge always uses the opt-in native protocol:
- Servers are declared as
McpServer::Acpwith aserverId. - Connections use
mcp/connect,mcp/message, andmcp/disconnect. mcp/disconnectis a request with a response.
The SDK-local underscore-prefixed method family and HTTP declarations with a special URL scheme have been retired. The polyfill now translates native declarations to real localhost HTTP URLs only at the compatibility boundary.
Native MCP-over-ACP requires the core SDK’s unstable_mcp_over_acp feature. The
polyfill enables that feature on its core dependency, so applications using the
polyfill receive it through Cargo feature unification.
The polyfill supports stable protocol v1 by default. To place it in a draft-v2
conductor chain, enable unstable_protocol_v2 on both the conductor and
polyfill dependencies:
agent-client-protocol-conductor = { version = "...", features = ["unstable_protocol_v2"] }
agent-client-protocol-polyfill = { version = "...", features = ["unstable_protocol_v2"] }
The feature makes this concrete compatibility proxy recognize v2
initialization, capability, session setup, and mcp/* wire types. It does not
change the core attachment API. Proxy.v2().with_mcp_server(...) provides
connection-global attachment. V2SessionBuilder::with_mcp_server(...) and
V2ResumeSessionBuilder::with_mcp_server(...) provide per-session attachment
for new and resumed sessions respectively. With unstable_session_fork,
V2ForkSessionBuilder::with_mcp_server(...) provides per-session fork
attachment. The polyfill adapts their native declarations when the final agent
supports only HTTP MCP.
Placement
Insert the polyfill immediately before the final agent that lacks native MCP-over-ACP support:
use agent_client_protocol_conductor::{ConductorImpl, ProxiesAndAgent};
use agent_client_protocol_polyfill::mcp_over_acp::McpOverAcpPolyfill;
let components = ProxiesAndAgent::new(agent)
.proxy(application_proxy)
.proxy(McpOverAcpPolyfill::http());
ConductorImpl::new_agent("conductor", components)
.run(upstream_transport)
.await?;
The application proxy can attach a high-level
agent_client_protocol::mcp_server::McpServer. The SDK advertises it in session
setup requests as McpServer::Acp; callers do not need to construct a transport
placeholder themselves. In v2, Proxy.v2().with_mcp_server(...) provides
connection-global attachment. V2SessionBuilder::with_mcp_server(...) and
V2ResumeSessionBuilder::with_mcp_server(...) provide per-session attachment
for new and resumed sessions respectively. With unstable_session_fork,
V2ForkSessionBuilder::with_mcp_server(...) provides per-session fork
attachment. The polyfill translates those native declarations at the final
compatibility boundary.
During initialization, the polyfill forwards the request to its successor. When the successor advertises HTTP MCP support, the polyfill advertises native ACP MCP support in the response seen upstream:
- v1 sets
agentCapabilities.mcpCapabilities.acptotrue. - v2 adds the
capabilities.session.mcp.acpmarker.
In this chain position that capability means the chain can consume native MCP-over-ACP declarations through the adapter; it does not imply that the final agent implements the transport itself.
If the successor already advertises native ACP MCP support, the polyfill leaves
the capability, declarations, and mcp/message traffic unchanged. If it
supports neither native nor HTTP MCP, the polyfill does not advertise ACP MCP
support and rejects any native declaration that is nevertheless supplied.
Transformation
For each schema-selected McpServer::Acp entry in a session setup request, the
polyfill:
- Creates or reuses a connection-scoped localhost bridge endpoint for the
serverIdand replaces the declaration with the HTTP transport for the final agent. - Retains the native
serverIdso connections can be routed back to the component that provided the server. - Opens the endpoint’s native connection by sending
mcp/connectwith that server ID toward the provider. - Relays requests and notifications through
mcp/message, using the returnedconnectionIdfor that active MCP connection. - Sends an
mcp/disconnectrequest when the local transport closes and removes the connection from the bridge.
Enable the polyfill crate’s unstable_session_fork feature when adapting fork
requests. Stable v1 setup includes session/new, session/load, and
session/resume; draft v2 includes session/new and session/resume. Both
versions include session/fork when unstable_session_fork is enabled.
Declarations using another transport are left unchanged, including extension
transports represented by v2’s McpServer::Other.
Endpoints are cached by serverId across session setup requests on the ACP
connection. The output declaration is rebuilt for each occurrence, preserving
that occurrence’s name, _meta, and other unmodified extension fields even
when its endpoint is reused.
The native wire envelopes are documented in the SDK Protocol Reference.
HTTP Mode
McpOverAcpPolyfill::http() is the default compatibility shape. It replaces
the native declaration with an HTTP MCP URL at http://127.0.0.1:PORT. The
embedded server accepts MCP POST requests and an SSE GET stream at /, retaining
JSON-RPC batch frames and correlating each POST with its response.
let bridge = McpOverAcpPolyfill::http();
The listener is bound only on loopback and uses an ephemeral port. It does not implement resumable SSE event IDs.
Lifecycle and Failure Behavior
Each bridge endpoint receives a unique connectionId from mcp/connect. The
polyfill keeps a connection map until the endpoint’s transport task closes,
then removes the entry, sends mcp/disconnect, and observes its response.
Request failures use the corresponding request’s error path; notifications are
never answered with synthetic errors.
A reverse mcp/message request for an unknown connectionId receives
Invalid params. A reverse notification for an unknown connection is ignored,
as required for JSON-RPC notifications.
The polyfill does not infer or store ACP session IDs. Association is carried by
the declared serverId and the resulting active connectionId.