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
15 changes: 10 additions & 5 deletions md/conductor.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,11 @@ The command-line component provider, `AgentOnly`, `ProxiesAndAgent`, and static
proxy vectors use their components for every version, so each supplied
component must support the selected version. Use `Agent.protocol_router()` or
`Proxy.protocol_router()` when a static component has separate
implementations. A custom instantiator rejects a version it does not support
(for example with `SelectedInitializeRequest::into_v1`); the error answers the
implementations. A custom instantiator that supports only a lower version
negotiates down by returning the request for that version (for example with
`SelectedInitializeRequest::into_v1`), and the client receives an `initialize`
response for it. An instantiator returns `InstantiatedChain` (agent mode) or
`InstantiatedProxies` (proxy mode). An instantiator error answers the
`initialize` request and leaves the connection in a failed state that rejects
later traffic. A first message that is not a valid initialization is answered
with an error and leaves the conductor uninitialized, so the client can retry.
Expand Down Expand Up @@ -187,9 +190,11 @@ orchestration from silently changing session MCP declarations. See [MCP
Bridge](./mcp-bridge.md).

The polyfill supports v1 by default. For a draft-v2 chain, enable
`unstable_protocol_v2` on both the conductor and polyfill crates; without the
polyfill feature, it rejects v2 initialization instead of interpreting v2
traffic as v1.
`unstable_protocol_v2` on both the conductor and the polyfill crate; the
conductor's feature does not enable the polyfill's. A polyfill built without
the feature negotiates its `proxy/initialize` down to v1 like any v1-only
proxy, so the whole chain, and the `initialize` response the client
receives, use v1.

## Tracing

Expand Down
9 changes: 5 additions & 4 deletions md/mcp-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,11 @@ polyfill:
into the corresponding HTTP JSON-RPC response.
5. 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.
The polyfill adapts the declarations of every session setup request: v1
`session/new`, `session/load`, `session/resume`, and `session/fork`, and draft
v2 `session/new`, `session/resume`, and `session/fork`. With the polyfill
crate's `unstable_session_fork` feature it also validates `session/fork`
requests against the schema's fork type. Without it, the agent validates them.

Declarations using another transport are left unchanged, including extension
transports represented by v2's `McpServer::Other`.
Expand Down
66 changes: 66 additions & 0 deletions md/migration-v5.md
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,72 @@ implemented outside the conductor crate. Its methods take a `ConductorRunner`,
whose fields are private, so no external implementation could be written
before either. Use the provided `Agent` and `Proxy` host roles.

`ConductorHostRole` is now a marker trait: its `Instantiator` type and its
`initialize_method`, `select_protocol_version`, `instantiate` and
`handle_dispatch` methods moved to a private supertrait and take an argument
only the conductor crate can create, so code outside it cannot call them.
`ConductorImpl::new`
is private. Create conductors with `ConductorImpl::new_agent` or
`ConductorImpl::new_proxy`.

### Conductor internals are no longer exported

The conductor crate exports its API explicitly instead of through a glob.
`ConductorRunner`, `ConductorMessage`, `ConductorSuccessor`, `ComponentIndex`
and `SourceComponentIndex` are no longer public; they only described the
conductor's internal routing.

### Instantiators return `InstantiatedChain` and `InstantiatedProxies`

`InstantiateProxiesAndAgent` and `InstantiateProxies`, including instantiator
closures, return `#[non_exhaustive]` structs instead of tuples. The
`ProxiesAndAgentComponents` type alias is removed.

| Before | After |
| --- | --- |
| `Ok((request, proxies, agent))` | `Ok(InstantiatedChain::new(request, proxies, agent))` |
| `Ok((request, proxies))` | `Ok(InstantiatedProxies::new(request, proxies))` |
| `ProxiesAndAgentComponents` | `InstantiatedChain` |

`new` takes `impl Into<SelectedInitializeRequest>`, so a `v1::InitializeRequest`
needs no `.into()`.

### Instantiators can negotiate down

`SelectedInitializeRequest::into_v1` now converts a v2 request to v1 the way
the SDK's agents negotiate `initialize`, instead of failing. An instantiator
may return a request for a lower version than the one the conductor selected,
and the chain then initializes with that version. A v1-only instantiator that
calls `into_v1()?` therefore accepts a v2 client and answers it with a v1
`initialize` response, where it used to reject it with `-32600`. Returning a
request for a version above the selected one is still rejected.

### Other conductor changes

