Request-scoped MCP HTTP Re-export
agent-client-protocol-polyfill::mcp_over_acp::McpOverAcpPolyfill adapts the
native ACP MCP transport for a final agent with an MCP 2026-07-28 HTTP client.
MCP adaptation is explicit and is not built into the conductor. There is no
fallback to older revisions.
The component-facing side of the bridge always uses the opt-in native protocol:
- Servers are declared as
McpServer::Acpwith aserverId. - Each operation uses
mcp/messagewith the server ID and a fresh logical ID. - Provider notifications belong to that operation; its final response carries an inner MCP result or error.
- There is no initialize/connect/disconnect or MCP session-header exchange.
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 one connection-scoped loopback listener.
- Replaces the declaration with a server-addressed URL and an HMAC-derived Bearer token in an Authorization header, preserving name and metadata.
- Maps each HTTP POST to one independent
mcp/messagerequest with a fresh UUID logical ID. External HTTP IDs may overlap without sharing lifetime. - Streams that operation’s notifications and projects its terminal carrier into the corresponding HTTP JSON-RPC response.
- Cancels only that operation if its HTTP response stream is dropped.
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.
Routes are derived from server IDs, not accumulated as per-server listeners or route-table entries. The token authenticates that server on this ACP connection. Keep the declaration’s header; never place its bearer credential in the URL.
The native wire envelopes are documented in the SDK Protocol Reference.
HTTP Mode
McpOverAcpPolyfill::http() replaces native declarations with loopback HTTP
URLs. Each POST carries current MCP method/version headers and required inner
version/client-capabilities metadata. Results use JSON, or an operation-scoped
SSE response when notifications are emitted.
let bridge = McpOverAcpPolyfill::http();
Authentication and Origin checks precede bounded request parsing. The adapter does not accept batches, client responses, MCP initialize/session headers, or standalone GET/DELETE streams. It has no resumable SSE event IDs.
Native-tool semantics, not another HTTP gateway’s policy
The endpoint re-exports native tools with its own routing and authentication.
It strips transport-only x-mcp-header annotations from schema positions in
tools/list, retaining argument validation, property names, defaults, examples,
and other instance data. Annotated native tools remain listed and callable.
tools/call is forwarded directly, without hidden descriptor discovery.
Mcp-Param-* headers are rejected and have no authorization or routing meaning.
An external HTTP gateway’s mirrored-header security policy is not transported;
deployments relying on it must enforce that policy separately at the new endpoint.
Lifecycle and Failure Behavior
Previously queued notifications precede the terminal outcome. Only a matching namespaced subscription ID is rewritten to the external HTTP ID; opaque retry state, unrelated metadata, and progress tokens are untouched.
Inner MCP errors retain their code, data, and extensions. Outer ACP failures are projected as binding failures, not mistaken for MCP method outcomes. Local overflow cancels that request and reports a distinct resource error.
Admission belongs to the HTTP response body until consumption or drop, including admitted validation-error responses. Notification buffering and request/terminal payload sizes have adapter-local bounds. These policies do not change generic ACP frame queues or establish a bound on arbitrary application memory.
The polyfill does not infer ACP session IDs. Native registration dispatch remains the authority for whether a server exists. The listener and signing secret live only for the containing ACP connection.