Skip to content

Conversation

@nikomatsakis
Copy link
Contributor

  • Proposes extending ACP with proxy chain capabilities for composable agent architectures
  • Defines conductor/proxy roles and initialization protocols
  • Includes MCP-over-ACP transport for seamless tool integration
  • Provides complete protocol specification with examples and FAQ
  • Enables universal extension mechanism that subsumes existing approaches

@nikomatsakis nikomatsakis requested a review from a team as a code owner November 15, 2025 18:19
@nikomatsakis
Copy link
Contributor Author

One thing I am noticing as I work with this system:

It would be really nice if the MCP-over-ACP protocol gave you the ability to correlate an MCP request with an ACP session-id that it originates from. I'm working on some research agent proxies that would benefit from this because they want to remember some details from the originating session to know how to react.

Copy link
Member

@benbrandt benbrandt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright a few questions, but overall I'm quite excited about what this unlocks!


### Capability Reference

**Proxy Capability** (`"proxy": true`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear:

  • a Client offers this as a capability to indicate to the agent it should run in proxy mode
  • an Agent offers this as a capability if it can work as a proxy and agrees to do so?

You cover this later, and if I understand correctly, the only ones who use this capability are components of the conductor<->proxy connection, it shouldn't be used elsewhere? Like basically a registration with a conductor at some level in the chain?

I guess I am trying to better understand which components would set this to "true" in the chain and which would not need to or should not?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's a bit tricky. The way I did it is like this: the conductor (and only the conductor) offers this capability to the "proxy" components (those that have a successor). It indicates to them that they can send messages to their successor. Then they respond with this capability to indicate that they are capable of being used in proxy mode.

This way, if a "terminal agent" like claude-code-acp is used in proxy position, the conductor will return an error, since that makes no sense.

On the other hand, if an agent is designed to be used as a proxy, it can error if it does NOT receive the proxy capability.

Some agents can operate either as a terminal agent OR a proxy (including the conductor itself), so they check for whether they've been offered the proxy capability to decide how they are operating.


- **Conductor ↔ proxy initialization**: `InitializeRequest` contains `"proxy": true`. The server MUST respond with `"proxy": true` in the `InitializeResponse` to acknowledge it will act as a proxy. The conductor MUST error and terminate if the server responds without `"proxy": true`.

- **Proxy ↔ successor agent**: When a proxy receives an `InitializeRequest`, it MUST remove the `"proxy"` capability before forwarding via `proxy/successor/request`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for later down the line: it would be great at the SDK level to have helpers for this at some level to make it easier not to forget this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think I baked this into the SDK actually so that agents don't have to do anything in particular.

- It is only legal to include in a server's initialization response if it was offered by the client. In that case, it indicates that the server agrees to act as a proxy, not as an agent.
- When a component responds with proxy capability, it SHOULD forward requests it does not understand and it SHOULD preserve metadata fields when forwarding messages.

**MCP-over-ACP Transport** (`"mcp_acp_transport": true`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor thing, but i wonder if there is a way to merge this with the existing MCP capabilities? https://agentclientprotocol.com/protocol/initialization#mcp-capabilities

We currently only have it on the server side. I could see that if a client supplies an mcp server of type "acp" transport it is an implicit indication that it would support it? And then the client should only send one of the agent responds withe an acp capability here?

It might be too implicit, but could work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could work, yes. It'd be nice not to have to separate out these things to be honest. I used to have just one (proxy + acp-over-mcp), but I separate them to allow for agents that can natively use mcp-over-acp.


### Why do proxies remove the proxy/MCP capabilities when forwarding to their successor?

The conductor would always re-add these capabilities anyway, which shows that they are really specific to conductor↔proxy communication and so we decided to simply omit them. This makes it clear that these capabilities are part of the conductor's orchestration protocol rather than something proxies need to understand or manage.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thought I had: is the relationship between conductor and proxy distinct enough to require it's own initialize method?

That being said, it is possible these things want to modify the initialize call to the eventual agent, so we should probably leave as is.

The next question is: should this be a capability? Or a top-level param?
Kind of thinking out loud, I guess there's just something about this that isn't just "I can act as a proxy" and more of a flag of "we must have a proxy relationship" that is hanging me up currently.

I actually think what you have proposed is ultimately fine, but there's just something here that makes me want to reflect this differently than a capability... but I am not sure if that instinct is correct or if I am overthinking it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I agree it doesn't quite feel right to me. Maybe a method like initialize/proxy makes sense instead? It's not really a capability, as you say, and it winds up requiring some "finnicky" stuff to act like one.


### 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this actually helps me figure out a better home for some unexplored design space of how best to handle IDE->agent functionality.

Like if we wanted to have a "standardized" way to offer IDE capabilities to an agent, like surfacing diagnostics, or even the current file system + terminal APIs, these could potentially move to "proxy" space potentially (would need to sort out how the specific proxy would talk back to what is essentially the "client")

But it would allow for a lot of experimentation on the best way to expose this without muddying the main protocol (especially since there is more demand for non-IDE clients) while still providing a lot more control and power than just offering an MCP server from the client

@josevalim
Copy link
Contributor

I wonder if, instead of baking proxies into the protocol, we should instead augment the protocol to make proxies possible (in case it isn't already). For example, HTTP does not have proxy specific messages, instead the proxy is a natural extension of the protocol.

FWIW, we have already implemented a websockets-to-io ACP proxy, and besides MCP-over-ACP (which we also had to emulate), the proxy works just fine. So I wonder if the benefits here could be implemented by cascading ACP commands: my-proxy foo-bar-proxy claude-code-acp.

The reason I bring this up is because extensions to ACP will push complexity to either client or agents (or both), and given ACP is a M:N protocol, any extension we push needs to be implemented either M, N, or M+N times. Unless I am misunderstanding the proposal. :)

It would be really nice if the MCP-over-ACP protocol gave you the ability to correlate an MCP request with an ACP session-id that it originates from. I'm working on some research agent proxies that would benefit from this because they want to remember some details from the originating session to know how to react.

Yes, this is a must have, as you can have multiple ACP sessions over the same channel.

Copy link

@gkgoat1 gkgoat1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea from the discussion


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

Leveraging `"acp"` transport allows a single ACP proxy to do all of the following:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO MCP proxying should be separate from the whole proxy infrastructure as it could be used by editors like VSCode with natively-integrated MCP servers and agents to supply tools in a sandbox.

@gkgoat1
Copy link

gkgoat1 commented Dec 17, 2025

I wonder if, instead of baking proxies into the protocol, we should instead augment the protocol to make proxies possible (in case it isn't already). For example, HTTP does not have proxy specific messages, instead the proxy is a natural extension of the protocol.

FWIW, we have already implemented a websockets-to-io ACP proxy, and besides MCP-over-ACP (which we also had to emulate), the proxy works just fine. So I wonder if the benefits here could be implemented by cascading ACP commands: my-proxy foo-bar-proxy claude-code-acp.

ACP cascades seem like a simpler option IMO as there would be no controller (what even would it bring to the table?) imo.

The reason I bring this up is because extensions to ACP will push complexity to either client or agents (or both), and given ACP is a M:N protocol, any extension we push needs to be implemented either M, N, or M+N times. Unless I am misunderstanding the proposal. :)

+1, this seems like a lot of complexity everywhere

It would be really nice if the MCP-over-ACP protocol gave you the ability to correlate an MCP request with an ACP session-id that it originates from. I'm working on some research agent proxies that would benefit from this because they want to remember some details from the originating session to know how to react.

Yes, this is a must have, as you can have multiple ACP sessions over the same channel.

+1, MCP-over-ACP can reduce the needed permissions of agents as well.

@jwflicker
Copy link

jwflicker commented Dec 21, 2025

wonder if this can be used to also create re-usable components for downloading, installing, updating, enabling out-of-protocol start up options / workarounds (add extra root folders for example). A commander that supported this would probably need its own API for such pre-ACP operations... probably too much stuff to proxy behind a basic Initializaiton request. But there is probably a lot of redundant efforts around managing these setup activities.

@nikomatsakis
Copy link
Contributor Author

Hi all, an update:

I've been iterating on the prototype implementation. I have a few updates to make to the specification but you can see it embodied in the sacp Rust crate. It is working very well and the Rust SDK now supports a number of interesting use cases, including ephermal MCP servers that have access to program state (see patchwork-rs).

I've made a few changes relative to what is written in this RFD at the moment:

  • we now use a _initialize/proxy message rather than the complex "capability dance" described in here; the response is the standard initialization response
  • we now use HTTP bridging for MCP servers, which is more convenient in a number of ways, but that's not "officially" part of the spec anyhow
  • we now use a single method _successor/message and _acp/message and let the request/notification be determined by the presence or absence of an id field; this simplified some of the JSON-RPC code

To respond to some of the points raised on this thread:

Do we really need the conductor?

I wonder if, instead of baking proxies into the protocol, we should instead augment the protocol to make proxies possible (in case it isn't already). For example, HTTP does not have proxy specific messages, instead the proxy is a natural extension of the protocol.

I initially had a design where an ACP proxy was an ACP agent that internally created its delegatee. However, I found that to have a critical flaw, which was that it required each proxy to understand the full chain and know how to start them, which shouldn't really be their concern: you want to be able to centrally upgrade the kinds of ACP proxies and agents and/or swap out the transport protocols without the proxy having to be recompiled. Under the current design, a proxy is blissfully ignorant of all those details.

This in turn allows for the end-goal I am working towards, which is that proxies can be shipped as "low-capability" WASM containers that run in sandboxes.

I could imagine a variant of this design in which the proxy "creates" its successor via a message to the conductor, but honestly, that happens already when the proxy sends the "initialize" method up to the conductor.

How would we scale to more agents and things?

The proposal as written just has a notion of a single successor, but it would be quite simple I think to scale this up to have more than just two. We would simply extend the initialize/proxy to give more information about the peers and successor/message method to have a peer field. We could add the peer field already in anticipation, but I'm not sure if it makes sense to do so, I'd be inclined to wait until we have a reason to do so.

Perhaps a good first step would be to have the response from initialize/proxy not be exactly the same as initialize but rather include a wrapper to leave room for adding additional fields later indicating the required peers.

I would rather not specify how that works without working from concerete use cases; my expectation is that we would actually want to do something where, as part of initialization, we can send follow-up requests to the conductor requesting that it "create a peer".

I could imagine doing that now to "create the successor".

@nikomatsakis
Copy link
Contributor Author

Hi all-- I pushed some updates from the work I've done.

First, I updated to use a distinct proxy/initialize request. Second, I updated the method names to match the current implementation, and changed a few other minor details.

In terms of larger feedback, I added a FAQ to explain why I think proxies deserve to be first-class in the protocol, "Why do all messages go through the conductor instead of direct proxy-to-proxy communication?. The TL;DR is that I want to be able to centralize and consolidate the lifecycle work of spawning successors and so forth; I don't want proxies to be responsible for knowing how to do that.

I also wrote up a FAQ explaining how we could support this work to cover multi-agent scenarios in the future, rather than just a single proxy.

But writing that last FAQ raised up a question that has been bugging me for some time, which is: Why are we distinguishing MCP-over-ACP and proxies anyway? On the one hand, they are conceptually distinct features, and I was pondering splitting up the RFD into two RFDs, one for MCP-over-ACP and one for proxies.

On the other hand, the MCP-over-ACP protocl and implementation is basically the same as ACP proxying, but more general, because it's already covering the "multi-agent" scenario. So I wrote up a FAQ explaining why I have specialized MCP-over-ACP and proxies into distinct things. The TL;DR is that if proxies could "start" their successor, that gives them a lot of generality (i.e., they can also disconnect, presumably, or start multiple successors) and I'm not sure we want to do that yet.

I have to say though I am not 100% convinced by this argument. It wouldn't be all that hard to modify the conductor to handle the more general case, and it would be interesting. Basically you'd just have a peer/{connect,message,disconnect} methods. Proxies would be able to "connect" to their "successor" peer (as many times as they want...?) and the conductor would be able to handle that and route things appropriately. MCP-over-ACP could be the same thing, it's just a question of what JSON the peer expects.

I'm curious what people think about that question.

nikomatsakis and others added 7 commits December 31, 2025 17:38
- Proposes extending ACP with proxy chain capabilities for composable agent architectures
- Defines conductor/proxy roles and initialization protocols
- Includes MCP-over-ACP transport for seamless tool integration
- Provides complete protocol specification with examples and FAQ
- Enables universal extension mechanism that subsumes existing approaches
Addresses feedback from PR discussion about whether protocol-level
proxy support is needed vs simple ACP command cascading.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Changes initialization protocol from capability negotiation to distinct
methods: proxies receive `proxy/initialize`, agents receive `initialize`.
This makes component roles explicit from the method name rather than
requiring a capability handshake.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replaces separate request/notification methods with unified methods
where the message type is determined by presence of id field:
- proxy/successor/message: proxy sends to successor
- proxy/successor/receive: conductor delivers from successor

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Explains how proxies correlate MCP requests with sessions by using
fresh ACP-IDs instead of session-ids, avoiding a deadlock where
agents don't return session-id until after MCP initialization.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Consolidate proxy/successor/message and proxy/successor/receive to
  single bidirectional proxy/successor method
- Update MCP message format to match implementation (mcp/connect,
  mcp/message, mcp/disconnect with connectionId)
- Add IDE capabilities section to Shiny Future
- Add FAQ on M:N multi-agent topologies with optional peer field
- Add FAQ explaining MCP vs proxy message separation (lifecycle semantics)
- Fix message format documentation to show flattened inner message structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@nikomatsakis
Copy link
Contributor Author

So, in accordance with the ACP process, I'm going to merge this as a draft RFD with myself as champion. I appreciate the feedback from folks on the thread so far -- I'd love to continue the conversation in the Zulip thread.

@nikomatsakis nikomatsakis merged commit a489734 into agentclientprotocol:main Dec 31, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants