Skip to content

Commit 763475c

Browse files
committed
Address PR review feedback
- Restore doc comments in polyfill actor.rs and http.rs that were stripped during extraction from conductor - Polyfill now intercepts InitializeProxyRequest and sets mcpCapabilities.acp = true in the response - Re-enable unstable_mcp_over_acp feature for polyfill crate (it needs to set the acp field) - Update snapshot tests to reflect acp: true ProxiesAndAgent API redesign noted for separate PR.
1 parent 7d5f69b commit 763475c

6 files changed

Lines changed: 65 additions & 6 deletions

File tree

src/agent-client-protocol-conductor/tests/trace_client_mcp_server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ async fn test_trace_client_mcp_server() -> Result<(), agent_client_protocol::Err
337337
"agentCapabilities": Object {
338338
"loadSession": Bool(false),
339339
"mcpCapabilities": Object {
340-
"acp": Bool(false),
340+
"acp": Bool(true),
341341
"http": Bool(false),
342342
"sse": Bool(false),
343343
},

src/agent-client-protocol-conductor/tests/trace_mcp_tool_call.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ async fn test_trace_mcp_tool_call() -> Result<(), agent_client_protocol::Error>
351351
"agentCapabilities": Object {
352352
"loadSession": Bool(false),
353353
"mcpCapabilities": Object {
354-
"acp": Bool(false),
354+
"acp": Bool(true),
355355
"http": Bool(false),
356356
"sse": Bool(false),
357357
},
@@ -378,7 +378,7 @@ async fn test_trace_mcp_tool_call() -> Result<(), agent_client_protocol::Error>
378378
"agentCapabilities": Object {
379379
"loadSession": Bool(false),
380380
"mcpCapabilities": Object {
381-
"acp": Bool(false),
381+
"acp": Bool(true),
382382
"http": Bool(false),
383383
"sse": Bool(false),
384384
},

src/agent-client-protocol-polyfill/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ keywords = ["acp", "agent", "mcp", "polyfill"]
1111
categories = ["development-tools"]
1212

1313
[dependencies]
14-
agent-client-protocol = { workspace = true }
14+
agent-client-protocol = { workspace = true, features = ["unstable_mcp_over_acp"] }
1515
agent-client-protocol-schema.workspace = true
1616
anyhow.workspace = true
1717
async-stream.workspace = true

src/agent-client-protocol-polyfill/src/mcp_over_acp/actor.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ use super::BridgeMessage;
1010
/// and the ACP proxy chain.
1111
#[derive(Debug)]
1212
pub(crate) struct BridgeConnectionActor {
13+
/// How to connect to the MCP server (e.g., stdio or HTTP transport).
1314
transport: DynConnectTo<mcp::Client>,
15+
16+
/// Sender for messages back to the polyfill's bridge responder loop.
1417
bridge_tx: mpsc::Sender<BridgeMessage>,
18+
19+
/// Receiver for messages from the polyfill to forward to the MCP client.
1520
to_mcp_client_rx: mpsc::Receiver<Dispatch>,
1621
}
1722

src/agent-client-protocol-polyfill/src/mcp_over_acp/http.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,14 @@ pub async fn run_http_listener(
4545
Ok(())
4646
}
4747

48+
/// A component that receives HTTP requests/responses using the HTTP transport
49+
/// defined by the MCP protocol.
4850
struct HttpMcpBridge {
4951
listener: tokio::net::TcpListener,
5052
}
5153

5254
impl HttpMcpBridge {
55+
/// Creates a new HTTP-MCP bridge from an existing TCP listener.
5356
fn new(listener: tokio::net::TcpListener) -> Self {
5457
Self { listener }
5558
}
@@ -80,6 +83,7 @@ impl ConnectTo<mcp::Client> for HttpMcpBridge {
8083
}
8184
}
8285

86+
/// Error type for responding to malformed HTTP requests.
8387
#[derive(Debug, thiserror::Error)]
8488
#[error(transparent)]
8589
struct HttpError(#[from] agent_client_protocol::Error);
@@ -97,10 +101,25 @@ impl IntoResponse for HttpError {
97101
}
98102
}
99103

104+
/// Run a webserver listening on `listener` for HTTP requests at `/`
105+
/// and communicating those requests over `channel` to the JSON-RPC server.
100106
async fn run(listener: TcpListener, channel: Channel) -> Result<(), agent_client_protocol::Error> {
101107
let (registration_tx, registration_rx) = mpsc::unbounded();
102108
let state = BridgeState { registration_tx };
103109

110+
// The way that the MCP protocol works is a bit "special".
111+
//
112+
// Clients *POST* messages to `/`. Those are submitted to the MCP server.
113+
// If the message is a REQUEST, then the client waits until it gets a reply.
114+
// It expects the server to close the connection after responding.
115+
//
116+
// Clients can also issue a *GET* request. This will result in a stream of messages.
117+
//
118+
// Non-reply messages can be sent to any open stream (POST, GET, etc) but must be sent to
119+
// exactly one.
120+
//
121+
// There are provisions for "resuming" from a blocked point by tagging each message in the SSE
122+
// stream with an id, but we are not implementing that because I am lazy.
104123
async {
105124
let app = Router::new()
106125
.route("/", post(handle_post).get(handle_get))
@@ -114,36 +133,47 @@ async fn run(listener: TcpListener, channel: Channel) -> Result<(), agent_client
114133
.await
115134
}
116135

136+
/// The state we pass to our POST/GET handlers.
117137
struct BridgeState {
138+
/// Where to send registration messages.
118139
registration_tx: mpsc::UnboundedSender<HttpMessage>,
119140
}
120141

142+
/// Messages from HTTP handlers to the bridge server.
121143
#[derive(Debug)]
122144
#[allow(dead_code)]
123145
enum HttpMessage {
146+
/// A JSON-RPC request (has an id, expects a response via the channel).
124147
Request {
125148
http_request_id: uuid::Uuid,
126149
request: agent_client_protocol::jsonrpcmsg::Request,
127150
response_tx: mpsc::UnboundedSender<agent_client_protocol::jsonrpcmsg::Message>,
128151
},
152+
/// A JSON-RPC notification (no id, no response expected).
129153
Notification {
130154
http_request_id: uuid::Uuid,
131155
request: agent_client_protocol::jsonrpcmsg::Request,
132156
},
157+
/// A JSON-RPC response from the client.
133158
Response {
134159
http_request_id: uuid::Uuid,
135160
response: agent_client_protocol::jsonrpcmsg::Response,
136161
},
162+
/// A GET request to open an SSE stream for server-initiated messages.
137163
Get {
138164
http_request_id: uuid::Uuid,
139165
response_tx: mpsc::UnboundedSender<agent_client_protocol::jsonrpcmsg::Message>,
140166
},
141167
}
142168

169+
/// Clone of `agent_client_protocol::jsonrpcmsg::Id` since it does not impl `Hash`.
143170
#[derive(Eq, PartialEq, PartialOrd, Ord, Hash, Debug, Clone)]
144171
enum JsonRpcId {
172+
/// String identifier.
145173
String(String),
174+
/// Numeric identifier.
146175
Number(u64),
176+
/// Null identifier (for notifications).
147177
Null,
148178
}
149179

@@ -172,6 +202,7 @@ impl RunningServer {
172202
}
173203
}
174204

205+
/// The main loop: listen for incoming HTTP messages and outgoing JSON-RPC messages.
175206
async fn run(
176207
mut self,
177208
mut channel: Channel,
@@ -215,6 +246,7 @@ impl RunningServer {
215246
Ok(())
216247
}
217248

249+
/// Handle an incoming HTTP message (request, notification, response, or GET).
218250
fn handle_http_message(
219251
&mut self,
220252
message: HttpMessage,
@@ -339,6 +371,9 @@ impl RegisteredSession {
339371
}
340372
}
341373

374+
/// Accept a POST request carrying a JSON-RPC message from an MCP client.
375+
/// For requests (messages with id), we return an SSE stream.
376+
/// For notifications/responses (messages without id), we return 202 Accepted.
342377
async fn handle_post(
343378
State(state): State<Arc<BridgeState>>,
344379
body: String,
@@ -392,6 +427,8 @@ async fn handle_post(
392427
}
393428
}
394429

430+
/// Accept a GET request from an MCP client.
431+
/// Opens an SSE stream for server-initiated messages.
395432
async fn handle_get(
396433
State(state): State<Arc<BridgeState>>,
397434
) -> Result<Sse<impl Stream<Item = Result<axum::response::sse::Event, HttpError>>>, HttpError> {

src/agent-client-protocol-polyfill/src/mcp_over_acp/mod.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ use std::collections::HashMap;
2828
use std::path::PathBuf;
2929

3030
use agent_client_protocol::schema::{
31-
McpConnectRequest, McpConnectResponse, McpDisconnectNotification, McpOverAcpMessage, McpServer,
32-
McpServerHttp, McpServerStdio, NewSessionRequest,
31+
InitializeProxyRequest, McpConnectRequest, McpConnectResponse, McpDisconnectNotification,
32+
McpOverAcpMessage, McpServer, McpServerHttp, McpServerStdio, NewSessionRequest,
3333
};
3434
use agent_client_protocol::{
3535
Agent, Client, Conductor, ConnectTo, ConnectionTo, Dispatch, Proxy, Role,
@@ -149,6 +149,23 @@ impl ConnectTo<Conductor> for McpOverAcpPolyfill {
149149
bridge_rx,
150150
bridge_connections: HashMap::new(),
151151
})
152+
.on_receive_request_from(
153+
Client,
154+
async move |request: InitializeProxyRequest,
155+
responder,
156+
cx: ConnectionTo<Conductor>| {
157+
// Forward initialize to successor, then set mcpCapabilities.acp = true
158+
// in the response to advertise that we handle MCP-over-ACP.
159+
cx.send_request_to(Agent, request.initialize)
160+
.on_receiving_result(async move |result| {
161+
responder.respond_with_result(result.map(|mut response| {
162+
response.agent_capabilities.mcp_capabilities.acp = true;
163+
response
164+
}))
165+
})
166+
},
167+
agent_client_protocol::on_receive_request!(),
168+
)
152169
.on_receive_request_from(
153170
Client,
154171
{

0 commit comments

Comments
 (0)