Skip to content

Commit bade279

Browse files
committed
Step 2: Extract MCP bridging into agent-client-protocol-polyfill crate
New crate src/agent-client-protocol-polyfill with mcp_over_acp module: - McpOverAcpPolyfill proxy (ConnectTo<Conductor>) - BridgeConnectionActor, BridgeListeners, BridgeConnection - HTTP and stdio bridge transports - Uses own BridgeMessage enum instead of ConductorMessage Conductor bridge code still present (removed in Step 3).
1 parent c199154 commit bade279

8 files changed

Lines changed: 976 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ members = [
44
"src/agent-client-protocol-conductor",
55
"src/agent-client-protocol-cookbook",
66
"src/agent-client-protocol-derive",
7+
"src/agent-client-protocol-polyfill",
78
"src/agent-client-protocol-rmcp",
89
"src/agent-client-protocol-test",
910
"src/agent-client-protocol-trace-viewer",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
[package]
2+
name = "agent-client-protocol-polyfill"
3+
version = "0.11.1"
4+
edition.workspace = true
5+
authors.workspace = true
6+
license.workspace = true
7+
repository.workspace = true
8+
homepage.workspace = true
9+
description = "Polyfill proxies for Agent Client Protocol backward compatibility"
10+
keywords = ["acp", "agent", "mcp", "polyfill"]
11+
categories = ["development-tools"]
12+
13+
[dependencies]
14+
agent-client-protocol.workspace = true
15+
agent-client-protocol-schema.workspace = true
16+
anyhow.workspace = true
17+
async-stream.workspace = true
18+
axum.workspace = true
19+
futures.workspace = true
20+
futures-concurrency.workspace = true
21+
rustc-hash.workspace = true
22+
serde.workspace = true
23+
serde_json.workspace = true
24+
thiserror = "2.0"
25+
tokio.workspace = true
26+
tokio-util.workspace = true
27+
tracing.workspace = true
28+
uuid.workspace = true
29+
30+
[lints]
31+
workspace = true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//! # agent-client-protocol-polyfill
2+
//!
3+
//! Polyfill proxies for backward compatibility with agents that don't support
4+
//! newer ACP features natively.
5+
//!
6+
//! ## MCP-over-ACP Polyfill
7+
//!
8+
//! The [`mcp_over_acp`] module provides a proxy that bridges MCP-over-ACP transport
9+
//! for agents that don't support `mcpCapabilities.acp`. It intercepts `NewSessionRequest`
10+
//! to transform `McpServer::Http` entries with `acp:` URLs into localhost TCP bridges,
11+
//! and handles `_mcp/*` messages by routing them through those bridges.
12+
13+
pub mod mcp_over_acp;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use agent_client_protocol::{
2+
ConnectTo, Dispatch, DynConnectTo, role::mcp, schema::McpDisconnectNotification,
3+
};
4+
use futures::{SinkExt as _, StreamExt as _, channel::mpsc};
5+
use tracing::info;
6+
7+
use super::BridgeMessage;
8+
9+
/// Actor that bridges a single MCP connection between a local MCP client
10+
/// and the ACP proxy chain.
11+
#[derive(Debug)]
12+
pub(crate) struct BridgeConnectionActor {
13+
transport: DynConnectTo<mcp::Client>,
14+
bridge_tx: mpsc::Sender<BridgeMessage>,
15+
to_mcp_client_rx: mpsc::Receiver<Dispatch>,
16+
}
17+
18+
impl BridgeConnectionActor {
19+
pub fn new(
20+
component: impl ConnectTo<mcp::Client>,
21+
bridge_tx: mpsc::Sender<BridgeMessage>,
22+
to_mcp_client_rx: mpsc::Receiver<Dispatch>,
23+
) -> Self {
24+
Self {
25+
transport: DynConnectTo::new(component),
26+
bridge_tx,
27+
to_mcp_client_rx,
28+
}
29+
}
30+
31+
pub async fn run(self, connection_id: String) -> Result<(), agent_client_protocol::Error> {
32+
info!(connection_id, "MCP bridge connected");
33+
34+
let Self {
35+
transport,
36+
mut bridge_tx,
37+
to_mcp_client_rx,
38+
} = self;
39+
40+
let result = mcp::Client
41+
.builder()
42+
.name(format!("mcp-client-to-polyfill({connection_id})"))
43+
.on_receive_dispatch(
44+
{
45+
let mut bridge_tx = bridge_tx.clone();
46+
let connection_id = connection_id.clone();
47+
async move |message: Dispatch, _cx| {
48+
bridge_tx
49+
.send(BridgeMessage::ClientToServer {
50+
connection_id: connection_id.clone(),
51+
message,
52+
})
53+
.await
54+
.map_err(|_| agent_client_protocol::Error::internal_error())
55+
}
56+
},
57+
agent_client_protocol::on_receive_dispatch!(),
58+
)
59+
.connect_with(transport, async move |mcp_connection_to_client| {
60+
let mut to_mcp_client_rx = to_mcp_client_rx;
61+
while let Some(message) = to_mcp_client_rx.next().await {
62+
mcp_connection_to_client.send_proxied_message(message)?;
63+
}
64+
Ok(())
65+
})
66+
.await;
67+
68+
bridge_tx
69+
.send(BridgeMessage::Disconnected {
70+
notification: McpDisconnectNotification {
71+
connection_id,
72+
meta: None,
73+
},
74+
})
75+
.await
76+
.map_err(|_| agent_client_protocol::Error::internal_error())?;
77+
78+
result
79+
}
80+
}

0 commit comments

Comments
 (0)