agent_client_protocol/concepts/proxies.rs
1//! Building proxies that intercept and modify messages.
2//!
3//! A **proxy** sits between a client and an agent, intercepting messages
4//! in both directions. This is how you add capabilities like MCP tools,
5//! logging, or message transformation.
6//!
7//! # The Proxy Role Type
8//!
9//! Proxies use the [`Proxy`] role type, which has two peers:
10//!
11//! - [`Client`] - messages from/to the client direction
12//! - [`Agent`] - messages from/to the agent direction
13//!
14//! Unlike simpler links, there's no default peer - you must always specify
15//! which direction you're communicating with.
16//!
17//! # Default Forwarding
18//!
19//! By default, [`Proxy`] forwards all messages it doesn't handle.
20//! This means a minimal proxy that does nothing is just:
21//!
22//! ```
23//! # use agent_client_protocol::{Proxy, Conductor, ConnectTo};
24//! # async fn example(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
25//! Proxy.builder()
26//! .connect_to(transport)
27//! .await?;
28//! # Ok(())
29//! # }
30//! ```
31//!
32//! All messages pass through unchanged.
33//!
34//! # Intercepting Messages
35//!
36//! To intercept specific messages, use `on_receive_*_from` with explicit peers:
37//!
38//! ```
39//! # use agent_client_protocol::{Proxy, Client, Agent, Conductor, ConnectTo};
40//! # use agent_client_protocol_test::ProcessRequest;
41//! # async fn example(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
42//! Proxy.builder()
43//! // Intercept requests from the client
44//! .on_receive_request_from(Client, async |req: ProcessRequest, responder, cx| {
45//! // Modify the request
46//! let modified = ProcessRequest {
47//! data: format!("prefix: {}", req.data),
48//! };
49//!
50//! // Forward to agent and relay the response back
51//! cx.send_request_to(Agent, modified)
52//! .forward_response_to(responder)
53//! }, agent_client_protocol::on_receive_request!())
54//! .connect_to(transport)
55//! .await?;
56//! # Ok(())
57//! # }
58//! ```
59//!
60//! Messages you don't handle are forwarded automatically.
61//!
62//! # Adding MCP Servers
63//!
64//! A common use case is adding tools via MCP. You can add them globally
65//! (available in all sessions) or per-session.
66//!
67//! These ACP attachment APIs require the `unstable_mcp_over_acp` feature.
68//!
69//! ## Global MCP Server
70//!
71//! ```ignore
72//! # use agent_client_protocol::{Proxy, Conductor, ConnectTo};
73//! # use agent_client_protocol::mcp_server::McpServer;
74//! # use agent_client_protocol_rmcp::McpServerExt;
75//! # async fn example(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
76//! # let my_mcp_server = McpServer::<Conductor, _>::builder("tools").build();
77//! Proxy.builder()
78//! .with_mcp_server(my_mcp_server)
79//! .connect_to(transport)
80//! .await?;
81//! # Ok(())
82//! # }
83//! ```
84//!
85//! ## Per-Session MCP Server
86//!
87//! ```ignore
88//! # use agent_client_protocol::{Proxy, Client, Conductor, ConnectTo};
89//! # use agent_client_protocol::schema::v1::NewSessionRequest;
90//! # use agent_client_protocol::mcp_server::McpServer;
91//! # use agent_client_protocol_rmcp::McpServerExt;
92//! # async fn example(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
93//! Proxy.builder()
94//! .on_receive_request_from(Client, async |req: NewSessionRequest, responder, cx| {
95//! let my_mcp_server = McpServer::<Conductor, _>::builder("tools").build();
96//! cx.build_session_from(req)
97//! .with_mcp_server(my_mcp_server)?
98//! .on_proxy_session_start(responder, async |session_id| {
99//! // Session started with MCP server attached
100//! Ok(())
101//! })
102//! }, agent_client_protocol::on_receive_request!())
103//! .connect_to(transport)
104//! .await?;
105//! # Ok(())
106//! # }
107//! ```
108//!
109//! # The Conductor
110//!
111//! Proxies don't run standalone - they're orchestrated by a **conductor**.
112//! The conductor:
113//!
114//! - Spawns proxy processes
115//! - Chains them together
116//! - Connects the final proxy to the agent
117//!
118//! The [`agent-client-protocol-conductor`] crate provides a conductor binary. You configure
119//! it with a list of proxies to run.
120//!
121//! # Proxy Chains
122//!
123//! Multiple proxies can be chained:
124//!
125//! ```text
126//! Client <-> Proxy A <-> Proxy B <-> Agent
127//! ```
128//!
129//! Each proxy sees messages from its perspective:
130//! - `Client` is "toward the client" (Proxy A, or conductor if first)
131//! - `Agent` is "toward the agent" (Proxy B, or agent if last)
132//!
133//! Messages flow through each proxy in order. Each can inspect, modify,
134//! or handle messages before they continue.
135//!
136//! # Summary
137//!
138//! | Task | Approach |
139//! |------|----------|
140//! | Forward everything | Just `connect_to(transport)` |
141//! | Intercept specific messages | `on_receive_*_from` with explicit peers |
142//! | Add global tools | `with_mcp_server` on builder |
143//! | Add per-session tools | `with_mcp_server` on session builder |
144//!
145//! [`Proxy`]: crate::Proxy
146//! [`Client`]: crate::Client
147//! [`Agent`]: crate::Agent
148//! [`agent-client-protocol-conductor`]: https://crates.io/crates/agent-client-protocol-conductor