- `ConductorArgs`, `ConductorCommand` and its variants are `#[non_exhaustive]`.
Create `ConductorArgs` by parsing arguments with `clap::Parser`.
- `trace::Protocol` is renamed to `trace::MessageProtocol`, so it no longer
shares a name with the core `Protocol` trait. The serialized trace format is
unchanged.
- The conductor's `unstable_protocol_v2` feature no longer enables the
polyfill's feature (it only did so for the conductor's own tests). Enable
`unstable_protocol_v2` on `agent-client-protocol-polyfill` yourself for a v2
chain.
- `McpOverAcpPolyfill` is `#[non_exhaustive]`. Create it with
`McpOverAcpPolyfill::http()`.
- The polyfill rewrites `McpServer::Acp` entries in `session/fork` in every
build. It used to forward them unchanged unless `unstable_session_fork` was
enabled.
- The polyfill forwards a repeated `proxy/initialize` on the connection's
protocol version instead of closing its connection. An invalid
`protocolVersion` is answered with `-32602` instead of `-32603`, and a
request for a different version than the first one with `-32600`.
- Conductor traces record the messages between the last proxy and the agent.
Trace snapshots and tools that read traces see these extra events.
- `agent_client_protocol_trace_viewer::TraceViewerConfig` is
`#[non_exhaustive]`. Start from `TraceViewerConfig::default()` and use the
`port` and `open_browser` setters. `TraceSource` is no longer public.

## The rmcp integration moves behind the `rmcp_3` feature

`agent-client-protocol-rmcp` now has one module per supported rmcp major
Expand Down
19 changes: 11 additions & 8 deletions md/protocol-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,13 +368,16 @@ schema, but each supplied component must support that version. Use
`Agent.protocol_router()` or `Proxy.protocol_router()` when a static component
has separate implementations.
Custom `InstantiateProxiesAndAgent` and `InstantiateProxies` implementations
receive a `SelectedInitializeRequest` whose variant is the selected version,
and reject the versions they do not support (for example with
`SelectedInitializeRequest::into_v1`). Returning the initialize request
unchanged preserves its complete raw parameters for an exact-version request,
including unknown extensions. Returning a modified typed request makes that
serialized request authoritative. The conductor pins `protocolVersion` to its
selected version even if an instantiator attempts to change it.
receive a `SelectedInitializeRequest` whose variant is the selected version.
An instantiator that supports only a lower version negotiates down by
returning the request for that version (for example with
`SelectedInitializeRequest::into_v1`, which keeps what parses under v1).
Returning the initialize request unchanged preserves its complete raw
parameters for an exact-version request, including unknown extensions.
Returning a modified typed request makes that serialized request
authoritative. The conductor sets `protocolVersion` to the version of the
returned variant even if an instantiator attempts to change the field, and
rejects a variant above the selected version.

### MCP compatibility polyfill

Expand All @@ -383,7 +386,7 @@ The concrete
participate in a v2 conductor chain when its `unstable_protocol_v2` feature is
enabled. It selects v1 or v2 from `proxy/initialize`, uses that version's MCP
capability and wire types, and adapts native `McpServer::Acp` declarations in
v2 `session/new`, `session/resume`, and feature-gated `session/fork` requests.
v2 `session/new`, `session/resume`, and `session/fork` requests.
Other declarations and unrelated request fields remain unchanged. See
[MCP-over-ACP Compatibility Bridge](./mcp-bridge.md) for placement and feature
configuration.
Expand Down
17 changes: 13 additions & 4 deletions src/agent-client-protocol-conductor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,25 @@ description = "Conductor for orchestrating Agent Client Protocol proxy chains"
keywords = ["acp", "agent", "conductor", "ai"]
categories = ["development-tools"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[[bin]]
name = "agent-client-protocol-conductor"
path = "src/main.rs"

# Skipped unless the polyfill's own v2 feature is enabled too, as
# `cargo test --workspace --all-features` does.
[[test]]
name = "mcp_over_acp_polyfill_v2"
required-features = ["unstable_protocol_v2", "agent-client-protocol-polyfill/unstable_protocol_v2"]

[features]
default = []
unstable_protocol_v2 = [
"agent-client-protocol/unstable_protocol_v2",
"agent-client-protocol-polyfill/unstable_protocol_v2",
]
# The polyfill is a separate crate: enable its own `unstable_protocol_v2`
# feature to use it in a v2 chain.
unstable_protocol_v2 = ["agent-client-protocol/unstable_protocol_v2"]

[dependencies]
agent-client-protocol = { workspace = true, features = ["process", "stdio", "unstable_mcp_over_acp"] }
Expand Down
Loading
Loading