Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions md/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

- [Transport Architecture](./transport-architecture.md)
- [HTTP / WebSocket Transport](./http-transport.md)
- [Native MCP-over-ACP](./mcp-over-acp.md)

# Conductor (agent-client-protocol-conductor)

Expand Down
51 changes: 40 additions & 11 deletions md/mcp-bridge.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# MCP-over-ACP Compatibility Bridge

**Draft checkpoint:** The stateful adapter described here targets older MCP
semantics. It is not an implementation of MCP 2026-07-28, which removes
initialization, protocol sessions, GET, and DELETE. The intended MCP-over-ACP
transport will target that stateless revision only; retaining this session mode
for backwards compatibility is not a goal. See the
[modernization audit](https://agentclientprotocol.com/rfds/mcp-over-acp#modernization-audit-mcp-2026-07-28).

`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.
Expand Down Expand Up @@ -93,12 +100,14 @@ polyfill:
final agent.
2. Retains the native `serverId` so connections can be routed back to the
component that provided the server.
3. Opens the endpoint's native connection by sending `mcp/connect` with that
server ID toward the provider.
3. Opens a native connection when an HTTP MCP client initializes a logical
session, sending `mcp/connect` with that server ID toward the provider.
Independent HTTP sessions receive independent native connections.
4. Relays requests and notifications through `mcp/message`, using the returned
`connectionId` for that active MCP connection.
5. Sends an `mcp/disconnect` request when the local transport closes and removes
the connection from the bridge.
5. Sends an `mcp/disconnect` request when the logical HTTP MCP session closes
and removes that connection from the bridge. The listening endpoint remains
available for other sessions.

Enable the polyfill crate's `unstable_session_fork` feature when adapting fork
requests. Stable v1 setup includes `session/new`, `session/load`, and
Expand All @@ -120,8 +129,15 @@ Reference](./protocol.md#native-mcp-over-acp).

`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.
embedded server accepts MCP POST requests, SSE GET streams, and session DELETE
requests at `/`, retaining JSON-RPC batch frames and correlating each POST with
its response.

The adapter uses stateful Streamable HTTP. A successful MCP initialization
returns an `MCP-Session-Id` header. Clients must send that header on subsequent
POST, GET, and DELETE requests; unknown or closed sessions return HTTP 404.
Clients that previously ignored session headers must retain the returned ID.
An individual POST response or GET stream closing does not end the session.

```rust,ignore
let bridge = McpOverAcpPolyfill::http();
Expand All @@ -132,15 +148,28 @@ 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.
The listener and the logical MCP connections have different lifetimes. Endpoint
creation alone does not open an MCP connection. Each HTTP MCP session receives
its own native `connectionId` from `mcp/connect`, so initialization, request IDs,
and server-originated messages cannot cross between clients. Disconnecting one
session leaves its siblings and the cached endpoint usable.

Deleting an HTTP MCP session stops its local transport and sends
`mcp/disconnect` for that session's native connection. Request failures use the
corresponding request's error path; notifications are never answered with
synthetic errors. Closing the parent ACP connection drops its listeners and
session tasks; a disconnect exchange is not possible after that transport is
gone.

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`.

Known checkpoint gaps: aborting HTTP initialization before receiving its
response can leave an unadvertised session until the listener stops. There is
no idle timeout, and DELETE during pending forward/reverse requests still
needs regression coverage. These are reasons to keep the checkpoint in draft,
not features to preserve in the stateless replacement.
105 changes: 105 additions & 0 deletions md/mcp-over-acp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Native MCP-over-ACP

**Draft checkpoint:** This chapter describes the in-progress connection-oriented
implementation, not conformance with MCP 2026-07-28. The stabilization target is
that stateless MCP revision only, with no legacy compatibility requirement.
Its initialization and connect/disconnect API below will need replacement; see
the [modernization audit](https://agentclientprotocol.com/rfds/mcp-over-acp#modernization-audit-mcp-2026-07-28).

MCP-over-ACP lets an ACP client or proxy provide an MCP server through its
existing ACP connection. A native agent can use that server without starting
another process, opening an HTTP endpoint, or introducing a conductor.

Enable `unstable_mcp_over_acp` on `agent-client-protocol`. Add
`unstable_protocol_v2` for draft-v2 connections. The wire types come from the
shared ACP schema; the transport remains unstable.

## Providing a server

The `mcp_server::McpServer` APIs attach servers to session setup requests using
`McpServer::Acp` declarations. The separate `agent-client-protocol-rmcp` crate
can build a server from tools or an `rmcp` service without making `rmcp` a
dependency of the core SDK.

There are three distinct identifiers:

| Identifier | Meaning |
| --- | --- |
| `serverId` | Provider-generated identity for the declared MCP server |
| `connectionId` | Provider-generated identity for one active connection to that server |
| Outer JSON-RPC `id` | Identity of one ACP request, including an `mcp/message` request |

A server can accept multiple connections. Each has independent MCP
initialization, pending requests, and shutdown. Providers must not reuse a
server ID for different servers visible on the same ACP connection.

Servers are ready as soon as their declarations are published. An agent can
connect and run MCP initialization before returning the ACP session ID.
Providers must not wait for the session setup response before serving MCP.

## Consuming a server

The core `mcp_client` module supplies `McpOverAcp`, a server transport for an
ordinary MCP client. Its version-specific connection helpers open a native
connection to the declared server and route bidirectional MCP traffic over
the ACP channel. No HTTP adapter is involved.

The consuming ACP agent holds a `ConnectionTo<Client>` (or its v2 counterpart):
the ACP client is providing the MCP server. The returned transport implements
`ConnectTo<role::mcp::Client>`, so it can be used by the SDK's MCP client role
or connected to an external MCP implementation through a byte-stream adapter.

The helper opens the transport, not the MCP protocol session. The MCP client
still performs its normal `initialize` / `notifications/initialized` handshake.
It can then list and call tools, while also handling server-originated requests
and notifications.

Do connection setup and MCP work outside the ACP dispatch loop, for example
in a connection-spawned task. Awaiting a peer response inside an ACP message
handler can block the very messages needed to complete that operation. See
[Ordered Application Dispatch](./ordered-application-dispatch.md).

## Closing connections

Use the helper's awaited close operation when shutdown completion matters.
Dropping a native consumer schedules best-effort disconnect; it cannot report
whether the provider acknowledged cleanup.

The provider acknowledges `mcp/disconnect` after stopping that connection's
relay and server work. Requests already dispatched to the child are completed
or failed; this checkpoint still needs explicit failure of requests queued in
the relay at shutdown and direct handler-drop coverage. Other MCP connections
to the same server, and the containing ACP connection, stay usable.

ACP transport closure drops its MCP connection-scoped work. No disconnect
exchange is possible once the ACP transport is gone.

## Runnable direct example

From the repository root:

```sh
cargo run -p agent-client-protocol-rmcp \
--example native_mcp_over_acp \
--features native_mcp_example
```

The example connects an ACP client directly to an ACP agent, attaches an
`rmcp` server on the client side, and uses a real MCP client on the agent side.
It exercises the normal MCP handshake and tool traffic without a conductor,
an HTTP listener, or a subprocess.

## Compatibility

An agent advertises native support with
`agentCapabilities.mcpCapabilities.acp: true` in v1, or
`capabilities.session.mcp.acp: {}` in draft v2. Do not advertise support unless
the agent can consume the transport.

For an HTTP-capable agent without native support, use the explicit
[MCP-over-ACP compatibility bridge](./mcp-bridge.md). Its listening endpoint
can be shared, but each logical HTTP MCP session has its own native connection.

See the [protocol reference](./protocol.md#native-mcp-over-acp) for exact
wire envelopes and the [RFD](https://agentclientprotocol.com/rfds/mcp-over-acp)
for the protocol design.
42 changes: 36 additions & 6 deletions md/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ conductor and the opt-in native MCP-over-ACP transport exposed by the shared ACP
schema. The proxy methods are provisional SDK extensions. MCP-over-ACP is also
unstable and is available only with the `unstable_mcp_over_acp` feature.

The MCP wire methods below describe the current **connection-oriented
checkpoint**, not the intended MCP 2026-07-28-only design. Modernization will
replace the old lifecycle with stateless requests and request-scoped
notifications/cancellation; see the
[RFD audit](https://agentclientprotocol.com/rfds/mcp-over-acp#modernization-audit-mcp-2026-07-28).

## Method Summary

| Method | JSON-RPC shape | Purpose |
Expand Down Expand Up @@ -63,8 +69,9 @@ inner message.

Enable `unstable_mcp_over_acp` to use the draft native transport. A component
providing an MCP server adds `McpServer::Acp` to session setup requests
(`session/new`, `session/load`, `session/resume`, and the opt-in `session/fork`).
Its wire shape contains a human-readable name and an opaque server identifier:
(`session/new` and `session/resume`, plus `session/load` in v1 and the opt-in
`session/fork` in either version). Its wire shape contains a human-readable
name and an opaque server identifier:

```json
{
Expand All @@ -81,9 +88,15 @@ for multiple visible servers on the same ACP connection. The high-level
automatically.

An agent that consumes this transport advertises
`agentCapabilities.mcpCapabilities.acp`. If the final agent supports HTTP but
not ACP-transport MCP servers, place the [MCP-over-ACP compatibility
bridge](./mcp-bridge.md) immediately before it.
`agentCapabilities.mcpCapabilities.acp: true` in v1 or
`capabilities.session.mcp.acp: {}` in draft v2. The v2 capability is an optional
object: omission or `null` means support is not advertised. The `mcp/*` wire
envelopes below are the same in both versions.

See [Native MCP-over-ACP](./mcp-over-acp.md) for the consumer helper and a direct
client/agent example. If the final agent supports HTTP but not ACP-transport
MCP servers, place the [MCP-over-ACP compatibility bridge](./mcp-bridge.md)
immediately before it.

### `mcp/connect`

Expand Down Expand Up @@ -111,6 +124,13 @@ connection ID:

The server ID selects what to connect to; the connection ID selects that
particular running connection. All subsequent messages use the connection ID.
Each connect request creates a separate MCP connection, even when the server
ID is reused. MCP initialization still happens inside that connection through
`mcp/message`.

The provider must be ready when it publishes the server declaration. An agent
may connect and initialize its MCP servers before returning the ACP session
ID; routing cannot depend on waiting for the session setup response.

### `mcp/message`

Expand All @@ -135,7 +155,10 @@ is bidirectional because MCP clients and servers can both issue requests:

Use an outer request for an inner MCP request and an outer notification for an
inner MCP notification. The outer response carries the inner MCP result or
error.
error directly, without another JSON-RPC envelope. The inner `params` field
accepts an object or `null`; omission and `null` both mean no parameters.
Positional arrays are not supported. ACP `_meta` alongside `connectionId` is
separate from MCP metadata inside the inner parameters or result.

### `mcp/disconnect`

Expand All @@ -161,8 +184,15 @@ A successful disconnect returns an empty result:
}
```

The provider stops the connection's relay and server work before acknowledging
disconnect. Outstanding requests complete or fail, and further messages
cannot use the closed connection. Sibling MCP connections and the parent ACP
connection remain usable. An MCP server failure is contained to that connection;
closing the ACP connection releases all of its MCP connections.

## Related Documentation

- [MCP-over-ACP RFD](https://agentclientprotocol.com/rfds/mcp-over-acp)
- [Conductor Design](./conductor.md)
- [MCP Bridge](./mcp-bridge.md)
- [Original P/ACP Design Proposal](./proxying-acp.md) (historical)
Expand Down
1 change: 1 addition & 0 deletions src/agent-client-protocol-conductor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ agent-client-protocol-test.workspace = true
yopo.workspace = true
expect-test.workspace = true
regex.workspace = true
reqwest.workspace = true
rmcp = { workspace = true, features = [
"client",
"server",
Expand Down
Loading
Loading