Skip to main content

agent_client_protocol_cookbook/
lib.rs

1//! Cookbook of common patterns for building ACP components.
2//!
3//! This crate contains guides and examples for the three main things you can build with ACP:
4//!
5//! - **Clients** - Connect to an existing agent and send prompts
6//! - **Proxies** - Sit between client and agent to add capabilities (like MCP tools)
7//! - **Agents** - Respond to prompts with AI-powered responses
8//!
9//! See the [`agent_client_protocol::concepts`] module for detailed explanations of
10//! the concepts behind the API.
11//!
12//! # Building Clients
13//!
14//! A client connects to an agent, sends requests, and handles responses. Use
15//! [`Client.builder()`](agent_client_protocol::Client) to build connections.
16//!
17//! - [`one_shot_prompt`] - Send a single prompt and get a response (simplest pattern)
18//! - [`connecting_as_client`] - More details on connection setup and permission handling
19//!
20//! # Building Proxies
21//!
22//! A proxy sits between client and agent, intercepting and optionally modifying
23//! messages. The most common use case is adding MCP tools. Use [`Proxy.builder()`](agent_client_protocol::Proxy)
24//! to build proxy connections.
25//!
26//! **Important:** Proxies don't run standalone—they need the [`agent-client-protocol-conductor`] to
27//! orchestrate the connection between client, proxies, and agent. See
28//! [`running_proxies_with_conductor`] for how to put the pieces together.
29//!
30//! - [`global_mcp_server`] - Add tools that work across all sessions
31//! - [`per_session_mcp_server`] - Add tools with session-specific state
32//! - [`filtering_tools`] - Enable or disable tools dynamically
33//! - [`reusable_components`] - Package your proxy as a [`ConnectTo`] for composition
34//! - [`running_proxies_with_conductor`] - Run your proxy with an agent
35//!
36//! [`agent-client-protocol-conductor`]: https://crates.io/crates/agent-client-protocol-conductor
37//!
38//! # Building Agents
39//!
40//! An agent receives prompts and generates responses. Use [`Agent.builder()`](agent_client_protocol::Agent)
41//! to build agent connections.
42//!
43//! - [`building_an_agent`] - Handle initialization, sessions, and prompts
44//! - [`reusable_components`] - Package your agent as a [`ConnectTo`]
45//! - [`custom_message_handlers`] - Fine-grained control over message routing
46//!
47//! [`agent_client_protocol::concepts`]: agent_client_protocol::concepts
48//! [`Client`]: agent_client_protocol::Client
49//! [`Agent`]: agent_client_protocol::Agent
50//! [`Proxy`]: agent_client_protocol::Proxy
51//! [`ConnectTo`]: agent_client_protocol::ConnectTo
52
53pub mod one_shot_prompt {
54    //! Pattern: You Only Prompt Once.
55    //!
56    //! The simplest client pattern: connect to an agent, send one prompt, get the
57    //! response. This is useful for CLI tools, scripts, or any case where you just
58    //! need a single interaction with an agent.
59    //!
60    //! # Example
61    //!
62    //! ```
63    //! use agent_client_protocol::{Client, Agent, ConnectTo};
64    //! use agent_client_protocol::schema::{InitializeRequest, ProtocolVersion};
65    //!
66    //! async fn ask_agent(
67    //!     transport: impl ConnectTo<Client> + 'static,
68    //!     prompt: &str,
69    //! ) -> Result<String, agent_client_protocol::Error> {
70    //!     Client.builder()
71    //!         .name("my-client")
72    //!         .connect_with(transport, async |connection| {
73    //!             // Initialize the connection
74    //!             connection.send_request(InitializeRequest::new(ProtocolVersion::V1))
75    //!                 .block_task().await?;
76    //!
77    //!             // Create a session, send prompt, read response
78    //!             let mut session = connection.build_session_cwd()?
79    //!                 .block_task()
80    //!                 .start_session()
81    //!                 .await?;
82    //!
83    //!             session.send_prompt(prompt)?;
84    //!             session.read_to_string().await
85    //!         })
86    //!         .await
87    //! }
88    //! ```
89    //!
90    //! # How it works
91    //!
92    //! 1. **[`connect_with`]** establishes the transport connection and runs your
93    //!    code while handling messages in the background
94    //! 2. **[`send_request`]** + **[`block_task`]** sends the initialize request
95    //!    and waits for the response
96    //! 3. **[`build_session_cwd`]** creates a session builder using the current working directory
97    //! 4. **[`start_session`]** sends the `NewSessionRequest` and returns an
98    //!    [`ActiveSession`] handle
99    //! 5. **[`send_prompt`]** queues the prompt to send to the agent
100    //! 6. **[`read_to_string`]** reads all text chunks until the agent finishes
101    //!
102    //! # Handling permission requests
103    //!
104    //! Most agents will ask for permission before taking actions like running
105    //! commands or writing files. See [`connecting_as_client`] for how to handle
106    //! [`RequestPermissionRequest`] messages.
107    //!
108    //! [`connect_with`]: agent_client_protocol::Builder::connect_with
109    //! [`send_request`]: agent_client_protocol::ConnectionTo::send_request
110    //! [`block_task`]: agent_client_protocol::SentRequest::block_task
111    //! [`build_session_cwd`]: agent_client_protocol::ConnectionTo::build_session_cwd
112    //! [`start_session`]: agent_client_protocol::SessionBuilder::start_session
113    //! [`ActiveSession`]: agent_client_protocol::ActiveSession
114    //! [`send_prompt`]: agent_client_protocol::ActiveSession::send_prompt
115    //! [`read_to_string`]: agent_client_protocol::ActiveSession::read_to_string
116    //! [`connecting_as_client`]: super::connecting_as_client
117    //! [`RequestPermissionRequest`]: agent_client_protocol::schema::RequestPermissionRequest
118}
119
120pub mod connecting_as_client {
121    //! Pattern: Connecting as a client.
122    //!
123    //! To connect to an ACP agent and send requests, use [`connect_with`].
124    //! This runs your code while the connection handles incoming messages
125    //! in the background.
126    //!
127    //! # Basic Example
128    //!
129    //! ```
130    //! use agent_client_protocol::{Client, Agent, ConnectTo};
131    //! use agent_client_protocol::schema::{InitializeRequest, ProtocolVersion};
132    //!
133    //! async fn connect_to_agent(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
134    //!     Client.builder()
135    //!         .name("my-client")
136    //!         .connect_with(transport, async |connection| {
137    //!             // Initialize the connection
138    //!             connection.send_request(InitializeRequest::new(ProtocolVersion::V1))
139    //!                 .block_task().await?;
140    //!
141    //!             // Create a session and send a prompt
142    //!             connection.build_session_cwd()?
143    //!                 .block_task()
144    //!                 .run_until(async |mut session| {
145    //!                     session.send_prompt("Hello, agent!")?;
146    //!                     let response = session.read_to_string().await?;
147    //!                     println!("Agent said: {}", response);
148    //!                     Ok(())
149    //!                 })
150    //!                 .await
151    //!         })
152    //!         .await
153    //! }
154    //! ```
155    //!
156    //! # Using the Session Builder
157    //!
158    //! The [`build_session`] method creates a [`SessionBuilder`] that handles
159    //! session creation and provides convenient methods for interacting with
160    //! the session:
161    //!
162    //! - [`send_prompt`] - Send a text prompt to the agent
163    //! - [`read_update`] - Read the next update (text chunk, tool call, etc.)
164    //! - [`read_to_string`] - Read all text until the turn ends
165    //!
166    //! The session builder also supports adding MCP servers with [`with_mcp_server`].
167    //!
168    //! # Handling Permission Requests
169    //!
170    //! Agents may send [`RequestPermissionRequest`] to ask for user approval
171    //! before taking actions. Handle these with [`on_receive_request`]:
172    //!
173    //! ```ignore
174    //! Client.builder()
175    //!     .on_receive_request(async |req: RequestPermissionRequest, responder, _connection| {
176    //!         // Auto-approve by selecting the first option (YOLO mode)
177    //!         let option_id = req.options.first().map(|opt| opt.id.clone());
178    //!         responder.respond(RequestPermissionResponse {
179    //!             outcome: match option_id {
180    //!                 Some(id) => RequestPermissionOutcome::Selected { option_id: id },
181    //!                 None => RequestPermissionOutcome::Cancelled,
182    //!             },
183    //!             meta: None,
184    //!         })
185    //!     }, agent_client_protocol::on_receive_request!())
186    //!     .connect_with(transport, async |connection| { /* ... */ })
187    //!     .await
188    //! ```
189    //!
190    //! # Note on `block_task`
191    //!
192    //! Using [`block_task`] is safe inside `connect_with` because the closure runs
193    //! as a spawned task, not on the event loop. The event loop continues processing
194    //! messages (including the response you're waiting for) while your task blocks.
195    //!
196    //! [`connect_with`]: agent_client_protocol::Builder::connect_with
197    //! [`block_task`]: agent_client_protocol::SentRequest::block_task
198    //! [`build_session`]: agent_client_protocol::ConnectionTo::build_session
199    //! [`SessionBuilder`]: agent_client_protocol::SessionBuilder
200    //! [`send_prompt`]: agent_client_protocol::ActiveSession::send_prompt
201    //! [`read_update`]: agent_client_protocol::ActiveSession::read_update
202    //! [`read_to_string`]: agent_client_protocol::ActiveSession::read_to_string
203    //! [`with_mcp_server`]: agent_client_protocol::SessionBuilder::with_mcp_server
204    //! [`RequestPermissionRequest`]: agent_client_protocol::schema::RequestPermissionRequest
205    //! [`on_receive_request`]: agent_client_protocol::Builder::on_receive_request
206}
207
208pub mod building_an_agent {
209    //! Pattern: Building an agent.
210    //!
211    //! An agent handles prompts and generates responses. At minimum, an agent must:
212    //!
213    //! 1. Handle [`InitializeRequest`] to establish the connection
214    //! 2. Handle [`NewSessionRequest`] to create sessions
215    //! 3. Handle [`PromptRequest`] to process prompts
216    //!
217    //! Use [`Agent.builder()`](agent_client_protocol::Agent) to build agent connections.
218    //!
219    //! # Minimal Example
220    //!
221    //! ```
222    //! use agent_client_protocol::{Agent, Client, ConnectTo, Dispatch, ConnectionTo};
223    //! use agent_client_protocol::schema::{
224    //!     InitializeRequest, InitializeResponse, AgentCapabilities,
225    //!     NewSessionRequest, NewSessionResponse, SessionId,
226    //!     PromptRequest, PromptResponse, StopReason,
227    //! };
228    //!
229    //! async fn run_agent(transport: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
230    //!     Agent.builder()
231    //!         .name("my-agent")
232    //!         // Handle initialization
233    //!         .on_receive_request(async |req: InitializeRequest, responder, _connection| {
234    //!             responder.respond(
235    //!                 InitializeResponse::new(req.protocol_version)
236    //!                     .agent_capabilities(AgentCapabilities::new())
237    //!             )
238    //!         }, agent_client_protocol::on_receive_request!())
239    //!         // Handle session creation
240    //!         .on_receive_request(async |req: NewSessionRequest, responder, _connection| {
241    //!             responder.respond(NewSessionResponse::new(SessionId::new("session-1")))
242    //!         }, agent_client_protocol::on_receive_request!())
243    //!         // Handle prompts
244    //!         .on_receive_request(async |req: PromptRequest, responder, connection| {
245    //!             // Send streaming updates via notifications
246    //!             // connection.send_notification(SessionNotification { ... })?;
247    //!
248    //!             // Return final response
249    //!             responder.respond(PromptResponse::new(StopReason::EndTurn))
250    //!         }, agent_client_protocol::on_receive_request!())
251    //!         // Reject unknown messages
252    //!         .on_receive_dispatch(async |message: Dispatch, connection: ConnectionTo<Client>| {
253    //!             message.respond_with_error(agent_client_protocol::Error::method_not_found(), connection)
254    //!         }, agent_client_protocol::on_receive_dispatch!())
255    //!         .connect_to(transport)
256    //!         .await
257    //! }
258    //! ```
259    //!
260    //! # Streaming Responses
261    //!
262    //! To stream text or other updates to the client, send [`SessionNotification`]s
263    //! while processing a prompt:
264    //!
265    //! ```ignore
266    //! .on_receive_request(async |req: PromptRequest, responder, connection| {
267    //!     // Stream some text
268    //!     connection.send_notification(SessionNotification {
269    //!         session_id: req.session_id.clone(),
270    //!         update: SessionUpdate::Text(TextUpdate {
271    //!             text: "Hello, ".into(),
272    //!             // ...
273    //!         }),
274    //!         meta: None,
275    //!     })?;
276    //!
277    //!     connection.send_notification(SessionNotification {
278    //!         session_id: req.session_id.clone(),
279    //!         update: SessionUpdate::Text(TextUpdate {
280    //!             text: "world!".into(),
281    //!             // ...
282    //!         }),
283    //!         meta: None,
284    //!     })?;
285    //!
286    //!     responder.respond(PromptResponse {
287    //!         stop_reason: StopReason::EndTurn,
288    //!         meta: None,
289    //!     })
290    //! }, agent_client_protocol::on_receive_request!())
291    //! ```
292    //!
293    //! # Requesting Permissions
294    //!
295    //! Before taking actions that require user approval (like running commands
296    //! or writing files), send a [`RequestPermissionRequest`]:
297    //!
298    //! ```ignore
299    //! let response = connection.send_request(RequestPermissionRequest {
300    //!     session_id: session_id.clone(),
301    //!     action: PermissionAction::Bash { command: "rm -rf /".into() },
302    //!     options: vec![
303    //!         PermissionOption { id: "allow".into(), label: "Allow".into() },
304    //!         PermissionOption { id: "deny".into(), label: "Deny".into() },
305    //!     ],
306    //!     meta: None,
307    //! }).block_task().await?;
308    //!
309    //! match response.outcome {
310    //!     RequestPermissionOutcome::Selected { option_id } if option_id == "allow" => {
311    //!         // User approved, proceed with action
312    //!     }
313    //!     _ => {
314    //!         // User denied or cancelled
315    //!     }
316    //! }
317    //! ```
318    //!
319    //! # As a Reusable Component
320    //!
321    //! For agents that will be composed with proxies, implement [`ConnectTo`].
322    //! See [`reusable_components`] for the pattern.
323    //!
324    //! [`InitializeRequest`]: agent_client_protocol::schema::InitializeRequest
325    //! [`NewSessionRequest`]: agent_client_protocol::schema::NewSessionRequest
326    //! [`PromptRequest`]: agent_client_protocol::schema::PromptRequest
327    //! [`SessionNotification`]: agent_client_protocol::schema::SessionNotification
328    //! [`RequestPermissionRequest`]: agent_client_protocol::schema::RequestPermissionRequest
329    //! [`Agent`]: agent_client_protocol::Agent
330    //! [`ConnectTo`]: agent_client_protocol::ConnectTo
331    //! [`reusable_components`]: super::reusable_components
332}
333
334pub mod reusable_components {
335    //! Pattern: Defining reusable components.
336    //!
337    //! When building agents or proxies that will be composed together (for example,
338    //! with [`agent-client-protocol-conductor`]), define a struct that implements [`ConnectTo`].
339    //! This allows your component to be connected to other components in a type-safe way.
340    //!
341    //! # Example
342    //!
343    //! ```
344    //! use agent_client_protocol::{ConnectTo, Agent, Client};
345    //! use agent_client_protocol::schema::{
346    //!     InitializeRequest, InitializeResponse, AgentCapabilities,
347    //! };
348    //!
349    //! struct MyAgent {
350    //!     name: String,
351    //! }
352    //!
353    //! impl ConnectTo<Client> for MyAgent {
354    //!     async fn connect_to(self, client: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
355    //!         Agent.builder()
356    //!             .name(&self.name)
357    //!             .on_receive_request(async move |req: InitializeRequest, responder, _connection| {
358    //!                 responder.respond(
359    //!                     InitializeResponse::new(req.protocol_version)
360    //!                         .agent_capabilities(AgentCapabilities::new())
361    //!                 )
362    //!             }, agent_client_protocol::on_receive_request!())
363    //!             .connect_to(client)
364    //!             .await
365    //!     }
366    //! }
367    //!
368    //! let agent = MyAgent { name: "my-agent".into() };
369    //! ```
370    //!
371    //! # Important: Don't block the event loop
372    //!
373    //! Message handlers run on the event loop. Blocking in a handler prevents the
374    //! connection from processing new messages. For expensive work:
375    //!
376    //! - Use [`ConnectionTo::spawn`] to offload work to a background task
377    //! - Use [`on_receiving_result`] to schedule work when a response arrives
378    //!
379    //! [`ConnectTo`]: agent_client_protocol::ConnectTo
380    //! [`ConnectionTo::spawn`]: agent_client_protocol::ConnectionTo::spawn
381    //! [`on_receiving_result`]: agent_client_protocol::SentRequest::on_receiving_result
382    //! [`agent-client-protocol-conductor`]: https://crates.io/crates/agent-client-protocol-conductor
383}
384
385pub mod custom_message_handlers {
386    //! Pattern: Custom message handlers.
387    //!
388    //! For reusable message handling logic, implement [`HandleDispatchFrom`] and use
389    //! [`MatchDispatch`] or [`MatchDispatchFrom`] for type-safe dispatching.
390    //!
391    //! This is useful when you need to:
392    //! - Share message handling logic across multiple components
393    //! - Build complex routing logic that doesn't fit the builder pattern
394    //! - Integrate with existing handler infrastructure
395    //!
396    //! # Example
397    //!
398    //! ```
399    //! use agent_client_protocol::{HandleDispatchFrom, Dispatch, Handled, ConnectionTo, UntypedRole};
400    //! use agent_client_protocol::schema::{InitializeRequest, InitializeResponse, AgentCapabilities};
401    //! use agent_client_protocol::util::MatchDispatch;
402    //!
403    //! struct MyHandler;
404    //!
405    //! impl HandleDispatchFrom<UntypedRole> for MyHandler {
406    //!     async fn handle_dispatch_from(
407    //!         &mut self,
408    //!         message: Dispatch,
409    //!         _connection: ConnectionTo<UntypedRole>,
410    //!     ) -> Result<Handled<Dispatch>, agent_client_protocol::Error> {
411    //!         MatchDispatch::new(message)
412    //!             .if_request(async |req: InitializeRequest, responder| {
413    //!                 responder.respond(
414    //!                     InitializeResponse::new(req.protocol_version)
415    //!                         .agent_capabilities(AgentCapabilities::new())
416    //!                 )
417    //!             })
418    //!             .await
419    //!             .done()
420    //!     }
421    //!
422    //!     fn describe_chain(&self) -> impl std::fmt::Debug {
423    //!         "MyHandler"
424    //!     }
425    //! }
426    //! ```
427    //!
428    //! # When to use `MatchDispatch` vs `MatchDispatchFrom`
429    //!
430    //! - [`MatchDispatch`] - Use when you don't need peer-aware handling
431    //! - [`MatchDispatchFrom`] - Use in proxies where messages come from different
432    //!   peers (`Client` vs `Agent`) and may need different handling
433    //!
434    //! [`HandleDispatchFrom`]: agent_client_protocol::HandleDispatchFrom
435    //! [`MatchDispatch`]: agent_client_protocol::util::MatchDispatch
436    //! [`MatchDispatchFrom`]: agent_client_protocol::util::MatchDispatchFrom
437}
438
439pub mod global_mcp_server {
440    //! Pattern: Global MCP server in handler chain.
441    //!
442    //! Use this pattern when you want a single MCP server that handles tool calls
443    //! for all sessions. The server is added to the connection's handler chain and
444    //! automatically injects itself into every `NewSessionRequest` that passes through.
445    //!
446    //! # When to use
447    //!
448    //! - The MCP server provides stateless tools (no per-session state needed)
449    //! - You want the simplest setup with minimal boilerplate
450    //! - Tools don't need access to session-specific context
451    //!
452    //! # Using the builder API
453    //!
454    //! The simplest way to create an MCP server is with [`McpServer::builder`]:
455    //!
456    //! ```
457    //! use agent_client_protocol::mcp_server::McpServer;
458    //! use agent_client_protocol::{ConnectTo, RunWithConnectionTo, Proxy, Conductor};
459    //! use schemars::JsonSchema;
460    //! use serde::{Deserialize, Serialize};
461    //!
462    //! #[derive(Debug, Deserialize, JsonSchema)]
463    //! struct EchoParams { message: String }
464    //!
465    //! #[derive(Debug, Serialize, JsonSchema)]
466    //! struct EchoOutput { echoed: String }
467    //!
468    //! // Build the MCP server with tools
469    //! let mcp_server = McpServer::builder("my-tools")
470    //!     .tool_fn("echo", "Echoes the input",
471    //!         async |params: EchoParams, _cx| {
472    //!             Ok(EchoOutput { echoed: params.message })
473    //!         },
474    //!         agent_client_protocol::tool_fn!())
475    //!     .build();
476    //!
477    //! // The proxy component is generic over the MCP server's responder type
478    //! struct MyProxy<R> {
479    //!     mcp_server: McpServer<Conductor, R>,
480    //! }
481    //!
482    //! impl<R: RunWithConnectionTo<Conductor> + Send + 'static> ConnectTo<Conductor> for MyProxy<R> {
483    //!     async fn connect_to(self, conductor: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
484    //!         Proxy.builder()
485    //!             .with_mcp_server(self.mcp_server)
486    //!             .connect_to(conductor)
487    //!             .await
488    //!     }
489    //! }
490    //!
491    //! let proxy = MyProxy { mcp_server };
492    //! ```
493    //!
494    //! # Using rmcp
495    //!
496    //! If you have an existing [rmcp](https://docs.rs/rmcp) server implementation,
497    //! use [`McpServer::from_rmcp`] from the `agent-client-protocol-rmcp` crate:
498    //!
499    //! ```
500    //! use rmcp::{ServerHandler, tool, tool_router, tool_handler};
501    //! use rmcp::handler::server::router::tool::ToolRouter;
502    //! use rmcp::handler::server::wrapper::Parameters;
503    //! use rmcp::model::*;
504    //! use agent_client_protocol::mcp_server::McpServer;
505    //! use agent_client_protocol::Conductor;
506    //! use agent_client_protocol_rmcp::McpServerExt;
507    //! use serde::{Deserialize, Serialize};
508    //!
509    //! #[derive(Debug, Serialize, Deserialize, schemars::JsonSchema)]
510    //! struct EchoParams {
511    //!     message: String,
512    //! }
513    //!
514    //! #[derive(Clone)]
515    //! struct MyMcpServer {
516    //!     tool_router: ToolRouter<Self>,
517    //! }
518    //!
519    //! impl MyMcpServer {
520    //!     fn new() -> Self {
521    //!         Self { tool_router: Self::tool_router() }
522    //!     }
523    //! }
524    //!
525    //! #[tool_router]
526    //! impl MyMcpServer {
527    //!     #[tool(description = "Echoes back the input message")]
528    //!     async fn echo(&self, Parameters(params): Parameters<EchoParams>) -> Result<CallToolResult, rmcp::ErrorData> {
529    //!         Ok(CallToolResult::success(vec![Content::text(format!("Echo: {}", params.message))]))
530    //!     }
531    //! }
532    //!
533    //! #[tool_handler]
534    //! impl ServerHandler for MyMcpServer {
535    //!     fn get_info(&self) -> ServerInfo {
536    //!         ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
537    //!             .with_protocol_version(ProtocolVersion::V_2024_11_05)
538    //!             .with_server_info(Implementation::from_build_env())
539    //!     }
540    //! }
541    //!
542    //! // Create an MCP server from the rmcp service
543    //! let mcp_server = McpServer::<Conductor, _>::from_rmcp("my-server", MyMcpServer::new);
544    //! ```
545    //!
546    //! The `from_rmcp` function takes a factory closure that creates a new server
547    //! instance. This allows each MCP connection to get a fresh server instance.
548    //!
549    //! # How it works
550    //!
551    //! When you call [`with_mcp_server`], the MCP server is added as a message
552    //! handler. It:
553    //!
554    //! 1. Intercepts `NewSessionRequest` messages and adds its `acp:UUID` URL to the
555    //!    request's `mcp_servers` list
556    //! 2. Passes the modified request through to the next handler
557    //! 3. Handles incoming MCP protocol messages (tool calls, etc.) for its URL
558    //!
559    //! [`McpServer::builder`]: agent_client_protocol::mcp_server::McpServer::builder
560    //! [`McpServer::from_rmcp`]: agent_client_protocol_rmcp::McpServerExt::from_rmcp
561    //! [`with_mcp_server`]: agent_client_protocol::Builder::with_mcp_server
562}
563
564pub mod per_session_mcp_server {
565    //! Pattern: Per-session MCP server with workspace context.
566    //!
567    //! Use this pattern when each session needs its own MCP server instance
568    //! with access to session-specific context like the working directory.
569    //!
570    //! # When to use
571    //!
572    //! - Tools need access to the session's working directory
573    //! - You want to track active sessions or maintain per-session state
574    //! - Tools need to customize behavior based on session parameters
575    //!
576    //! # Basic pattern with `on_proxy_session_start`
577    //!
578    //! The most common pattern intercepts [`NewSessionRequest`], extracts context,
579    //! creates a per-session MCP server, and uses [`on_proxy_session_start`] to
580    //! run code after the session is established:
581    //!
582    //! ```
583    //! use agent_client_protocol::mcp_server::McpServer;
584    //! use agent_client_protocol::schema::NewSessionRequest;
585    //! use agent_client_protocol::{Client, Proxy, Conductor, ConnectTo};
586    //!
587    //! async fn run_proxy(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
588    //!     Proxy.builder()
589    //!         .on_receive_request_from(Client, async move |request: NewSessionRequest, responder, connection| {
590    //!             // Extract session context from the request
591    //!             let workspace_path = request.cwd.clone();
592    //!
593    //!             // Create tools that capture the workspace path
594    //!             let mcp_server = McpServer::builder("workspace-tools")
595    //!                 .tool_fn("get_workspace", "Returns the session's workspace directory", {
596    //!                     async move |_params: (), _cx| {
597    //!                         Ok(workspace_path.display().to_string())
598    //!                     }
599    //!                 }, agent_client_protocol::tool_fn!())
600    //!                 .build();
601    //!
602    //!             // Build the session and run code after it starts
603    //!             connection.build_session_from(request)
604    //!                 .with_mcp_server(mcp_server)?
605    //!                 .on_proxy_session_start(responder, async move |session_id| {
606    //!                     // This callback runs after the session-id has been sent to the
607    //!                     // client but before any further messages from the client or agent
608    //!                     // related to this session have been processed.
609    //!                     //
610    //!                     // You can use this to store the `session_id` before processing
611    //!                     // future messages, or to send a first prompt to the agent before
612    //!                     // the client has a chance to do so.
613    //!                     tracing::info!(%session_id, "Session started");
614    //!                     Ok(())
615    //!                 })
616    //!         }, agent_client_protocol::on_receive_request!())
617    //!         .connect_to(transport)
618    //!         .await
619    //! }
620    //! ```
621    //!
622    //! # How `on_proxy_session_start` works
623    //!
624    //! [`on_proxy_session_start`] is the non-blocking way to set up a proxy session:
625    //!
626    //! 1. Sends `NewSessionRequest` to the agent
627    //! 2. When the response arrives, responds to the client automatically
628    //! 3. Sets up message proxying for the session
629    //! 4. Runs your callback with the `SessionId`
630    //!
631    //! The callback runs after the session is established but doesn't block
632    //! the message handler. This is ideal for proxies that just need to inject
633    //! tools and track sessions.
634    //!
635    //! # Alternative: blocking with `start_session_proxy`
636    //!
637    //! If you need the simpler blocking API (e.g., in a client context where
638    //! blocking is safe), use [`block_task`] + [`start_session_proxy`]:
639    //!
640    //! ```
641    //! # use agent_client_protocol::mcp_server::McpServer;
642    //! # use agent_client_protocol::schema::NewSessionRequest;
643    //! # use agent_client_protocol::{Client, Proxy, Conductor, ConnectTo};
644    //! # async fn run_proxy(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
645    //!     Proxy.builder()
646    //!         .on_receive_request_from(Client, async |request: NewSessionRequest, responder, connection| {
647    //!             let cwd = request.cwd.clone();
648    //!             let mcp_server = McpServer::builder("tools")
649    //!                 .tool_fn("get_cwd", "Returns working directory", {
650    //!                     async move |_params: (), _cx| Ok(cwd.display().to_string())
651    //!                 }, agent_client_protocol::tool_fn!())
652    //!                 .build();
653    //!
654    //!             let session_id = connection.build_session_from(request)
655    //!                 .with_mcp_server(mcp_server)?
656    //!                 .block_task()
657    //!                 .start_session_proxy(responder)
658    //!                 .await?;
659    //!
660    //!             tracing::info!(%session_id, "Session started");
661    //!             Ok(())
662    //!         }, agent_client_protocol::on_receive_request!())
663    //!         .connect_to(transport)
664    //!         .await
665    //! # }
666    //! ```
667    //!
668    //! For patterns where you need to interact with the session before proxying,
669    //! use [`start_session`] + [`proxy_remaining_messages`] instead.
670    //!
671    //! [`start_session`]: agent_client_protocol::SessionBuilder::start_session
672    //! [`proxy_remaining_messages`]: agent_client_protocol::ActiveSession::proxy_remaining_messages
673    //!
674    //! [`NewSessionRequest`]: agent_client_protocol::schema::NewSessionRequest
675    //! [`on_proxy_session_start`]: agent_client_protocol::SessionBuilder::on_proxy_session_start
676    //! [`block_task`]: agent_client_protocol::SessionBuilder::block_task
677    //! [`start_session_proxy`]: agent_client_protocol::SessionBuilder::start_session_proxy
678}
679
680pub mod filtering_tools {
681    //! Pattern: Filtering which tools are available.
682    //!
683    //! Use [`disable_tool`] and [`enable_tool`] to control which tools are
684    //! visible to clients. This is useful when:
685    //!
686    //! - Some tools should only be available in certain configurations
687    //! - You want to conditionally expose tools based on runtime settings
688    //! - You need to restrict access to sensitive tools
689    //!
690    //! # Disabling specific tools (deny-list)
691    //!
692    //! By default, all registered tools are enabled. Use [`disable_tool`] to
693    //! hide specific tools:
694    //!
695    //! ```
696    //! use agent_client_protocol::mcp_server::McpServer;
697    //! use agent_client_protocol::{Conductor, RunWithConnectionTo};
698    //! use schemars::JsonSchema;
699    //! use serde::Deserialize;
700    //!
701    //! #[derive(Debug, Deserialize, JsonSchema)]
702    //! struct Params {}
703    //!
704    //! fn build_server(enable_admin: bool) -> Result<McpServer<Conductor, impl RunWithConnectionTo<Conductor>>, agent_client_protocol::Error> {
705    //!     let mut builder = McpServer::builder("my-server")
706    //!         .tool_fn("echo", "Echo a message",
707    //!             async |_p: Params, _cx| Ok("echoed"),
708    //!             agent_client_protocol::tool_fn!())
709    //!         .tool_fn("admin", "Admin-only tool",
710    //!             async |_p: Params, _cx| Ok("admin action"),
711    //!             agent_client_protocol::tool_fn!());
712    //!
713    //!     // Conditionally disable the admin tool
714    //!     if !enable_admin {
715    //!         builder = builder.disable_tool("admin")?;
716    //!     }
717    //!
718    //!     Ok(builder.build())
719    //! }
720    //! ```
721    //!
722    //! Disabled tools:
723    //! - Don't appear in `list_tools` responses
724    //! - Return "tool not found" errors if called directly
725    //!
726    //! # Enabling only specific tools (allow-list)
727    //!
728    //! Use [`disable_all_tools`] followed by [`enable_tool`] to create an
729    //! allow-list where only explicitly enabled tools are available:
730    //!
731    //! ```
732    //! use agent_client_protocol::mcp_server::McpServer;
733    //! use agent_client_protocol::{Conductor, RunWithConnectionTo};
734    //! use schemars::JsonSchema;
735    //! use serde::Deserialize;
736    //!
737    //! #[derive(Debug, Deserialize, JsonSchema)]
738    //! struct Params {}
739    //!
740    //! fn build_restricted_server() -> Result<McpServer<Conductor, impl RunWithConnectionTo<Conductor>>, agent_client_protocol::Error> {
741    //!     McpServer::builder("restricted-server")
742    //!         .tool_fn("safe", "Safe operation",
743    //!             async |_p: Params, _cx| Ok("safe"),
744    //!             agent_client_protocol::tool_fn!())
745    //!         .tool_fn("dangerous", "Dangerous operation",
746    //!             async |_p: Params, _cx| Ok("danger!"),
747    //!             agent_client_protocol::tool_fn!())
748    //!         .tool_fn("experimental", "Experimental feature",
749    //!             async |_p: Params, _cx| Ok("experimental"),
750    //!             agent_client_protocol::tool_fn!())
751    //!         // Start with all tools disabled
752    //!         .disable_all_tools()
753    //!         // Only enable the safe tool
754    //!         .enable_tool("safe")
755    //!         .map(|b| b.build())
756    //! }
757    //! ```
758    //!
759    //! # Error handling
760    //!
761    //! Both [`enable_tool`] and [`disable_tool`] return `Result` and will error
762    //! if the tool name doesn't match any registered tool. This helps catch typos:
763    //!
764    //! ```
765    //! use agent_client_protocol::mcp_server::McpServer;
766    //! use agent_client_protocol::Conductor;
767    //!
768    //! // This will error because "ech" is not a registered tool
769    //! let result = McpServer::<Conductor, _>::builder("server")
770    //!     .disable_tool("ech");  // Typo! Should be "echo"
771    //!
772    //! assert!(result.is_err());
773    //! ```
774    //!
775    //! Calling enable/disable on an already enabled/disabled tool is not an error -
776    //! the operations are idempotent.
777    //!
778    //! [`disable_tool`]: agent_client_protocol::mcp_server::McpServerBuilder::disable_tool
779    //! [`enable_tool`]: agent_client_protocol::mcp_server::McpServerBuilder::enable_tool
780    //! [`disable_all_tools`]: agent_client_protocol::mcp_server::McpServerBuilder::disable_all_tools
781}
782
783pub mod running_proxies_with_conductor {
784    //! Pattern: Running proxies with the conductor.
785    //!
786    //! Proxies don't run standalone. To add an MCP server (or other proxy behavior)
787    //! to an existing agent, you need the **conductor** to orchestrate the connection.
788    //!
789    //! The conductor:
790    //! 1. Accepts connections from clients
791    //! 2. Chains your proxies together
792    //! 3. Connects to the final agent
793    //! 4. Routes messages through the entire chain
794    //!
795    //! # Using the `agent-client-protocol-conductor` binary
796    //!
797    //! The simplest way to run a proxy is with the [`agent-client-protocol-conductor`] binary.
798    //! Configure it with a JSON file:
799    //!
800    //! ```json
801    //! {
802    //!   "proxies": [
803    //!     { "command": ["cargo", "run", "--bin", "my-proxy"] }
804    //!   ],
805    //!   "agent": { "command": ["claude-code", "--agent"] }
806    //! }
807    //! ```
808    //!
809    //! Then run:
810    //!
811    //! ```bash
812    //! agent-client-protocol-conductor --config conductor.json
813    //! ```
814    //!
815    //! # Using the conductor as a library
816    //!
817    //! For more control, use [`agent-client-protocol-conductor`] as a library with the `ConductorImpl` type:
818    //!
819    //! ```ignore
820    //! use agent_client_protocol_conductor::{ConductorImpl, ProxiesAndAgent};
821    //!
822    //! // Define your proxy as a ConnectTo<Conductor>
823    //! let my_proxy = MyProxy::new();
824    //!
825    //! // Spawn the agent process
826    //! let agent_process = agent_client_protocol_tokio::spawn_process("claude-code", &["--agent"]).await?;
827    //!
828    //! // Create the conductor with your proxy chain
829    //! let conductor = ConductorImpl::new(ProxiesAndAgent {
830    //!     proxies: vec![Box::new(my_proxy)],
831    //!     agent: agent_process,
832    //! });
833    //!
834    //! // Run the conductor (it will accept client connections on stdin/stdout)
835    //! conductor.connect_to(client_transport).await?;
836    //! ```
837    //!
838    //! # Why can't I just connect my proxy directly to an agent?
839    //!
840    //! ACP uses a message envelope format for proxy chains. When a proxy sends a
841    //! message toward the agent, it gets wrapped in a [`SuccessorMessage`] envelope.
842    //! The conductor handles this wrapping/unwrapping automatically.
843    //!
844    //! If you connected directly to an agent, your proxy would send `SuccessorMessage`
845    //! envelopes that the agent doesn't understand.
846    //!
847    //! # Example: Complete proxy with conductor
848    //!
849    //! See the [`agent-client-protocol-conductor` tests] for complete working examples of proxies
850    //! running with the conductor.
851    //!
852    //! [`agent-client-protocol-conductor`]: https://crates.io/crates/agent-client-protocol-conductor
853    //! [`SuccessorMessage`]: agent_client_protocol::schema::SuccessorMessage
854    //! [`agent-client-protocol-conductor` tests]: https://github.com/anthropics/acp-rust-sdk/tree/main/src/agent-client-protocol-conductor/tests
855}