Skip to main content

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//! ## Global MCP Server
68//!
69//! ```
70//! # use agent_client_protocol::{Proxy, Conductor, ConnectTo};
71//! # use agent_client_protocol::mcp_server::McpServer;
72//! # async fn example(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
73//! # let my_mcp_server = McpServer::<Conductor, _>::builder("tools").build();
74//! Proxy.builder()
75//!     .with_mcp_server(my_mcp_server)
76//!     .connect_to(transport)
77//!     .await?;
78//! # Ok(())
79//! # }
80//! ```
81//!
82//! ## Per-Session MCP Server
83//!
84//! ```
85//! # use agent_client_protocol::{Proxy, Client, Conductor, ConnectTo};
86//! # use agent_client_protocol::schema::NewSessionRequest;
87//! # use agent_client_protocol::mcp_server::McpServer;
88//! # async fn example(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
89//! Proxy.builder()
90//!     .on_receive_request_from(Client, async |req: NewSessionRequest, responder, cx| {
91//!         let my_mcp_server = McpServer::<Conductor, _>::builder("tools").build();
92//!         cx.build_session_from(req)
93//!             .with_mcp_server(my_mcp_server)?
94//!             .on_proxy_session_start(responder, async |session_id| {
95//!                 // Session started with MCP server attached
96//!                 Ok(())
97//!             })
98//!     }, agent_client_protocol::on_receive_request!())
99//!     .connect_to(transport)
100//!     .await?;
101//! # Ok(())
102//! # }
103//! ```
104//!
105//! # The Conductor
106//!
107//! Proxies don't run standalone - they're orchestrated by a **conductor**.
108//! The conductor:
109//!
110//! - Spawns proxy processes
111//! - Chains them together
112//! - Connects the final proxy to the agent
113//!
114//! The [`agent-client-protocol-conductor`] crate provides a conductor binary. You configure
115//! it with a list of proxies to run.
116//!
117//! # Proxy Chains
118//!
119//! Multiple proxies can be chained:
120//!
121//! ```text
122//! Client <-> Proxy A <-> Proxy B <-> Agent
123//! ```
124//!
125//! Each proxy sees messages from its perspective:
126//! - `Client` is "toward the client" (Proxy A, or conductor if first)
127//! - `Agent` is "toward the agent" (Proxy B, or agent if last)
128//!
129//! Messages flow through each proxy in order. Each can inspect, modify,
130//! or handle messages before they continue.
131//!
132//! # Summary
133//!
134//! | Task | Approach |
135//! |------|----------|
136//! | Forward everything | Just `connect_to(transport)` |
137//! | Intercept specific messages | `on_receive_*_from` with explicit peers |
138//! | Add global tools | `with_mcp_server` on builder |
139//! | Add per-session tools | `with_mcp_server` on session builder |
140//!
141//! [`Proxy`]: crate::Proxy
142//! [`Client`]: crate::Client
143//! [`Agent`]: crate::Agent
144//! [`agent-client-protocol-conductor`]: https://crates.io/crates/agent-client-protocol-conductor