From 472d93722d8358ea04abd1c2fa0cf2c55a776f8b Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 7 Jan 2026 15:45:50 -0500 Subject: [PATCH 1/6] docs(rfd): Split MCP-over-ACP into separate RFD Extract MCP transport mechanism from proxy-chains RFD into standalone mcp-over-acp.mdx. This separation reflects that MCP-over-ACP is useful for any ACP component, not just proxies. New mcp-over-acp.mdx covers: - ACP as MCP transport type (transport: acp with UUID) - Capability advertising via mcpCapabilities.acp - mcp/connect, mcp/message, mcp/disconnect protocol - Bridging for agents without native support proxy-chains.mdx now references mcp-over-acp for MCP-specific details and focuses on the proxy architecture and conductor design. --- docs/rfds/mcp-over-acp.mdx | 290 +++++++++++++++++++++++++++++++++++++ docs/rfds/proxy-chains.mdx | 248 ++++++++----------------------- 2 files changed, 347 insertions(+), 191 deletions(-) create mode 100644 docs/rfds/mcp-over-acp.mdx diff --git a/docs/rfds/mcp-over-acp.mdx b/docs/rfds/mcp-over-acp.mdx new file mode 100644 index 0000000..445ab4a --- /dev/null +++ b/docs/rfds/mcp-over-acp.mdx @@ -0,0 +1,290 @@ +--- +title: "MCP-over-ACP: MCP Transport via ACP Channels" +--- + +Author(s): [nikomatsakis](https://github.com/nikomatsakis) + +## Elevator pitch + +> What are you proposing to change? + +Add support for MCP servers that communicate over ACP channels instead of stdio or HTTP. This enables any ACP component to provide MCP tools and handle callbacks through the existing ACP connection, without spawning separate processes or managing additional transports. + +## Status quo + +> How do things work today and what problems does this cause? Why would we change things? + +MCP (Model Context Protocol) defines how agents interact with tool servers. MCP has two fundamental limitations in its current form: + +**The agent controls the MCP server lifecycle.** The agent decides when to spawn MCP servers and when to invoke tools. Clients and proxies that want to provide tools must configure the agent to use them - they can't inject tools dynamically based on conversation context or project state. + +**MCP servers must be separate binaries.** With stdio transport, MCP servers run as child processes. With HTTP transport, they run as separate services. There's no way to have a unified component that both injects context into prompts *and* provides an MCP server running in the same address space. This forces artificial separation between "things that modify prompts" and "things that provide tools." + +These limitations create friction for components that want to provide tools dynamically: + +**For clients**: A client that wants to inject project-specific tools into an agent session must either: +- Spawn a separate MCP server process and configure the agent to connect to it +- Hope the agent has built-in support for the specific tools needed + +**For proxies**: A proxy that intercepts agent communication and wants to provide context-aware tools faces the same challenge - it must spawn and manage a separate process just to handle MCP callbacks. + +**For embedded scenarios**: WebAssembly-based components or sandboxed environments may not be able to spawn child processes at all, making stdio transport impossible. + +The result is that providing MCP tools requires process management overhead, even when the tool logic lives in a component that's already connected via ACP. + +## What we propose to do about it + +> What are you proposing to improve the situation? + +We propose adding `"acp"` as a new MCP transport type. When an ACP component (client or proxy) adds an MCP server with ACP transport to a session, tool invocations for that server are routed back through the ACP channel to the component that provided it. + +This enables patterns like: + +- A **client** that injects project-aware tools into every session and handles callbacks directly +- An **intermediary** that adds context-aware tools based on the conversation state +- A **bridge** that translates ACP-transport MCP servers to stdio for agents that don't support native ACP transport + +### How it works + +When a component adds an MCP server to a `session/new` request, it can specify `"transport": "acp"` with a UUID that identifies the server: + +```json +{ + "tools": { + "mcpServers": { + "project-tools": { + "transport": "acp", + "uuid": "550e8400-e29b-41d4-a716-446655440000" + } + } + } +} +``` + +The UUID is generated by the component providing the MCP server. When the agent invokes tools from this server, the messages are routed back through ACP to the component that owns that UUID. Agents advertise their ability to handle ACP transport via `mcpCapabilities.acp` in their `InitializeResponse`. + +### Message flow example + +```mermaid +sequenceDiagram + participant Client + participant Agent + + Client->>Agent: session/new (with ACP-transport MCP server) + Agent-->>Client: session created + + Client->>Agent: prompt ("analyze this codebase") + + Note over Agent: Agent decides to use the tool + Agent->>Client: mcp/connect (acpUrl: "acp:") + Client-->>Agent: connectionId: "conn-1" + + Agent->>Client: mcp/message (list_files tool call) + Client-->>Agent: file listing results + + Agent-->>Client: response using tool results + + Agent->>Client: mcp/disconnect (connectionId: "conn-1") +``` + +## Shiny future + +> How will things play out once this feature exists? + +### Seamless tool injection + +Components can provide tools without any process management. A Rust development environment could inject cargo-aware tools, a cloud IDE could inject deployment tools, and a security scanner could inject vulnerability checking - all through the same ACP connection they're already using. + +### WebAssembly-based tooling + +Components running in sandboxed environments (like WASM) can provide MCP tools without needing filesystem or process spawning capabilities. The ACP channel is their only interface, and that's sufficient. + +### Transparent bridging + +For agents that don't natively support ACP transport, intermediaries can transparently bridge: accepting MCP-over-ACP from clients and spawning stdio-based MCP servers that the agent can use normally. This provides backwards compatibility while allowing the ecosystem to adopt ACP transport incrementally. + +## Implementation details and plan + +> Tell me more about your implementation. What is your detailed implementation plan? + +### Capability advertising + +Agents advertise MCP-over-ACP support via the `mcpCapabilities` field in their `InitializeResponse`. This extends the existing MCP capabilities structure: + +```json +{ + "capabilities": { + "mcpCapabilities": { + "http": false, + "sse": false, + "acp": true + } + } +} +``` + +When `mcpCapabilities.acp` is `true`, the agent can handle MCP servers declared with `"transport": "acp"` natively - it will send `mcp/connect`, `mcp/message`, and `mcp/disconnect` messages through the ACP channel. + +Clients don't need to advertise anything - they simply check the agent's capabilities to determine whether bridging is needed. + +**Bridging intermediaries**: An intermediary that provides bridging can present `mcpCapabilities.acp: true` to its clients regardless of whether the downstream agent supports it, handling bridging transparently (see [Bridging](#bridging-for-agents-without-native-support) below). + +### MCP transport schema extension + +We extend the MCP JSON schema to include ACP as a transport option: + +```json +{ + "type": "object", + "properties": { + "transport": { + "type": "string", + "enum": ["stdio", "http", "acp"] + } + }, + "allOf": [ + { + "if": { "properties": { "transport": { "const": "acp" } } }, + "then": { + "properties": { + "uuid": { + "type": "string", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + }, + "required": ["uuid"] + } + } + ] +} +``` + +### Message reference + +**Connection lifecycle:** + +```json +// Establish MCP connection +{ + "method": "mcp/connect", + "params": { + "acpUrl": "acp:550e8400-e29b-41d4-a716-446655440000", + "meta": { ... } + } +} +// Response: +{ + "connectionId": "conn-123", + "meta": { ... } +} + +// Close MCP connection +{ + "method": "mcp/disconnect", + "params": { + "connectionId": "conn-123", + "meta": { ... } + } +} +``` + +**MCP message exchange:** + +```json +// Send MCP message (bidirectional - works agent→client or client→agent) +{ + "method": "mcp/message", + "params": { + "connectionId": "conn-123", + "method": "", + "params": { ... }, + "meta": { ... } + } +} +``` + +The inner MCP message fields (`method`, `params`) are flattened into the params object. Whether the wrapped message is a request or notification is determined by the presence of an `id` field in the outer JSON-RPC envelope, following JSON-RPC conventions. + +### Routing by UUID + +The `acpUrl` in `mcp/connect` uses the format `acp:`. This UUID was provided by the component when it declared the MCP server in `session/new`. The receiving side uses this UUID to route messages to the correct handler. + +When a component provides multiple MCP servers in a single session, each gets a unique UUID, enabling proper message routing. + +### Connection multiplexing + +Multiple connections to the same MCP server are supported - each `mcp/connect` returns a unique `connectionId`. This allows scenarios where an agent opens multiple concurrent connections to the same tool server. + +### Bridging for agents without native support + +Not all agents will support MCP-over-ACP natively. To maintain compatibility, it is possible to write a bridge that translates ACP-transport MCP servers to transports the agent does support. + +**Bridging approaches:** + +- **Stdio shim**: Spawn a small shim process that the agent connects to via stdio. The shim relays MCP messages to/from the ACP channel. This is the most compatible approach since all MCP-capable agents support stdio. + +- **HTTP bridge**: Run a local HTTP server that the agent connects to. MCP messages are relayed to/from the ACP channel. This works for agents that prefer HTTP transport. + +**How bridging works:** + +When a client provides an MCP server with `"transport": "acp"`, and the agent doesn't advertise `mcpCapabilities.acp: true`, a bridge can: + +1. Rewrite the MCP server declaration in `session/new` to use stdio or HTTP transport +2. Spawn the appropriate shim process or HTTP server +3. Relay messages between the shim and the ACP channel + +From the agent's perspective, it's talking to a normal stdio/HTTP MCP server. From the client's perspective, it's handling MCP-over-ACP messages. The bridge handles the translation transparently. + +```mermaid +sequenceDiagram + participant Client + participant Bridge + participant Shim as Stdio Shim + participant Agent + + Note over Bridge: Agent doesn't support mcpCapabilities.acp + Client->>Bridge: session/new (MCP server with acp transport) + Bridge->>Agent: session/new (MCP server with stdio transport) + Note over Bridge: Spawns shim for bridging + + Agent->>Shim: MCP tool call (stdio) + Shim->>Bridge: relay + Bridge->>Client: mcp/message + Client-->>Bridge: tool result + Bridge-->>Shim: relay + Shim-->>Agent: MCP response (stdio) +``` + +A first implementation of this bridging exists in the `sacp-conductor` crate, part of the proposed new version of the [ACP Rust SDK](https://github.com/anthropics/rust-sdk). + +## Frequently asked questions + +> What questions have arisen over the course of authoring this document or during subsequent discussions? + +### Why use UUIDs instead of server names? + +Server names in `mcpServers` are chosen by whoever adds them to the session, and could potentially collide if multiple components add servers. UUIDs provide guaranteed uniqueness and allow the providing component to correlate incoming messages back to the correct session context. + +This also avoids a potential deadlock: some agents don't return the session ID until after MCP servers have been initialized. Using a component-generated UUID avoids any dependency on agent-provided identifiers. + +### How does this relate to proxy chains? + +MCP-over-ACP is a transport mechanism that works independently of proxy chains. However, proxy chains are a natural use case: a proxy can inject MCP servers into sessions it forwards, handle the tool callbacks, and use the results to enhance its transformations. + +See the [Proxy Chains RFD](./proxy-chains) for details on how MCP-over-ACP enables context-aware tooling. + +### What if the agent doesn't support ACP transport? + +See the [Bridging for agents without native support](#bridging-for-agents-without-native-support) section above. A bridge can transparently translate ACP-transport MCP servers to stdio or HTTP for agents that don't advertise `mcpCapabilities.acp` support. + +### Can agents provide MCP servers to clients? + +The protocol is symmetric - there's nothing preventing an agent from declaring MCP servers with ACP transport that the client would connect to. However, this is an unusual pattern; typically clients/proxies provide tools for agents to use, not the reverse. + +### What about security? + +MCP-over-ACP has the same trust model as regular MCP: you're allowing a component to handle tool invocations. The difference is transport, not trust. Components should only add MCP servers from sources they trust, same as with stdio or HTTP transport. + +## Revision history + +Split from proxy-chains RFD to enable independent use of MCP-over-ACP transport by any ACP component, not just proxies. diff --git a/docs/rfds/proxy-chains.mdx b/docs/rfds/proxy-chains.mdx index e83df3f..14453f8 100644 --- a/docs/rfds/proxy-chains.mdx +++ b/docs/rfds/proxy-chains.mdx @@ -36,8 +36,6 @@ Users end up choosing agents not just based on model quality, but on which speci We propose extending ACP to enable creating _proxies_, ACP components that sit between the client and the agent. Because proxies can do anything a client could do, they serve as a kind of "universal extension mechanism" that can subsume AGENTS.md, hooks, MCP servers, etc. -Part of the proxy protocol includes the ability to send and receive MCP messages, enabling a single component to (1) add an MCP server to a session and then (2) handle those MCP tool requests. This effectively creates a way for agents to "callback" to a proxy, enabling rich bidirectional interactions beyond simple message transformation. - Proxies are limited to the customizations exposed by ACP itself, so they would benefit from future ACP extensions like mechanisms to customize system prompts. However, they can already handle the majority of common extension use cases through message interception and transformation. ### Proxying in theory @@ -56,17 +54,15 @@ flowchart LR (As described in the "Proxying in Practice" section, proxies in our design do not actually communicate directly with their successor, but instead use a central conductor. For the purposes of explaining the protocol, however, this section will continue to show diagrams "as if" proxies were in direct communication with each other.) -### Proxying MCP requests through ACP - -When proxing a `session/new` request, proxies can add MCP servers using a new transport type, `"acp"`. When agents invoke an MCP server that uses `"acp"` transport, the MCP requests are sent through the ACP channel. (To accommodate existing agents, our conductor will automatically bridge MCP servers using `"acp"` transport to use `"stdio"` transport.) +### Enhanced capabilities with MCP-over-ACP -Leveraging `"acp"` transport allows a single ACP proxy to do all of the following: +Proxies become even more powerful when combined with [MCP-over-ACP transport](./mcp-over-acp). A proxy can add MCP servers to sessions it forwards, then handle tool callbacks when the agent invokes those tools. This enables a single proxy to: 1. **Add context** by analyzing the project and injecting relevant documentation 2. **Provide tools** via MCP server that understand the injected context 3. **Handle callbacks** when the agent uses those tools, with full awareness of the conversation state -**Example Flow (Idealized)**: Context Proxy (with MCP server) → Filter Proxy → Agent +**Example Flow**: Context Proxy (with MCP server) → Filter Proxy → Agent ```mermaid sequenceDiagram @@ -99,9 +95,9 @@ sequenceDiagram P1-->>Client: final response ``` -This demonstrates how proxy chains enable rich, context-aware tooling that would be difficult to achieve with traditional MCP servers alone. +This demonstrates how proxy chains combined with MCP-over-ACP enable rich, context-aware tooling that would be difficult to achieve with traditional MCP servers alone. -### Proxing in practice: the role of the conductor +### Proxying in practice: the role of the conductor To allow for proxy isolation, our design does not have proxies communicate directly with their successor in the chain. Instead, there is a central conductor component that handles routing messages between components. @@ -149,15 +145,15 @@ sequenceDiagram ## Shiny future -> How will things will play out once this feature exists? +> How will things play out once this feature exists? -### User Experience and Editor Integration +### User experience and editor integration We expect editors to expose the ability to install proxies in the same way they currently support adding MCP servers - in fact, the distinction probably doesn't matter to users. Both are "extensions" that add capabilities to their AI workflow. When proxies are installed, editors would not start the agent directly, but instead invoke the conductor with the configured proxy chain. From the user's perspective, they're just getting enhanced agent capabilities - the proxy chain architecture remains transparent. -### Language-Specific Proxy Ecosystems +### Language-specific proxy ecosystems The monolithic nature of agent development has meant that most of the "action" happens within agents. We wish to invert this, with agents trending towards simple agentic loops, and the creativity being pushed outwards into the broader ecosystem. @@ -165,10 +161,6 @@ The Symposium project is one example exploring this concept, with a focus on Rus Symposium aims to become the standard "Rust ACP experience" by providing both core Rust tooling and a framework for Rust libraries to contribute their own proxy components. -### Standardized IDE Capabilities - -Proxy infrastructure could also enable editors to expose standardized IDE capabilities (diagnostics, file system access, terminal APIs) to agents via MCP servers provided by proxies. This keeps the core ACP protocol focused on agent communication while allowing rich IDE integration through the proxy layer. - ```mermaid flowchart LR Client[Client] @@ -191,11 +183,15 @@ flowchart LR Symposium --> Agent ``` +### Standardized IDE capabilities + +Proxy infrastructure could also enable editors to expose standardized IDE capabilities (diagnostics, file system access, terminal APIs) to agents via MCP servers provided by proxies. This keeps the core ACP protocol focused on agent communication while allowing rich IDE integration through the proxy layer. + ## Implementation details and plan > Tell me more about your implementation. What is your detailed implementation plan? -### Component Roles +### Component roles ACP defines client and agent as superroles, each with two specializations: @@ -218,7 +214,7 @@ classDiagram class Conductor { +manages proxy chain - +sends successor/MCP messages + +sends successor messages } class TerminalAgent { @@ -229,7 +225,7 @@ classDiagram class Proxy { +forwards to successor - +sends successor/MCP messages + +sends successor messages } Client <|-- TerminalClient @@ -262,15 +258,7 @@ flowchart TB TA -->|terminal agent| C ``` -### Capability Reference - -**MCP-over-ACP Transport** (`"mcp_acp_transport": true`) - -- Indicates a component can handle MCP messages with `"transport": "acp"` -- Must handle `mcp/connect`, `mcp/message`, and `mcp/disconnect` messages -- Enables seamless MCP server provision and tool callbacks - -### Proxy Initialization Protocol +### Proxy initialization protocol Components discover their role from the initialization method they receive: @@ -293,17 +281,13 @@ The `proxy/initialize` request has the same parameters as `initialize` and expec Note: A conductor can be configured to run in either terminal mode (expecting `initialize`) or proxy mode (expecting `proxy/initialize`), enabling nested proxy chains. -### MCP Transport Capability Initialization - -MCP transport capability negotiation follows different rules depending on the relationship: - -- **Conductor ↔ proxy initialization**: The conductor MUST offer `"mcp_acp_transport": true` in the `proxy/initialize` request. The proxy MUST respond with `"mcp_acp_transport": true` - all proxies are required to support MCP servers that use ACP transport. +### MCP-over-ACP support -- **Terminal client ↔ terminal agent initialization**: The client MAY offer `"mcp_acp_transport": true` in the `initialize` request. The agent MAY respond with `"mcp_acp_transport": true` if it supports native MCP-over-ACP transport. +Proxies that provide MCP servers use the [MCP-over-ACP transport](./mcp-over-acp) mechanism. The conductor always advertises `mcpCapabilities.acp: true` to proxies and handles bridging for agents that don't support native ACP transport. -Note: Because a conductor MUST offer `"mcp_acp_transport"` to each proxy but terminal agents are not obligated to support it, the conductor MUST bridge MCP servers using ACP transport to alternative transport methods (such as stdio) when the agent lacks native support. +All proxies MUST support MCP-over-ACP. When the conductor sends `proxy/initialize`, proxies should be prepared to handle `mcp/connect`, `mcp/message`, and `mcp/disconnect` messages for any MCP servers they provide. -### Message Reference +### Message reference **Initialization:** @@ -317,7 +301,7 @@ Note: Because a conductor MUST offer `"mcp_acp_transport"` to each proxy but ter Both methods use the same parameters as the standard ACP `InitializeRequest` and expect a standard `InitializeResponse`. -**Proxy Messages:** +**Proxy messages:** ```json // Proxy sends message to successor, or conductor delivers message from successor @@ -334,108 +318,11 @@ Both methods use the same parameters as the standard ACP `InitializeRequest` and The inner message fields (`method`, `params`) are flattened into the params object. Whether the wrapped message is a request or notification is determined by the presence of an `id` field in the outer JSON-RPC envelope, following JSON-RPC conventions. -**MCP Messages:** - -```json -// Establish MCP connection -{"method": "mcp/connect", "params": {"acpUrl": "acp:", "meta": {...}}} -// Response: {"connectionId": "", "meta": {...}} - -// Send MCP message (bidirectional - agent↔proxy) -{ - "method": "mcp/message", - "params": { - "connectionId": "", - "method": "", - "params": , - "meta": { ... } - } -} - -// Close MCP connection -{"method": "mcp/disconnect", "params": {"connectionId": "", "meta": {...}}} -``` - -The `connectionId` is obtained from the `mcp/connect` response. The inner MCP message fields are flattened into the params object, similar to `proxy/successor`. The `acpUrl` in the connect request identifies which proxy component owns the MCP server. - -### MCP Transport Extension - -We extend the MCP JSON schema to support ACP as a new transport type alongside existing stdio and HTTP transports. - -**Extended MCP Server Schema:** - -```json -{ - "type": "object", - "properties": { - "transport": { - "type": "string", - "enum": ["stdio", "http", "acp"] - } - }, - "allOf": [ - { - "if": { - "properties": { "transport": { "const": "stdio" } } - }, - "then": { - "properties": { - "command": { "type": "string" }, - "args": { "type": "array", "items": { "type": "string" } } - }, - "required": ["command"] - } - }, - { - "if": { - "properties": { "transport": { "const": "http" } } - }, - "then": { - "properties": { - "url": { "type": "string" }, - "headers": { "type": "object" } - }, - "required": ["url"] - } - }, - { - "if": { - "properties": { "transport": { "const": "acp" } } - }, - "then": { - "properties": { - "uuid": { - "type": "string", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" - } - }, - "required": ["uuid"] - } - } - ] -} -``` - -**Example ACP Transport Declaration:** - -```json -{ - "tools": { - "mcpServers": { - "filesystem": { - "transport": "acp", - "uuid": "550e8400-e29b-41d4-a716-446655440000" - } - } - } -} -``` - -### Examples (Non-normative) +### Examples (non-normative) The following sequence diagrams illustrate common proxy chain scenarios for implementers. -#### Initialization of a 4-Component Proxy Chain +#### Initialization of a 4-component proxy chain This shows the initialization flow for: Terminal Client → Conductor → Context Proxy → Tool Filter Proxy → Terminal Agent @@ -452,27 +339,27 @@ sequenceDiagram TC->>C: initialize Note over C: Conductor spawns proxy components - C->>P1: proxy/initialize (mcp_acp_transport: true) + C->>P1: proxy/initialize Note over P1: Proxy forwards to successor P1->>C: proxy/successor (initialize) Note over C: Conductor sends proxy/initialize to next proxy - C->>P2: proxy/initialize (mcp_acp_transport: true) + C->>P2: proxy/initialize Note over P2: Proxy forwards to successor P2->>C: proxy/successor (initialize) Note over C: Conductor sends initialize to final agent - C->>TA: initialize (mcp_acp_transport: true) + C->>TA: initialize - TA-->>C: InitializeResponse (may include mcp_acp_transport: true) + TA-->>C: InitializeResponse (mcpCapabilities.acp: true/false) C-->>P2: proxy/successor (InitializeResponse) - P2-->>C: InitializeResponse (mcp_acp_transport: true) + P2-->>C: InitializeResponse C-->>P1: proxy/successor (InitializeResponse) - P1-->>C: InitializeResponse (mcp_acp_transport: true) + P1-->>C: InitializeResponse Note over C: Conductor acts as terminal agent to client C-->>TC: InitializeResponse @@ -480,7 +367,7 @@ sequenceDiagram Note over TC,TA: Proxy chain initialized and ready ``` -#### Context-Providing MCP Server with Session Notifications +#### Context-providing proxy with session notifications This example shows how a proxy can handle initialization and forward session notifications. Sparkle (a collaborative AI framework) runs an embodiment sequence during session creation. @@ -535,21 +422,13 @@ Earlier designs used a `"proxy": true` capability in the `InitializeRequest` and Using a distinct method makes the contract clearer: if you receive `proxy/initialize`, you're a proxy with a successor; if you receive `initialize`, you're the terminal agent. There's no capability dance, no risk of misconfiguration, and components know their role immediately from the method name. -### How do proxies correlate MCP requests with sessions? - -When a proxy adds an MCP server to a `session/new` request, it uses a fresh ACP-ID (the `uuid` field) that the proxy controls. When MCP-over-ACP messages arrive with that ACP-ID, the proxy can correlate them back to the originating session. - -This approach avoids a potential deadlock: some agents don't return the `session-id` until after MCP servers have been initialized. If proxies needed the `session-id` to handle MCP requests during initialization, they would be stuck waiting for a value that depends on their response. - -By using a fresh ACP-ID per session instead, proxies maintain full correlation capability without circular dependencies. The proxy knows which session spawned which ACP-ID, so it can always map MCP requests back to their originating session context. - ### How do proxies subsume existing agent extension mechanisms? Because proxies sit between the client and agent, they can replicate the functionality of existing extension mechanisms: - **AGENTS.md files**: Proxies can inject context and instructions into prompts before they reach the agent - **Claude Code plugins/skills**: Proxies can add contextual data for available skills and provide MCP resources with detailed skill instructions that are provided on-demand when requested by the agent -- **MCP servers**: Proxies can provide tools via the MCP-over-ACP protocol and handle tool callbacks +- **MCP servers**: Proxies can provide tools via [MCP-over-ACP](./mcp-over-acp) and handle tool callbacks - **Subagents**: Proxies can create "subagents" by initiating new sessions and coordinating between multiple agent instances - **Hooks and steering files**: Proxies can modify conversation flow by intercepting requests and responses - **System prompt customization**: Proxies can switch between predefined session modes or prepend system messages to prompts @@ -564,34 +443,6 @@ For example, proxies cannot directly modify an agent's system prompt or context This is actually a feature - it ensures that proxy-based extensions remain portable across different agent implementations and don't rely on agent-specific internals. -### How does the standard conductor implementation work? - -The `sacp-conductor` reference implementation can form trees of proxy chains. It can be configured to run in proxy mode (expecting `proxy/initialize`) or terminal mode (expecting `initialize`). When the last proxy in its managed chain sends a message to its successor, the conductor forwards that message to its own parent conductor (if in proxy mode) or to the final agent (if in terminal mode). - -This enables hierarchical structures like: - -``` -client → conductor1 → final-agent - ↓ manages - proxy-a → conductor2 → proxy-d - ↓ manages - proxy-b → proxy-c -``` - -The conductor handles process management, capability negotiation, and message routing, but these are implementation details - the protocol only specifies the message formats and capability requirements. - -### What's the current implementation status? - -A prototype version of this proposal has been implemented and is available on crates.io as the crates - -- `sacp` -- base ACP protocol SDK - - `sacp-tokio` -- adds specific utilities for use with the `tokio` runtime -- `sacp-proxy` -- extensions for implementing a proxy - - `sacp-rmcp` -- adds specific proxy extension traits for bridging to the rmcp crate -- sacp-conductor -- reference conductor implementation - -The canonical sources for those crates is currently the [symposium-dev/symposium-acp] repository. However, copies have been upstreamed to the [agentclientprotocol/rust-sdk](https://github.com/agentclientprotocol/rust-sdk/tree/main/src/sacp-conductor) repository and, if and when this RFD is accepted, that will become the canonical home. - ### Why not just cascade ACP commands without protocol changes? One alternative is to make proxies be ordinary agents that internally create and manage their successors. This works (HTTP proxies operate this way) but requires each proxy to understand the full chain and know how to start its successors. @@ -614,6 +465,22 @@ This supports running proxies as isolated WebAssembly components with minimal ca The conductor handles process management, capability negotiation, and message routing, allowing proxies to focus on transformation logic. +### How does the standard conductor implementation work? + +The `sacp-conductor` reference implementation can form trees of proxy chains. It can be configured to run in proxy mode (expecting `proxy/initialize`) or terminal mode (expecting `initialize`). When the last proxy in its managed chain sends a message to its successor, the conductor forwards that message to its own parent conductor (if in proxy mode) or to the final agent (if in terminal mode). + +This enables hierarchical structures like: + +``` +client → conductor1 → final-agent + ↓ manages + proxy-a → conductor2 → proxy-d + ↓ manages + proxy-b → proxy-c +``` + +The conductor handles process management, capability negotiation, and message routing, but these are implementation details - the protocol only specifies the message formats and capability requirements. + ### What about security concerns with proxy chains? Proxy components can intercept and modify all communication, so trust is essential - similar to installing any software. Users are responsible for the components they choose to run. @@ -666,20 +533,19 @@ The current design assumes a linear chain where each proxy has a single successo When `peer` is omitted, the message goes to the default successor (backwards compatible with the current linear chain model). When present, it specifies which peer the message is intended for. The `proxy/initialize` response could be extended to enumerate available peers, enabling proxies to discover and coordinate between multiple downstream components. -### Why are MCP and proxy messages separate instead of unified as "peers"? - -The `mcp/*` messages and `proxy/successor` are structurally similar - both wrap an inner message and route it to a destination. A unified design might use `peer/connect`, `peer/message`, and `peer/disconnect` for everything, where the successor is just another peer (with `peer: "successor"`). - -We kept them separate because successors and MCP servers have different lifecycle semantics: - -- **Successors** are implicit and permanent. When a proxy receives `proxy/initialize`, its successor already exists and will exist for the proxy's entire lifetime. The proxy doesn't need to think about connecting or disconnecting - it just forwards messages. This simplicity is intentional: proxies shouldn't need to manage successor lifecycle, and the conductor doesn't need to handle dynamic successor creation. +### What's the current implementation status? -- **MCP servers** require explicit connection management. When the conductor bridges an MCP server via stdio, it spawns a new process. Multiple connections to the same MCP server are possible (different connection IDs). The `mcp/connect` and `mcp/disconnect` lifecycle is necessary because these connections are dynamic and multiplexed. +A prototype version of this proposal has been implemented and is available on crates.io as the crates: -A unified `peer/*` approach would require proxies to explicitly connect to their successor on startup, implying more generality than the current design intends. It would also require the conductor to support starting successors multiple times, which adds complexity for a capability we don't currently need. +- `sacp` -- base ACP protocol SDK + - `sacp-tokio` -- adds specific utilities for use with the `tokio` runtime +- `sacp-proxy` -- extensions for implementing a proxy + - `sacp-rmcp` -- adds specific proxy extension traits for bridging to the rmcp crate +- `sacp-conductor` -- reference conductor implementation -That said, if M:N topologies become common, revisiting this unification might make sense, and the MCP server protocol could be a model for more general "peers". +The canonical sources for those crates is currently the [symposium-dev/symposium-acp] repository. However, copies have been upstreamed to the [agentclientprotocol/rust-sdk](https://github.com/agentclientprotocol/rust-sdk/tree/main/src/sacp-conductor) repository and, if and when this RFD is accepted, that will become the canonical home. ## Revision history -Initial draft based on working implementation in symposium-acp repository. +- Initial draft based on working implementation in symposium-acp repository. +- Split MCP-over-ACP transport into [separate RFD](./mcp-over-acp) to enable independent use by any ACP component. From aaa5331d8ab55968b2c6dbd6d7e6cacc96a83af3 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 7 Jan 2026 18:27:56 -0500 Subject: [PATCH 2/6] docs(rfd): Refine MCP-over-ACP RFD - Rewrite status quo to clarify ACP/MCP relationship (front/behind framing) - Add capability advertisement flow to 'How it works' section - Add 'Bridging and compatibility' subsection early in the doc - Rename uuid field to id (no format restriction) - Rename acpUrl to acpId (simpler, no acp: prefix) - Link mcpCapabilities to schema docs --- docs/rfds/mcp-over-acp.mdx | 65 ++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/docs/rfds/mcp-over-acp.mdx b/docs/rfds/mcp-over-acp.mdx index 445ab4a..cf96fd0 100644 --- a/docs/rfds/mcp-over-acp.mdx +++ b/docs/rfds/mcp-over-acp.mdx @@ -14,23 +14,11 @@ Add support for MCP servers that communicate over ACP channels instead of stdio > How do things work today and what problems does this cause? Why would we change things? -MCP (Model Context Protocol) defines how agents interact with tool servers. MCP has two fundamental limitations in its current form: +ACP and MCP each solve different halves of the problem of interacting with an agent. ACP stands in "front" of the agent, managing sessions, sending prompts, and receiving responses. MCP stands "behind" the agent, providing tools that the agent can use to do its work. -**The agent controls the MCP server lifecycle.** The agent decides when to spawn MCP servers and when to invoke tools. Clients and proxies that want to provide tools must configure the agent to use them - they can't inject tools dynamically based on conversation context or project state. +Many applications would benefit from being able to be both "in front" of the agent and "behind" it. This would allow a client, for example, to create custom MCP tools that are tailored to a specific request and which live in the client's address space. -**MCP servers must be separate binaries.** With stdio transport, MCP servers run as child processes. With HTTP transport, they run as separate services. There's no way to have a unified component that both injects context into prompts *and* provides an MCP server running in the same address space. This forces artificial separation between "things that modify prompts" and "things that provide tools." - -These limitations create friction for components that want to provide tools dynamically: - -**For clients**: A client that wants to inject project-specific tools into an agent session must either: -- Spawn a separate MCP server process and configure the agent to connect to it -- Hope the agent has built-in support for the specific tools needed - -**For proxies**: A proxy that intercepts agent communication and wants to provide context-aware tools faces the same challenge - it must spawn and manage a separate process just to handle MCP callbacks. - -**For embedded scenarios**: WebAssembly-based components or sandboxed environments may not be able to spawn child processes at all, making stdio transport impossible. - -The result is that providing MCP tools requires process management overhead, even when the tool logic lives in a component that's already connected via ACP. +The only way to combine ACP and MCP today is to use some sort of "backdoor", such as opening an HTTP port for the agent to connect to or providing a binary that communicates with IPC. This is inconvenient to implement but also means that clients cannot be properly abstracted and sandboxed, as some of the communication with the agent is going through side channels. Imagine trying to host an ACP component (client, agent, or [agent extension](./proxy-chains.mdx)) that runs in a WASM sandbox or even on another machine: for that to work, the ACP protocol has to encompass all of the relevant interactions so that messages can be transmitted properly. ## What we propose to do about it @@ -41,12 +29,12 @@ We propose adding `"acp"` as a new MCP transport type. When an ACP component (cl This enables patterns like: - A **client** that injects project-aware tools into every session and handles callbacks directly -- An **intermediary** that adds context-aware tools based on the conversation state +- An **[agent extension](./proxy-chains.mdx)** that adds context-aware tools based on the conversation state - A **bridge** that translates ACP-transport MCP servers to stdio for agents that don't support native ACP transport ### How it works -When a component adds an MCP server to a `session/new` request, it can specify `"transport": "acp"` with a UUID that identifies the server: +When the client connects, the agent advertises MCP-over-ACP support via `mcpCapabilities.acp` in its `InitializeResponse`. If supported, the client can add MCP servers to a `session/new` request with `"transport": "acp"` and an `id` that identifies the server: ```json { @@ -54,14 +42,22 @@ When a component adds an MCP server to a `session/new` request, it can specify ` "mcpServers": { "project-tools": { "transport": "acp", - "uuid": "550e8400-e29b-41d4-a716-446655440000" + "id": "550e8400-e29b-41d4-a716-446655440000" } } } } ``` -The UUID is generated by the component providing the MCP server. When the agent invokes tools from this server, the messages are routed back through ACP to the component that owns that UUID. Agents advertise their ability to handle ACP transport via `mcpCapabilities.acp` in their `InitializeResponse`. +The `id` is generated by the component providing the MCP server. + +When the agent connects to the MCP server, an `mcp/connect` message is sent with the MCP server's `id`. This returns a fresh `connectionId`. MCP messages are then sent back and forth using `mcp/message` requests. Finally, `mcp/disconnect` signals that the connection is closing. + +### Bridging and compatibility + +Existing agents don't support ACP transport for MCP servers. To bridge this gap, a wrapper component can translate between ACP-transport MCP servers and the stdio/HTTP transports that agents already support. The wrapper spawns shim processes or HTTP servers that the agent connects to normally, then relays messages to/from the ACP channel. + +We've implemented this bridging as part of the conductor described in the [Proxy Chains RFD](./proxy-chains). The conductor always advertises `mcpCapabilities.acp: true` to its clients, handling the translation transparently regardless of whether the downstream agent supports native ACP transport. ### Message flow example @@ -76,7 +72,7 @@ sequenceDiagram Client->>Agent: prompt ("analyze this codebase") Note over Agent: Agent decides to use the tool - Agent->>Client: mcp/connect (acpUrl: "acp:") + Agent->>Client: mcp/connect (acpId: "") Client-->>Agent: connectionId: "conn-1" Agent->>Client: mcp/message (list_files tool call) @@ -101,7 +97,7 @@ Components running in sandboxed environments (like WASM) can provide MCP tools w ### Transparent bridging -For agents that don't natively support ACP transport, intermediaries can transparently bridge: accepting MCP-over-ACP from clients and spawning stdio-based MCP servers that the agent can use normally. This provides backwards compatibility while allowing the ecosystem to adopt ACP transport incrementally. +For agents that don't natively support ACP transport, intermediaries can transparently bridge: accepting MCP-over-ACP from clients and spawning stdio- or HTTP-based MCP servers that the agent can use normally. This provides backwards compatibility while allowing the ecosystem to adopt ACP transport incrementally. ## Implementation details and plan @@ -109,7 +105,7 @@ For agents that don't natively support ACP transport, intermediaries can transpa ### Capability advertising -Agents advertise MCP-over-ACP support via the `mcpCapabilities` field in their `InitializeResponse`. This extends the existing MCP capabilities structure: +Agents advertise MCP-over-ACP support via the [`mcpCapabilities`](/protocol/schema#mcpcapabilities) field in their `InitializeResponse`. We propose adding an `acp` field to this existing structure: ```json { @@ -147,12 +143,11 @@ We extend the MCP JSON schema to include ACP as a transport option: "if": { "properties": { "transport": { "const": "acp" } } }, "then": { "properties": { - "uuid": { - "type": "string", - "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + "id": { + "type": "string" } }, - "required": ["uuid"] + "required": ["id"] } } ] @@ -168,7 +163,7 @@ We extend the MCP JSON schema to include ACP as a transport option: { "method": "mcp/connect", "params": { - "acpUrl": "acp:550e8400-e29b-41d4-a716-446655440000", + "acpId": "550e8400-e29b-41d4-a716-446655440000", "meta": { ... } } } @@ -205,11 +200,11 @@ We extend the MCP JSON schema to include ACP as a transport option: The inner MCP message fields (`method`, `params`) are flattened into the params object. Whether the wrapped message is a request or notification is determined by the presence of an `id` field in the outer JSON-RPC envelope, following JSON-RPC conventions. -### Routing by UUID +### Routing by ID -The `acpUrl` in `mcp/connect` uses the format `acp:`. This UUID was provided by the component when it declared the MCP server in `session/new`. The receiving side uses this UUID to route messages to the correct handler. +The `acpId` in `mcp/connect` matches the `id` that was provided by the component when it declared the MCP server in `session/new`. The receiving side uses this `id` to route messages to the correct handler. -When a component provides multiple MCP servers in a single session, each gets a unique UUID, enabling proper message routing. +When a component provides multiple MCP servers in a single session, each gets a unique `id`, enabling proper message routing. ### Connection multiplexing @@ -261,11 +256,11 @@ A first implementation of this bridging exists in the `sacp-conductor` crate, pa > What questions have arisen over the course of authoring this document or during subsequent discussions? -### Why use UUIDs instead of server names? +### Why use a separate `id` instead of server names? -Server names in `mcpServers` are chosen by whoever adds them to the session, and could potentially collide if multiple components add servers. UUIDs provide guaranteed uniqueness and allow the providing component to correlate incoming messages back to the correct session context. +Server names in `mcpServers` are chosen by whoever adds them to the session, and could potentially collide if multiple components add servers. A component-generated `id` provides guaranteed uniqueness and allows the providing component to correlate incoming messages back to the correct session context. -This also avoids a potential deadlock: some agents don't return the session ID until after MCP servers have been initialized. Using a component-generated UUID avoids any dependency on agent-provided identifiers. +This also avoids a potential deadlock: some agents don't return the session ID until after MCP servers have been initialized. Using a component-generated `id` avoids any dependency on agent-provided identifiers. ### How does this relate to proxy chains? @@ -277,10 +272,6 @@ See the [Proxy Chains RFD](./proxy-chains) for details on how MCP-over-ACP enabl See the [Bridging for agents without native support](#bridging-for-agents-without-native-support) section above. A bridge can transparently translate ACP-transport MCP servers to stdio or HTTP for agents that don't advertise `mcpCapabilities.acp` support. -### Can agents provide MCP servers to clients? - -The protocol is symmetric - there's nothing preventing an agent from declaring MCP servers with ACP transport that the client would connect to. However, this is an unusual pattern; typically clients/proxies provide tools for agents to use, not the reverse. - ### What about security? MCP-over-ACP has the same trust model as regular MCP: you're allowing a component to handle tool invocations. The difference is transport, not trust. Components should only add MCP servers from sources they trust, same as with stdio or HTTP transport. From c24413ac08b12b84e947281f69763e47cd9f0d6b Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 7 Jan 2026 18:43:15 -0500 Subject: [PATCH 3/6] docs(rfd): Refine proxy-chains RFD - Rename to 'Agent Extensions via ACP Proxies' - Lead with agent extensions concept, proxies as mechanism - Tighten status quo section (~50% shorter) - Move MCP-over-ACP content to FAQ with bridging explanation --- docs/rfds/proxy-chains.mdx | 97 +++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 53 deletions(-) diff --git a/docs/rfds/proxy-chains.mdx b/docs/rfds/proxy-chains.mdx index 14453f8..6709c0d 100644 --- a/docs/rfds/proxy-chains.mdx +++ b/docs/rfds/proxy-chains.mdx @@ -1,5 +1,5 @@ --- -title: "Proxy Chains: Composable Agent Architectures" +title: "Agent Extensions via ACP Proxies" --- Author(s): [nikomatsakis](https://github.com/nikomatsakis) @@ -8,33 +8,28 @@ Author(s): [nikomatsakis](https://github.com/nikomatsakis) > What are you proposing to change? -Add proxy chain capabilities to ACP that allow components to intercept and transform messages between clients and agents, enabling composable agent architectures where techniques like context injection, tool coordination, and response filtering can be extracted into reusable components. +Enable a universal agent extension mechanism via ACP proxies, components that sit between a client and an agent. Proxies can intercept and transform messages, enabling composable architectures where techniques like context injection, tool coordination, and response filtering can be extracted into reusable components. ## Status quo > How do things work today and what problems does this cause? Why would we change things? -The AI agent ecosystem has developed a wide variety of extension mechanisms: AGENTS.md files, Claude Code plugins and skills, rules and steering files, hooks, MCP servers, etc. Of these, only MCP servers and AGENTS.md files have achieved any standardization across the ecosystem. - -The popularity of MCP servers demonstrates a clear desire for portable extensions that work across different clients and agents. Many MCP servers come with instructions like "add this text to your context to help the agent use the MCP server correctly," showing that developers want their tools to work seamlessly without manual configuration. +The AI agent ecosystem has developed many extension mechanisms: AGENTS.md files, Claude Code plugins, rules files, hooks, MCP servers, etc. Of these, only MCP servers have achieved real standardization across the ecosystem. However, MCP servers are fundamentally limited because they sit "behind" the agent. They can provide tools and respond to function calls, but they cannot: - **Inject or modify prompts** before they reach the agent - **Add global context** that persists across conversations - **Transform responses** before they reach the user -- **Respond to async events** outside the request-response cycle - **Coordinate between multiple agents** or manage conversation flow -This creates a gap in the ecosystem. Developers want portable, composable extensions, but the only standardized mechanism (MCP) can't handle many common use cases. As a result, valuable techniques like context management, conversation orchestration, and response processing remain locked within individual agent implementations. - -Users end up choosing agents not just based on model quality, but on which specific extensions and capabilities are built in. There's no way to take the context injection from one system, the tool coordination from another, and the response filtering from a third, and combine them with your preferred base model. +As a result, valuable techniques like context management and response processing remain locked within individual agent implementations, with no way to extract and reuse them across different agents. ## What we propose to do about it > What are you proposing to improve the situation? -We propose extending ACP to enable creating _proxies_, ACP components that sit between the client and the agent. Because proxies can do anything a client could do, they serve as a kind of "universal extension mechanism" that can subsume AGENTS.md, hooks, MCP servers, etc. +We propose implementing _agent extensions_ via ACP _proxies_, a new kind of component that sits between the client and the agent, forwarding (and potentially altering or introducing) messages. Because proxies can do anything a client could do, they serve as a universal extension mechanism that can subsume AGENTS.md, hooks, MCP servers, etc. Proxies are limited to the customizations exposed by ACP itself, so they would benefit from future ACP extensions like mechanisms to customize system prompts. However, they can already handle the majority of common extension use cases through message interception and transformation. @@ -54,49 +49,6 @@ flowchart LR (As described in the "Proxying in Practice" section, proxies in our design do not actually communicate directly with their successor, but instead use a central conductor. For the purposes of explaining the protocol, however, this section will continue to show diagrams "as if" proxies were in direct communication with each other.) -### Enhanced capabilities with MCP-over-ACP - -Proxies become even more powerful when combined with [MCP-over-ACP transport](./mcp-over-acp). A proxy can add MCP servers to sessions it forwards, then handle tool callbacks when the agent invokes those tools. This enables a single proxy to: - -1. **Add context** by analyzing the project and injecting relevant documentation -2. **Provide tools** via MCP server that understand the injected context -3. **Handle callbacks** when the agent uses those tools, with full awareness of the conversation state - -**Example Flow**: Context Proxy (with MCP server) → Filter Proxy → Agent - -```mermaid -sequenceDiagram - participant Client - participant P1 as Context Proxy - participant P2 as Filter Proxy - participant Agent - - Note over Client: User asks about project structure - Client->>P1: prompt request - - Note over P1: Analyzes project, adds context + filesystem MCP server - P1->>P2: enhanced prompt + filesystem MCP server - - Note over P2: Forwards enhanced prompt - P2->>Agent: prompt with context + tools available - - Note over Agent: Decides to explore project structure - Agent->>P2: mcp/message (list files) - - Note over P2: Forwards tool call back to Context Proxy - P2->>P1: mcp/message (list files) - - Note over P1: Handles tool call with full project context - P1-->>P2: file listing with relevant details - P2-->>Agent: file listing with relevant details - - Agent-->>P2: response using both context and tool results - P2-->>P1: response (potentially filtered) - P1-->>Client: final response -``` - -This demonstrates how proxy chains combined with MCP-over-ACP enable rich, context-aware tooling that would be difficult to achieve with traditional MCP servers alone. - ### Proxying in practice: the role of the conductor To allow for proxy isolation, our design does not have proxies communicate directly with their successor in the chain. Instead, there is a central conductor component that handles routing messages between components. @@ -435,6 +387,45 @@ Because proxies sit between the client and agent, they can replicate the functio The key advantage is that proxy-based extensions work with any ACP-compatible agent without requiring agent-specific integration or modification. +### How do proxies work with MCP servers? + +Proxies can provide MCP servers via [MCP-over-ACP transport](./mcp-over-acp), enabling a single proxy to add context, provide tools, and handle callbacks with full awareness of the conversation state. + +The conductor always advertises `mcpCapabilities.acp: true` to proxies, regardless of whether the downstream agent supports it natively. When the agent doesn't support ACP transport, the conductor handles bridging transparently - spawning stdio shims or HTTP servers that the agent connects to normally, then relaying messages to/from the proxy's ACP channel. + +This means proxy authors don't need to worry about agent compatibility - they implement MCP-over-ACP, and the conductor handles the rest. + +```mermaid +sequenceDiagram + participant Client + participant P1 as Context Proxy + participant P2 as Filter Proxy + participant Agent + + Note over Client: User asks about project structure + Client->>P1: prompt request + + Note over P1: Analyzes project, adds context + filesystem MCP server + P1->>P2: enhanced prompt + filesystem MCP server + + Note over P2: Forwards enhanced prompt + P2->>Agent: prompt with context + tools available + + Note over Agent: Decides to explore project structure + Agent->>P2: mcp/message (list files) + + Note over P2: Forwards tool call back to Context Proxy + P2->>P1: mcp/message (list files) + + Note over P1: Handles tool call with full project context + P1-->>P2: file listing with relevant details + P2-->>Agent: file listing with relevant details + + Agent-->>P2: response using both context and tool results + P2-->>P1: response (potentially filtered) + P1-->>Client: final response +``` + ### Are there any limitations to what proxies can do? Yes, proxies are limited to what is available through the ACP protocol itself. They can intercept and transform any ACP message, but they cannot access capabilities that ACP doesn't expose. From 0ff71fb8e8e34275262a306e5301e07e5a9d2624 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 7 Jan 2026 19:04:46 -0500 Subject: [PATCH 4/6] docs(rfd): Clarify component roles and conductor in proxy-chains - Add diagrams showing proxy chain structure and conductor abstraction - Define terminal vs non-terminal roles upfront - Explain conductor's role and reference canonical Rust implementation - Minor wording improvements throughout --- docs/rfds/proxy-chains.mdx | 42 +++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/docs/rfds/proxy-chains.mdx b/docs/rfds/proxy-chains.mdx index 6709c0d..d03a82f 100644 --- a/docs/rfds/proxy-chains.mdx +++ b/docs/rfds/proxy-chains.mdx @@ -47,11 +47,9 @@ flowchart LR P1 -->|final| Client ``` -(As described in the "Proxying in Practice" section, proxies in our design do not actually communicate directly with their successor, but instead use a central conductor. For the purposes of explaining the protocol, however, this section will continue to show diagrams "as if" proxies were in direct communication with each other.) - ### Proxying in practice: the role of the conductor -To allow for proxy isolation, our design does not have proxies communicate directly with their successor in the chain. Instead, there is a central conductor component that handles routing messages between components. +To allow for proxy isolation, our design does not have proxies communicate directly with their successor in the chain. Instead, there is a central *conductor* component that orchestrates messages moving between components. ```mermaid flowchart TB @@ -145,7 +143,31 @@ Proxy infrastructure could also enable editors to expose standardized IDE capabi ### Component roles -ACP defines client and agent as superroles, each with two specializations: +Each ACP proxy chain forms a sequence of components: + +```mermaid +flowchart LR + Client --> Proxy0 --> Proxy1 --> ... --> ProxyN --> Agent +``` + +The **client** and **agent** are _terminal_ roles - the client has only a successor (no predecessor), and the agent has only a predecessor (no successor). Proxies are _non-terminal_ - they have both a predecessor and a successor, forwarding messages between them. + +The **conductor** is a special component that orchestrates proxy chains. It spawns and manages proxy components, routes messages between them, and handles initialization. From the client's perspective, the conductor appears to be an ordinary agent: + +```mermaid +flowchart LR + Client -->|ACP| Conductor + + subgraph Managed["Managed by Conductor"] + Proxy0 --> Proxy1 --> ... --> Agent + end + + Conductor -.->|spawns & routes| Managed +``` + +We provide a canonical conductor implementation in Rust (`sacp-conductor`). Most editors would use this conductor directly to host proxies and agents, though they could also reimplement conductor functionality if needed. + +ACP defines client and agent as superroles, each with two specializations: ```mermaid classDiagram @@ -186,10 +208,6 @@ classDiagram Agent <|-- Proxy ``` -**Terminal roles**: Standard ACP behavior - direct client-to-agent communication. - -**Non-terminal roles**: Extended ACP behavior - clients manage proxy chains, agents forward to successors. - **Example Architecture:** ```mermaid @@ -215,15 +233,15 @@ flowchart TB Components discover their role from the initialization method they receive: - **Proxies** receive `proxy/initialize` - they have a successor and should forward messages -- **Agents** receive `initialize` - they are terminal and process messages directly +- **Agents** receive `initialize` - they are terminal (no successor) and process messages directly The `proxy/initialize` request has the same parameters as `initialize` and expects a standard `InitializeResponse`. The only difference is the method name, which signals to the component that it should operate as a proxy. **Conductor behavior:** - The conductor MUST send `proxy/initialize` to all proxy components -- The conductor MUST send `initialize` to the final agent component -- When a proxy forwards an `initialize` via `proxy/successor`, the conductor determines whether the successor is another proxy or the agent, and sends the appropriate method +- The conductor MUST send `initialize` to the final agent component (if any) +- When a proxy forwards an `initialize` via `proxy/successor`, the conductor determines whether the successor is another proxy or the agent, and sends `proxy/initialize` or `initialize` respectively. **Proxy behavior:** @@ -237,7 +255,7 @@ Note: A conductor can be configured to run in either terminal mode (expecting `i Proxies that provide MCP servers use the [MCP-over-ACP transport](./mcp-over-acp) mechanism. The conductor always advertises `mcpCapabilities.acp: true` to proxies and handles bridging for agents that don't support native ACP transport. -All proxies MUST support MCP-over-ACP. When the conductor sends `proxy/initialize`, proxies should be prepared to handle `mcp/connect`, `mcp/message`, and `mcp/disconnect` messages for any MCP servers they provide. +All proxies MUST respond to `proxy/initialize` with the MCP-over-ACP capability enabled. When the conductor sends `proxy/initialize`, proxies should be prepared to handle `mcp/connect`, `mcp/message`, and `mcp/disconnect` messages for any MCP servers they provide. ### Message reference From 5eceed2ee295e139e23dab7669ffb413030a52ba Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Wed, 7 Jan 2026 19:11:04 -0500 Subject: [PATCH 5/6] docs: Add mcp-over-acp RFD to navigation --- docs/docs.json | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs.json b/docs/docs.json index 5e17da6..560ab50 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -113,6 +113,7 @@ "rfds/session-info-update", "rfds/agent-telemetry-export", "rfds/proxy-chains", + "rfds/mcp-over-acp", "rfds/session-usage", "rfds/acp-agent-registry" ] From 2bd4e82213e9c0992ec36711de29014d830e97c7 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Thu, 8 Jan 2026 11:34:32 +0100 Subject: [PATCH 6/6] format --- docs/rfds/mcp-over-acp.mdx | 8 ++++---- docs/rfds/proxy-chains.mdx | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/rfds/mcp-over-acp.mdx b/docs/rfds/mcp-over-acp.mdx index cf96fd0..353d024 100644 --- a/docs/rfds/mcp-over-acp.mdx +++ b/docs/rfds/mcp-over-acp.mdx @@ -70,16 +70,16 @@ sequenceDiagram Agent-->>Client: session created Client->>Agent: prompt ("analyze this codebase") - + Note over Agent: Agent decides to use the tool Agent->>Client: mcp/connect (acpId: "") Client-->>Agent: connectionId: "conn-1" - + Agent->>Client: mcp/message (list_files tool call) Client-->>Agent: file listing results - + Agent-->>Client: response using tool results - + Agent->>Client: mcp/disconnect (connectionId: "conn-1") ``` diff --git a/docs/rfds/proxy-chains.mdx b/docs/rfds/proxy-chains.mdx index d03a82f..eb89a05 100644 --- a/docs/rfds/proxy-chains.mdx +++ b/docs/rfds/proxy-chains.mdx @@ -49,7 +49,7 @@ flowchart LR ### Proxying in practice: the role of the conductor -To allow for proxy isolation, our design does not have proxies communicate directly with their successor in the chain. Instead, there is a central *conductor* component that orchestrates messages moving between components. +To allow for proxy isolation, our design does not have proxies communicate directly with their successor in the chain. Instead, there is a central _conductor_ component that orchestrates messages moving between components. ```mermaid flowchart TB @@ -157,17 +157,17 @@ The **conductor** is a special component that orchestrates proxy chains. It spaw ```mermaid flowchart LR Client -->|ACP| Conductor - + subgraph Managed["Managed by Conductor"] Proxy0 --> Proxy1 --> ... --> Agent end - + Conductor -.->|spawns & routes| Managed ``` We provide a canonical conductor implementation in Rust (`sacp-conductor`). Most editors would use this conductor directly to host proxies and agents, though they could also reimplement conductor functionality if needed. -ACP defines client and agent as superroles, each with two specializations: +ACP defines client and agent as superroles, each with two specializations: ```mermaid classDiagram @@ -255,7 +255,7 @@ Note: A conductor can be configured to run in either terminal mode (expecting `i Proxies that provide MCP servers use the [MCP-over-ACP transport](./mcp-over-acp) mechanism. The conductor always advertises `mcpCapabilities.acp: true` to proxies and handles bridging for agents that don't support native ACP transport. -All proxies MUST respond to `proxy/initialize` with the MCP-over-ACP capability enabled. When the conductor sends `proxy/initialize`, proxies should be prepared to handle `mcp/connect`, `mcp/message`, and `mcp/disconnect` messages for any MCP servers they provide. +All proxies MUST respond to `proxy/initialize` with the MCP-over-ACP capability enabled. When the conductor sends `proxy/initialize`, proxies should be prepared to handle `mcp/connect`, `mcp/message`, and `mcp/disconnect` messages for any MCP servers they provide. ### Message reference