Skip to content
Merged
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
2 changes: 0 additions & 2 deletions Cargo.lock

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

7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ This repository is the official **Rust SDK** for ACP. It provides crates for bui
- [`agent-client-protocol-rmcp`](./src/agent-client-protocol-rmcp/) – Integration with the [`rmcp`](https://docs.rs/rmcp) MCP SDK.
- [`agent-client-protocol-derive`](./src/agent-client-protocol-derive/) – Derive macros used by the core crate.

Native MCP-over-ACP support is currently opt-in through the core crate's
`unstable_mcp_over_acp` feature. Standalone MCP servers need no ACP transport
feature; the rmcp integration exposes a matching passthrough feature when those
servers are attached to ACP.

**Proxy orchestration**

- [`agent-client-protocol-conductor`](./src/agent-client-protocol-conductor/) – Binary and library that manages chains of proxy components.
- [`agent-client-protocol-polyfill`](./src/agent-client-protocol-polyfill/) – Compatibility proxies, including bridging legacy `acp:` MCP declarations for agents that cannot consume them directly.
- [`agent-client-protocol-polyfill`](./src/agent-client-protocol-polyfill/) – Compatibility proxies, including adapting native MCP-over-ACP declarations to HTTP for agents that cannot consume them directly.
- [`agent-client-protocol-trace-viewer`](./src/agent-client-protocol-trace-viewer/) – Interactive sequence-diagram viewer for conductor trace files.

**Patterns, examples, and testing**
Expand Down
17 changes: 10 additions & 7 deletions md/conductor.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ agent-client-protocol-conductor --serve agent "proxy-one" "base-agent"
agent-client-protocol-conductor --trace ./trace.jsons --serve agent "proxy-one" "base-agent"
```

There is no conductor `mcp` subcommand. MCP compatibility bridging lives in
`agent-client-protocol-polyfill` and must be inserted explicitly when needed.
There is no conductor `mcp` subcommand. Compatibility for HTTP-capable agents that lack the
native ACP MCP transport lives in `agent-client-protocol-polyfill` and must
be inserted explicitly when needed.

## Programmatic Usage

Expand All @@ -122,11 +123,13 @@ the chain depends on initialization data.
## MCP Compatibility

MCP-over-ACP adaptation is intentionally not built into `ConductorImpl`. Add
`McpOverAcpPolyfill::http()` or `McpOverAcpPolyfill::stdio(...)` as a proxy in
the chain when an agent cannot consume the legacy `McpServer::Http` `acp:`
declaration directly. Keeping the polyfill explicit prevents instrumentation
or orchestration from silently changing session MCP declarations. See
[MCP Bridge](./mcp-bridge.md).
`McpOverAcpPolyfill::http()` as a proxy in the chain immediately before a final
agent that cannot consume native `McpServer::Acp` declarations. The
provider-facing side continues to use the feature-gated `mcp/connect`,
`mcp/message`, and `mcp/disconnect` methods; only the final-agent side is
adapted to HTTP. Keeping the polyfill explicit prevents instrumentation or
orchestration from silently changing session MCP declarations. See [MCP
Bridge](./mcp-bridge.md).

## Tracing

Expand Down
7 changes: 6 additions & 1 deletion md/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The core SDK. Provides:
- **Message handling** (`on_receive_request`, `on_receive_notification`, `on_receive_dispatch`)
- **Protocol types** (`agent_client_protocol::schema::*`) - all ACP message types
- **Transports and process launching** (`Channel`, `Lines`, `ByteStreams`, `Stdio`, `AcpAgent`)
- **MCP server attachment** - runtime-agnostic interfaces for wiring MCP servers into ACP sessions
- **MCP server attachment** - runtime-agnostic interfaces for wiring MCP servers into ACP sessions through the opt-in `unstable_mcp_over_acp` transport

### agent-client-protocol-http

Expand All @@ -28,6 +28,11 @@ Integration with the [rmcp](https://docs.rs/rmcp) crate:
- **`McpServer::builder()`** - define MCP tools in Rust code
- **`McpServer::from_rmcp()`** - wrap an rmcp server as an ACP MCP server

Standalone rmcp-backed servers need no ACP transport feature. Enable the
integration crate's `unstable_mcp_over_acp` feature to advertise an attached
server as `McpServer::Acp`. Agents limited to HTTP MCP transports require the
separate compatibility polyfill.

## Role System

The type system is built around **roles** - the logical identity of an endpoint.
Expand Down
5 changes: 3 additions & 2 deletions md/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ src/
├── agent-client-protocol-cookbook/ # Usage patterns (rendered as rustdoc)
├── agent-client-protocol-derive/ # Proc macros
├── agent-client-protocol-conductor/ # Conductor binary and library
├── agent-client-protocol-polyfill/ # Compatibility proxy implementations
├── agent-client-protocol-polyfill/ # MCP-over-ACP transport compatibility
├── agent-client-protocol-test/ # Test utilities and fixtures
├── agent-client-protocol-trace-viewer/ # Trace visualization tool
└── yopo/ # "You Only Prompt Once" example client
Expand All @@ -40,7 +40,7 @@ graph TD
http[agent-client-protocol-http<br/>HTTP/SSE/WebSocket transport]
rmcp[agent-client-protocol-rmcp<br/>rmcp integration]
conductor[agent-client-protocol-conductor<br/>Proxy orchestration]
polyfill[agent-client-protocol-polyfill<br/>Compatibility proxies]
polyfill[agent-client-protocol-polyfill<br/>MCP transport compatibility]
trace[agent-client-protocol-trace-viewer<br/>Trace visualization]
cookbook[agent-client-protocol-cookbook<br/>Usage patterns]

Expand All @@ -60,6 +60,7 @@ graph TD
- [Transport Architecture](./transport-architecture.md) - The frame-aware boundary shared by transports and in-process components
- [Conductor Design](./conductor.md) - How the conductor orchestrates proxy chains
- [Protocol Reference](./protocol.md) - Wire protocol details and extension methods
- [MCP Bridge](./mcp-bridge.md) - Adapting native MCP-over-ACP for HTTP-capable agents
- [Original P/ACP Design Proposal](./proxying-acp.md) - Historical design context; not the current wire reference
- [Migrating to v2.0](./migration_v2.0.md) - Upgrade guide from 1.x to 2.0
- [Migrating to v0.11](./migration_v0.11.x.md) - Upgrade guide from 0.10.x to 0.11
126 changes: 65 additions & 61 deletions md/mcp-bridge.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
# MCP-over-ACP Compatibility Bridge

`agent-client-protocol-polyfill::mcp_over_acp::McpOverAcpPolyfill` is an
explicit proxy for agents that cannot consume the SDK's legacy ACP-routed MCP
server declarations directly. MCP adaptation is no longer built into the
conductor.
`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.

## Native and Legacy Transports
The component-facing side of the bridge always uses the opt-in native protocol:

Two similarly named mechanisms coexist and must not be mixed:
- Servers are declared as `McpServer::Acp` with a `serverId`.
- Connections use `mcp/connect`, `mcp/message`, and `mcp/disconnect`.
- `mcp/disconnect` is a request with a response.

- The draft **native** transport is enabled by `unstable_mcp_over_acp`. It uses
`McpServer::Acp` plus `mcp/connect`, `mcp/message`, and `mcp/disconnect`.
- The polyfill's **legacy compatibility** path recognizes
`McpServer::Http` entries whose URL begins with `acp:`. It routes them with
`_mcp/connect`, `_mcp/message`, and `_mcp/disconnect`.
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.

The polyfill currently implements the second path. New implementations that
control both peers should prefer the draft native schema types; use the
polyfill only where compatibility requires it.
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.

## Placement

Insert the polyfill as a proxy before the final agent:
Insert the polyfill immediately before the final agent that lacks native
MCP-over-ACP support:

```rust,ignore
use agent_client_protocol_conductor::{ConductorImpl, ProxiesAndAgent};
Expand All @@ -36,40 +37,56 @@ ConductorImpl::new_agent("conductor", components)
.await?;
```

The application proxy can then supply a legacy declaration such as an HTTP MCP
server whose URL is `acp:server-1`. The identifier is opaque and must route back
to the component that provides the MCP server.
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.

During initialization, the polyfill forwards the request to its successor and
sets `agentCapabilities.mcpCapabilities.acp` in the response seen upstream. In
this chain position that capability means the polyfill can handle the routed
transport; it does not imply that the final agent implements it natively.
sets `agentCapabilities.mcpCapabilities.acp` in the response seen upstream when
the successor advertises HTTP MCP support. 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 `McpServer::Http` entry with an `acp:` URL in `session/new`, the
polyfill:
For each `McpServer::Acp` entry in `session/new`, `session/load`,
`session/resume`, or feature-gated `session/fork`, the polyfill:

1. Creates or reuses a connection-scoped localhost bridge endpoint for the
`serverId` and replaces the declaration with the HTTP transport for the
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.
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.

Enable the polyfill crate's `unstable_session_fork` feature when adapting fork
requests.

1. Rejects non-empty HTTP headers, which have no defined meaning for this
compatibility transport.
2. Reuses an existing listener for the same ACP identifier or binds a new
localhost TCP listener.
3. Replaces the server declaration with a transport the final agent can open.
4. When that transport connects, sends `_mcp/connect` toward the component that
declared the identifier.
5. Relays MCP requests and notifications through `_mcp/message`, keyed by the
returned connection identifier, and sends `_mcp/disconnect` when the bridge
closes.
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` and `_meta` even when its endpoint is reused.

The exact underscore-prefixed envelopes are documented in the [Proxy Extension
Protocol Reference](./protocol.md#legacy-mcp-polyfill-methods).
The native wire envelopes are documented in the [SDK Protocol
Reference](./protocol.md#native-mcp-over-acp).

## HTTP Mode

`McpOverAcpPolyfill::http()` is the default compatibility shape. It replaces
the `acp:` declaration with an HTTP MCP URL on `localhost`. 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.
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.

```rust,ignore
let bridge = McpOverAcpPolyfill::http();
Expand All @@ -78,30 +95,17 @@ let bridge = McpOverAcpPolyfill::http();
The listener is bound only on loopback and uses an ephemeral port. It does not
implement resumable SSE event IDs.

## Stdio Mode

`McpOverAcpPolyfill::stdio(command)` replaces the declaration with an MCP stdio
server. It appends `mcp PORT` to the supplied command and expects that process
to copy newline-delimited JSON-RPC between stdio and the designated localhost
TCP port.

```rust,ignore
let bridge = McpOverAcpPolyfill::stdio(vec![
"legacy-mcp-bridge".to_owned(),
]);
```

The current `agent-client-protocol-conductor` binary has no `mcp` subcommand,
so it is not itself a valid stdio bridge command. Use this mode only with a
compatible bridge executable; otherwise use HTTP mode.

## Lifecycle and Failure Behavior

Each accepted bridge connection receives a unique connection ID from
`_mcp/connect`. The polyfill keeps a connection map until the local MCP
transport closes, then removes the entry and sends `_mcp/disconnect`.
Connection or relay failures propagate through the proxy connection rather than
being encoded as notification responses.
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 MCP server identifier and the resulting MCP connection ID.
the declared `serverId` and the resulting active `connectionId`.
77 changes: 70 additions & 7 deletions md/migration_v2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Version 2.0 makes JSON-RPC notification semantics explicit, changes the low-leve
transport boundary so frames remain intact across components and adapters, clarifies the
distinction between responding to requests and routing responses, makes dynamic handler lifetimes
explicit, and gives `AcpAgent` an SDK-owned process-launch configuration instead of reusing an MCP
wire-schema type.
wire-schema type. It also replaces the SDK-local MCP-over-ACP wire extension with the shared
schema's opt-in native transport.

## Notifications cannot receive error responses

Expand Down Expand Up @@ -167,16 +168,78 @@ or responses. Their method names now reflect that input:

## Connection and session accessors borrow

`McpConnectionTo::acp_id` now returns `&str`. The deprecated `acp_url` alias was removed; use
`acp_id` instead. `McpConnectionTo::connection_to` is now `connection` and returns
`&ConnectionTo<_>`.
`McpConnectionTo::acp_id` is now `server_id` and returns `Option<&McpServerAcpId>`. The new name
matches the native `McpServer::Acp` declaration; the `Option` reflects that a server can also be
connected directly without ACP. `connection_id` returns an `Option<&McpConnectionId>` for the
distinct active connection created by `mcp/connect`. Use `context()` to match explicitly on
`McpConnectionContext::Standalone` or `McpConnectionContext::Acp { server_id, connection_id }`.
The deprecated `acp_url` alias was removed. `McpConnectionTo::connection_to` is now `connection`
and returns `&ConnectionTo<_>`.

`ActiveSession::modes` and `ActiveSession::meta` now return `Option<&T>` instead of `&Option<T>`,
and `ActiveSession::connection` returns `&ConnectionTo<_>`.

These accessors avoid implicit allocation and handle cloning. Call `.to_owned()` on `acp_id`,
`.cloned()` on `modes` or `meta`, and `.clone()` on either connection accessor when an owned value
is required.
These accessors avoid implicit allocation and handle cloning. Call `.cloned()` on `server_id()`,
`connection_id()`, `modes`, or `meta`, and `.clone()` on either connection accessor when an owned
value is required.

## MCP servers use the native opt-in transport

The runtime-agnostic `agent_client_protocol::mcp_server` module remains available without an
unstable ACP feature, so standalone MCP servers do not allocate or retain schema transport IDs.
Enable the feature when attaching a server to ACP with `Builder::with_mcp_server` or
`SessionBuilder::with_mcp_server`:

```toml
agent-client-protocol = { version = "2", features = ["unstable_mcp_over_acp"] }
```

`agent-client-protocol-rmcp` no longer enables this feature merely to build or directly serve an
MCP server. Applications that attach an rmcp-backed server to ACP should enable its matching
`unstable_mcp_over_acp` passthrough feature. The transport remains unstable and may change
independently of the stable ACP surface.

In 1.x, the SDK represented an ACP-provided MCP server as `McpServer::Http` with an `acp:` URL and
routed it through SDK-local underscore-prefixed methods. In 2.0, providers and native consumers
use:

- `McpServer::Acp(McpServerAcp { name, server_id, .. })` in session setup requests;
- `mcp/connect` with `serverId`, returning a distinct `connectionId`;
- `mcp/message` requests and notifications keyed by that connection ID; and
- an `mcp/disconnect` request with an empty response.

The low-level SDK-local `McpConnectRequest`, `McpConnectResponse`, `McpOverAcpMessage`, and
`McpDisconnectNotification` types were removed. Use the feature-gated schema types instead:

| 1.x SDK-local type | 2.0 schema type |
| --- | --- |
| `McpConnectRequest` | `schema::v1::ConnectMcpRequest` |
| `McpConnectResponse` | `schema::v1::ConnectMcpResponse` |
| `McpOverAcpMessage` request | `schema::v1::MessageMcpRequest` |
| `McpOverAcpMessage` notification | `schema::v1::MessageMcpNotification` |
| `McpDisconnectNotification` | `schema::v1::DisconnectMcpRequest` and `DisconnectMcpResponse` |

The public method-name constants moved to the schema's generated method-name
tables:

| 1.x SDK-local constant | 2.0 schema constant |
| --- | --- |
| `METHOD_MCP_CONNECT_REQUEST` | `schema::v1::CLIENT_METHOD_NAMES.mcp_connect` |
| `METHOD_MCP_MESSAGE` | `schema::v1::CLIENT_METHOD_NAMES.mcp_message` or `AGENT_METHOD_NAMES.mcp_message`, depending on direction |
| `METHOD_MCP_DISCONNECT_NOTIFICATION` | `schema::v1::CLIENT_METHOD_NAMES.mcp_disconnect` |

Code using `Builder::with_mcp_server` or `SessionBuilder::with_mcp_server` continues to attach the
high-level server in the same place; the emitted declaration and wire methods change. Global
builder attachment advertises the same server ID on `session/new`, `session/load`,
`session/resume`, and feature-gated `session/fork`. Per-session attachment remains specific to
`session/new`. Do not construct an HTTP server with an `acp:` URL. If the final agent accepts HTTP
but not native ACP MCP servers, insert `McpOverAcpPolyfill` immediately before it. The polyfill now
consumes native `McpServer::Acp` declarations and adapts only its final-agent-facing side.

The polyfill's public `BridgeMode` enum and `McpOverAcpPolyfill::stdio` were removed because the
required conductor `mcp` helper subcommand no longer exists. The polyfill has one supported mode;
construct it with `McpOverAcpPolyfill::http()` or `Default`, or manage a standard MCP transport
separately.

## Low-level helpers have a narrower surface

Expand Down
Loading