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::{ProtocolVersion, v1::InitializeRequest};
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::v1::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::{ProtocolVersion, v1::InitializeRequest};
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    //! With the core SDK's `unstable_mcp_over_acp` feature, the session builder
167    //! also supports adding MCP servers with [`with_mcp_server`].
168    //!
169    //! # Handling Permission Requests
170    //!
171    //! Agents may send [`RequestPermissionRequest`] to ask for user approval
172    //! before taking actions. Handle these with [`on_receive_request`]:
173    //!
174    //! ```ignore
175    //! Client.builder()
176    //!     .on_receive_request(async |req: RequestPermissionRequest, responder, _connection| {
177    //!         // Auto-approve by selecting the first option (YOLO mode)
178    //!         let option_id = req.options.first().map(|opt| opt.option_id.clone());
179    //!         responder.respond(RequestPermissionResponse::new(
180    //!             match option_id {
181    //!                 Some(id) => RequestPermissionOutcome::Selected(
182    //!                     SelectedPermissionOutcome::new(id),
183    //!                 ),
184    //!                 None => RequestPermissionOutcome::Cancelled,
185    //!             }
186    //!         ))
187    //!     }, agent_client_protocol::on_receive_request!())
188    //!     .connect_with(transport, async |connection| { /* ... */ })
189    //!     .await
190    //! ```
191    //!
192    //! # Note on `block_task`
193    //!
194    //! Using [`block_task`] is safe inside `connect_with` because its foreground
195    //! future runs alongside, rather than inside, the dispatch loop. The loop
196    //! continues processing messages (including the response you're waiting for)
197    //! while the foreground future waits.
198    //!
199    //! [`connect_with`]: agent_client_protocol::Builder::connect_with
200    //! [`block_task`]: agent_client_protocol::SentRequest::block_task
201    //! [`build_session`]: agent_client_protocol::ConnectionTo::build_session
202    //! [`SessionBuilder`]: agent_client_protocol::SessionBuilder
203    //! [`send_prompt`]: agent_client_protocol::ActiveSession::send_prompt
204    //! [`read_update`]: agent_client_protocol::ActiveSession::read_update
205    //! [`read_to_string`]: agent_client_protocol::ActiveSession::read_to_string
206    //! [`with_mcp_server`]: agent_client_protocol::SessionBuilder::with_mcp_server
207    //! [`RequestPermissionRequest`]: agent_client_protocol::schema::v1::RequestPermissionRequest
208    //! [`on_receive_request`]: agent_client_protocol::Builder::on_receive_request
209}
210
211pub mod building_an_agent {
212    //! Pattern: Building an agent.
213    //!
214    //! An agent handles prompts and generates responses. At minimum, an agent must:
215    //!
216    //! 1. Handle [`InitializeRequest`] to establish the connection
217    //! 2. Handle [`NewSessionRequest`] to create sessions
218    //! 3. Handle [`PromptRequest`] to process prompts
219    //!
220    //! Use [`Agent.builder()`](agent_client_protocol::Agent) to build agent connections.
221    //!
222    //! # Minimal Example
223    //!
224    //! ```
225    //! use agent_client_protocol::{Agent, ConnectTo};
226    //! use agent_client_protocol::schema::v1::{
227    //!     InitializeRequest, InitializeResponse, AgentCapabilities,
228    //!     NewSessionRequest, NewSessionResponse, SessionId,
229    //!     PromptRequest, PromptResponse, StopReason,
230    //! };
231    //!
232    //! async fn run_agent(transport: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
233    //!     Agent.builder()
234    //!         .name("my-agent")
235    //!         // Handle initialization
236    //!         .on_receive_request(async |req: InitializeRequest, responder, _connection| {
237    //!             responder.respond(
238    //!                 InitializeResponse::new(req.protocol_version)
239    //!                     .agent_capabilities(AgentCapabilities::new())
240    //!             )
241    //!         }, agent_client_protocol::on_receive_request!())
242    //!         // Handle session creation
243    //!         .on_receive_request(async |req: NewSessionRequest, responder, _connection| {
244    //!             responder.respond(NewSessionResponse::new(SessionId::new("session-1")))
245    //!         }, agent_client_protocol::on_receive_request!())
246    //!         // Handle prompts
247    //!         .on_receive_request(async |req: PromptRequest, responder, connection| {
248    //!             // Send streaming updates via notifications
249    //!             // connection.send_notification(SessionNotification { ... })?;
250    //!
251    //!             // Return final response
252    //!             responder.respond(PromptResponse::new(StopReason::EndTurn))
253    //!         }, agent_client_protocol::on_receive_request!())
254    //!         // Unknown requests receive Method not found automatically;
255    //!         // unhandled notifications are ignored.
256    //!         .connect_to(transport)
257    //!         .await
258    //! }
259    //! ```
260    //!
261    //! # Streaming Responses
262    //!
263    //! To stream text or other updates to the client, send [`SessionNotification`]s
264    //! while processing a prompt:
265    //!
266    //! ```ignore
267    //! .on_receive_request(async |req: PromptRequest, responder, connection| {
268    //!     // Stream some text
269    //!     connection.send_notification(SessionNotification::new(
270    //!         req.session_id.clone(),
271    //!         SessionUpdate::AgentMessageChunk(ContentChunk::new("Hello, ".into())),
272    //!     ))?;
273    //!
274    //!     connection.send_notification(SessionNotification::new(
275    //!         req.session_id.clone(),
276    //!         SessionUpdate::AgentMessageChunk(ContentChunk::new("world!".into())),
277    //!     ))?;
278    //!
279    //!     responder.respond(PromptResponse::new(StopReason::EndTurn))
280    //! }, agent_client_protocol::on_receive_request!())
281    //! ```
282    //!
283    //! # Requesting Permissions
284    //!
285    //! Before taking actions that require user approval (like running commands
286    //! or writing files), send a [`RequestPermissionRequest`]:
287    //!
288    //! ```ignore
289    //! let response = connection.send_request(RequestPermissionRequest::new(
290    //!     session_id.clone(),
291    //!     ToolCallUpdate::new(
292    //!         "dangerous-command",
293    //!         ToolCallUpdateFields::new()
294    //!             .title("Run rm -rf /")
295    //!             .kind(ToolKind::Execute)
296    //!             .status(ToolCallStatus::Pending),
297    //!     ),
298    //!     vec![
299    //!         PermissionOption::new("allow", "Allow", PermissionOptionKind::AllowOnce),
300    //!         PermissionOption::new("deny", "Deny", PermissionOptionKind::RejectOnce),
301    //!     ],
302    //! )).block_task().await?;
303    //!
304    //! match response.outcome {
305    //!     RequestPermissionOutcome::Selected(selected) if selected.option_id == "allow" => {
306    //!         // User approved, proceed with action
307    //!     }
308    //!     _ => {
309    //!         // User denied or cancelled
310    //!     }
311    //! }
312    //! ```
313    //!
314    //! # As a Reusable Component
315    //!
316    //! For agents that will be composed with proxies, implement [`ConnectTo`].
317    //! See [`reusable_components`] for the pattern.
318    //!
319    //! [`InitializeRequest`]: agent_client_protocol::schema::v1::InitializeRequest
320    //! [`NewSessionRequest`]: agent_client_protocol::schema::v1::NewSessionRequest
321    //! [`PromptRequest`]: agent_client_protocol::schema::v1::PromptRequest
322    //! [`SessionNotification`]: agent_client_protocol::schema::v1::SessionNotification
323    //! [`RequestPermissionRequest`]: agent_client_protocol::schema::v1::RequestPermissionRequest
324    //! [`Agent`]: agent_client_protocol::Agent
325    //! [`ConnectTo`]: agent_client_protocol::ConnectTo
326    //! [`reusable_components`]: super::reusable_components
327}
328
329pub mod reusable_components {
330    //! Pattern: Defining reusable components.
331    //!
332    //! When building agents or proxies that will be composed together (for example,
333    //! with [`agent-client-protocol-conductor`]), define a struct that implements [`ConnectTo`].
334    //! This allows your component to be connected to other components in a type-safe way.
335    //!
336    //! # Example
337    //!
338    //! ```
339    //! use agent_client_protocol::{ConnectTo, Agent, Client};
340    //! use agent_client_protocol::schema::v1::{
341    //!     InitializeRequest, InitializeResponse, AgentCapabilities,
342    //! };
343    //!
344    //! struct MyAgent {
345    //!     name: String,
346    //! }
347    //!
348    //! impl ConnectTo<Client> for MyAgent {
349    //!     async fn connect_to(self, client: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
350    //!         Agent.builder()
351    //!             .name(&self.name)
352    //!             .on_receive_request(async move |req: InitializeRequest, responder, _connection| {
353    //!                 responder.respond(
354    //!                     InitializeResponse::new(req.protocol_version)
355    //!                         .agent_capabilities(AgentCapabilities::new())
356    //!                 )
357    //!             }, agent_client_protocol::on_receive_request!())
358    //!             .connect_to(client)
359    //!             .await
360    //!     }
361    //! }
362    //!
363    //! let agent = MyAgent { name: "my-agent".into() };
364    //! ```
365    //!
366    //! # Important: Don't block the event loop
367    //!
368    //! Message handlers run on the event loop. Blocking in a handler prevents the
369    //! connection from processing new messages:
370    //!
371    //! - Use [`ConnectionTo::spawn`] to offload work to a background task
372    //! - Use [`on_receiving_result`] for bounded, ordered response handling; if it
373    //!   must await later traffic, spawn that work from the callback and return
374    //!
375    //! [`ConnectTo`]: agent_client_protocol::ConnectTo
376    //! [`ConnectionTo::spawn`]: agent_client_protocol::ConnectionTo::spawn
377    //! [`on_receiving_result`]: agent_client_protocol::SentRequest::on_receiving_result
378    //! [`agent-client-protocol-conductor`]: https://crates.io/crates/agent-client-protocol-conductor
379}
380
381pub mod custom_message_handlers {
382    //! Pattern: Custom message handlers.
383    //!
384    //! For reusable message handling logic, implement [`HandleDispatchFrom`] and use
385    //! [`MatchDispatch`] or [`MatchDispatchFrom`] for type-safe dispatching.
386    //!
387    //! This is useful when you need to:
388    //! - Share message handling logic across multiple components
389    //! - Build complex routing logic that doesn't fit the builder pattern
390    //! - Integrate with existing handler infrastructure
391    //!
392    //! # Example
393    //!
394    //! ```
395    //! use agent_client_protocol::{HandleDispatchFrom, Dispatch, Handled, ConnectionTo, UntypedRole};
396    //! use agent_client_protocol::schema::v1::{AgentCapabilities, InitializeRequest, InitializeResponse};
397    //! use agent_client_protocol::util::MatchDispatch;
398    //!
399    //! struct MyHandler;
400    //!
401    //! impl HandleDispatchFrom<UntypedRole> for MyHandler {
402    //!     async fn handle_dispatch_from(
403    //!         &mut self,
404    //!         message: Dispatch,
405    //!         _connection: ConnectionTo<UntypedRole>,
406    //!     ) -> Result<Handled<Dispatch>, agent_client_protocol::Error> {
407    //!         MatchDispatch::new(message)
408    //!             .if_request(async |req: InitializeRequest, responder| {
409    //!                 responder.respond(
410    //!                     InitializeResponse::new(req.protocol_version)
411    //!                         .agent_capabilities(AgentCapabilities::new())
412    //!                 )
413    //!             })
414    //!             .await
415    //!             .done()
416    //!     }
417    //!
418    //!     fn describe_chain(&self) -> impl std::fmt::Debug {
419    //!         "MyHandler"
420    //!     }
421    //! }
422    //! ```
423    //!
424    //! # When to use `MatchDispatch` vs `MatchDispatchFrom`
425    //!
426    //! - [`MatchDispatch`] - Use when you don't need peer-aware handling
427    //! - [`MatchDispatchFrom`] - Use in proxies where messages come from different
428    //!   peers (`Client` vs `Agent`) and may need different handling
429    //!
430    //! [`HandleDispatchFrom`]: agent_client_protocol::HandleDispatchFrom
431    //! [`MatchDispatch`]: agent_client_protocol::util::MatchDispatch
432    //! [`MatchDispatchFrom`]: agent_client_protocol::util::MatchDispatchFrom
433}
434
435pub mod global_mcp_server {
436    //! Pattern: Global MCP server in handler chain.
437    //!
438    //! Use this pattern when you want a single MCP server that handles tool calls
439    //! for all sessions. The server is added to the connection's handler chain and
440    //! automatically injects itself into every supported session setup request.
441    //! This pattern requires the core SDK's `unstable_mcp_over_acp` feature (or
442    //! the rmcp crate's matching passthrough feature).
443    //!
444    //! # When to use
445    //!
446    //! - The MCP server provides stateless tools (no per-session state needed)
447    //! - You want the simplest setup with minimal boilerplate
448    //! - Tools don't need access to session-specific context
449    //!
450    //! # Using the builder API
451    //!
452    //! The simplest way to create an MCP server is with [`McpServer::builder`]:
453    //!
454    //! ```
455    //! use agent_client_protocol::mcp_server::McpServer;
456    //! use agent_client_protocol_rmcp::McpServerExt;
457    //! use agent_client_protocol::{ConnectTo, RunWithConnectionTo, Proxy, Conductor};
458    //! use schemars::JsonSchema;
459    //! use serde::{Deserialize, Serialize};
460    //!
461    //! #[derive(Debug, Deserialize, JsonSchema)]
462    //! struct EchoParams { message: String }
463    //!
464    //! #[derive(Debug, Serialize, JsonSchema)]
465    //! struct EchoOutput { echoed: String }
466    //!
467    //! // Build the MCP server with tools
468    //! let mcp_server = McpServer::builder("my-tools")
469    //!     .tool_fn("echo", "Echoes the input",
470    //!         async |params: EchoParams, _cx| {
471    //!             Ok(EchoOutput { echoed: params.message })
472    //!         },
473    //!         agent_client_protocol::tool_fn!())
474    //!     .build();
475    //!
476    //! // The proxy component is generic over the MCP server's runner type
477    //! struct MyProxy<R> {
478    //!     mcp_server: McpServer<Conductor, R>,
479    //! }
480    //!
481    //! impl<R: RunWithConnectionTo<Conductor> + Send + 'static> ConnectTo<Conductor> for MyProxy<R> {
482    //!     async fn connect_to(self, conductor: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
483    //!         Proxy.builder()
484    //!             .with_mcp_server(self.mcp_server)
485    //!             .connect_to(conductor)
486    //!             .await
487    //!     }
488    //! }
489    //!
490    //! let proxy = MyProxy { mcp_server };
491    //! ```
492    //!
493    //! # Using rmcp
494    //!
495    //! If you have an existing [rmcp](https://docs.rs/rmcp) server implementation,
496    //! use [`McpServer::from_rmcp`] from the `agent-client-protocol-rmcp` crate:
497    //!
498    //! ```
499    //! use rmcp::{ServerHandler, tool, tool_router, tool_handler};
500    //! use rmcp::handler::server::router::tool::ToolRouter;
501    //! use rmcp::handler::server::wrapper::Parameters;
502    //! use rmcp::model::*;
503    //! use agent_client_protocol::mcp_server::McpServer;
504    //! use agent_client_protocol::Conductor;
505    //! use agent_client_protocol_rmcp::McpServerExt;
506    //! use serde::{Deserialize, Serialize};
507    //!
508    //! #[derive(Debug, Serialize, Deserialize, schemars::JsonSchema)]
509    //! struct EchoParams {
510    //!     message: String,
511    //! }
512    //!
513    //! #[derive(Clone)]
514    //! struct MyMcpServer {
515    //!     tool_router: ToolRouter<Self>,
516    //! }
517    //!
518    //! impl MyMcpServer {
519    //!     fn new() -> Self {
520    //!         Self { tool_router: Self::tool_router() }
521    //!     }
522    //! }
523    //!
524    //! #[tool_router]
525    //! impl MyMcpServer {
526    //!     #[tool(description = "Echoes back the input message")]
527    //!     async fn echo(&self, Parameters(params): Parameters<EchoParams>) -> Result<CallToolResult, rmcp::ErrorData> {
528    //!         Ok(CallToolResult::success(vec![ContentBlock::text(format!("Echo: {}", params.message))]))
529    //!     }
530    //! }
531    //!
532    //! #[tool_handler]
533    //! impl ServerHandler for MyMcpServer {
534    //!     fn get_info(&self) -> ServerInfo {
535    //!         ServerInfo::new(ServerCapabilities::builder().enable_tools().build())
536    //!             .with_protocol_version(ProtocolVersion::V_2024_11_05)
537    //!             .with_server_info(Implementation::from_build_env())
538    //!     }
539    //! }
540    //!
541    //! // Create an MCP server from the rmcp service
542    //! let mcp_server = McpServer::<Conductor, _>::from_rmcp("my-server", MyMcpServer::new);
543    //! ```
544    //!
545    //! The `from_rmcp` function takes a factory closure that creates a new server
546    //! instance. This allows each MCP connection to get a fresh server instance.
547    //!
548    //! # How it works
549    //!
550    //! When you call [`with_mcp_server`], the MCP server is added as a message
551    //! handler. It:
552    //!
553    //! 1. Intercepts session setup requests and adds a schema-native
554    //!    `McpServer::Acp` declaration with one connection-scoped server ID,
555    //!    reused in each request's `mcp_servers` list (`session/new`,
556    //!    `session/load`, `session/resume`, and feature-gated `session/fork`)
557    //! 2. Passes the modified request through to the next handler
558    //! 3. Handles `mcp/connect`, `mcp/message`, and `mcp/disconnect` for that server ID
559    //!
560    //! [`McpServer::builder`]: agent_client_protocol_rmcp::McpServerExt::builder
561    //! [`McpServer::from_rmcp`]: agent_client_protocol_rmcp::McpServerExt::from_rmcp
562    //! [`with_mcp_server`]: agent_client_protocol::Builder::with_mcp_server
563}
564
565pub mod per_session_mcp_server {
566    //! Pattern: Per-session MCP server with workspace context.
567    //!
568    //! Use this pattern when each session needs its own MCP server instance
569    //! with access to session-specific context like the working directory.
570    //! It requires the core SDK's `unstable_mcp_over_acp` feature (or the rmcp
571    //! crate's matching passthrough feature).
572    //!
573    //! # When to use
574    //!
575    //! - Tools need access to the session's working directory
576    //! - You want eventual active-session tracking that does not need to precede later traffic
577    //! - Tools need to customize behavior based on session parameters
578    //!
579    //! # Basic pattern with `on_proxy_session_start`
580    //!
581    //! The most common pattern intercepts [`NewSessionRequest`], extracts context,
582    //! creates a per-session MCP server, and uses [`on_proxy_session_start`] to
583    //! run code after the session is established:
584    //!
585    //! ```
586    //! use agent_client_protocol::mcp_server::McpServer;
587    //! use agent_client_protocol_rmcp::McpServerExt;
588    //! use agent_client_protocol::schema::v1::NewSessionRequest;
589    //! use agent_client_protocol::{Client, Proxy, Conductor, ConnectTo};
590    //!
591    //! async fn run_proxy(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
592    //!     Proxy.builder()
593    //!         .on_receive_request_from(Client, async move |request: NewSessionRequest, responder, connection| {
594    //!             // Extract session context from the request
595    //!             let workspace_path = request.cwd.clone();
596    //!
597    //!             // Create tools that capture the workspace path
598    //!             let mcp_server = McpServer::builder("workspace-tools")
599    //!                 .tool_fn("get_workspace", "Returns the session's workspace directory", {
600    //!                     async move |_params: (), _cx| {
601    //!                         Ok(workspace_path.display().to_string())
602    //!                     }
603    //!                 }, agent_client_protocol::tool_fn!())
604    //!                 .build();
605    //!
606    //!             // Build the session and run code after it starts
607    //!             connection.build_session_from(request)
608    //!                 .with_mcp_server(mcp_server)?
609    //!                 .on_proxy_session_start(responder, async move |session_id| {
610    //!                     // Session proxying is installed before this callback is spawned.
611    //!                     //
612    //!                     // Use this for follow-up work that may wait for later connection
613    //!                     // traffic. Register ID-independent state in the request handler.
614    //!                     // For ID-keyed state, preinstall a gate that later handlers await,
615    //!                     // then populate it here.
616    //!                     tracing::info!(%session_id, "Session started");
617    //!                     Ok(())
618    //!                 })
619    //!         }, agent_client_protocol::on_receive_request!())
620    //!         .connect_to(transport)
621    //!         .await
622    //! }
623    //! ```
624    //!
625    //! # How `on_proxy_session_start` works
626    //!
627    //! [`on_proxy_session_start`] is the non-blocking way to set up a proxy session:
628    //!
629    //! 1. Sends `NewSessionRequest` to the agent
630    //! 2. When the response arrives, responds to the client automatically
631    //! 3. Sets up message proxying for the session
632    //! 4. Runs your callback with the `SessionId`
633    //!
634    //! The callback runs after the session is established but doesn't block
635    //! the message handler. It is suitable for follow-up work and eventual
636    //! session tracking. Because it runs concurrently with later traffic, it
637    //! does not guarantee that bookkeeping keyed by `SessionId` completes
638    //! first. Register ID-independent state before calling the helper. For
639    //! ID-keyed state, preinstall a gate or placeholder that later handlers
640    //! await, then populate it from the callback.
641    //!
642    //! # Alternative: spawning `start_session_proxy`
643    //!
644    //! If you need the linear [`start_session_proxy`] API, move it into a
645    //! spawned task. Awaiting it directly in the request handler would block
646    //! the dispatch loop that must receive the agent's response:
647    //!
648    //! ```
649    //! # use agent_client_protocol::mcp_server::McpServer;
650    //! # use agent_client_protocol_rmcp::McpServerExt;
651    //! # use agent_client_protocol::schema::v1::NewSessionRequest;
652    //! # use agent_client_protocol::{Client, Proxy, Conductor, ConnectTo};
653    //! # async fn run_proxy(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
654    //!     Proxy.builder()
655    //!         .on_receive_request_from(Client, async |request: NewSessionRequest, responder, connection| {
656    //!             let cwd = request.cwd.clone();
657    //!             let mcp_server = McpServer::builder("tools")
658    //!                 .tool_fn("get_cwd", "Returns working directory", {
659    //!                     async move |_params: (), _cx| Ok(cwd.display().to_string())
660    //!                 }, agent_client_protocol::tool_fn!())
661    //!                 .build();
662    //!
663    //!             let task_connection = connection.clone();
664    //!             connection.spawn(async move {
665    //!                 let session_id = task_connection.build_session_from(request)
666    //!                     .with_mcp_server(mcp_server)?
667    //!                     .block_task()
668    //!                     .start_session_proxy(responder)
669    //!                     .await?;
670    //!
671    //!                 tracing::info!(%session_id, "Session started");
672    //!                 Ok(())
673    //!             })?;
674    //!             Ok(())
675    //!         }, agent_client_protocol::on_receive_request!())
676    //!         .connect_to(transport)
677    //!         .await
678    //! # }
679    //! ```
680    //!
681    //! For patterns where you need to interact with the session before proxying,
682    //! use [`start_session`] + [`proxy_remaining_messages`] instead.
683    //!
684    //! [`start_session`]: agent_client_protocol::SessionBuilder::start_session
685    //! [`proxy_remaining_messages`]: agent_client_protocol::ActiveSession::proxy_remaining_messages
686    //!
687    //! [`NewSessionRequest`]: agent_client_protocol::schema::v1::NewSessionRequest
688    //! [`on_proxy_session_start`]: agent_client_protocol::SessionBuilder::on_proxy_session_start
689    //! [`block_task`]: agent_client_protocol::SessionBuilder::block_task
690    //! [`start_session_proxy`]: agent_client_protocol::SessionBuilder::start_session_proxy
691}
692
693pub mod filtering_tools {
694    //! Pattern: Filtering which tools are available.
695    //!
696    //! Use [`disable_tool`] and [`enable_tool`] to control which tools are
697    //! visible to clients. This is useful when:
698    //!
699    //! - Some tools should only be available in certain configurations
700    //! - You want to conditionally expose tools based on runtime settings
701    //! - You need to restrict access to sensitive tools
702    //!
703    //! # Disabling specific tools (deny-list)
704    //!
705    //! By default, all registered tools are enabled. Use [`disable_tool`] to
706    //! hide specific tools:
707    //!
708    //! ```
709    //! use agent_client_protocol::mcp_server::McpServer;
710    //! use agent_client_protocol_rmcp::McpServerExt;
711    //! use agent_client_protocol::{Conductor, RunWithConnectionTo};
712    //! use schemars::JsonSchema;
713    //! use serde::Deserialize;
714    //!
715    //! #[derive(Debug, Deserialize, JsonSchema)]
716    //! struct Params {}
717    //!
718    //! fn build_server(enable_admin: bool) -> Result<McpServer<Conductor, impl RunWithConnectionTo<Conductor>>, agent_client_protocol::Error> {
719    //!     let mut builder = McpServer::builder("my-server")
720    //!         .tool_fn("echo", "Echo a message",
721    //!             async |_p: Params, _cx| Ok("echoed"),
722    //!             agent_client_protocol::tool_fn!())
723    //!         .tool_fn("admin", "Admin-only tool",
724    //!             async |_p: Params, _cx| Ok("admin action"),
725    //!             agent_client_protocol::tool_fn!());
726    //!
727    //!     // Conditionally disable the admin tool
728    //!     if !enable_admin {
729    //!         builder = builder.disable_tool("admin")?;
730    //!     }
731    //!
732    //!     Ok(builder.build())
733    //! }
734    //! ```
735    //!
736    //! Disabled tools:
737    //! - Don't appear in `list_tools` responses
738    //! - Return "tool not found" errors if called directly
739    //!
740    //! # Enabling only specific tools (allow-list)
741    //!
742    //! Use [`disable_all_tools`] followed by [`enable_tool`] to create an
743    //! allow-list where only explicitly enabled tools are available:
744    //!
745    //! ```
746    //! use agent_client_protocol::mcp_server::McpServer;
747    //! use agent_client_protocol_rmcp::McpServerExt;
748    //! use agent_client_protocol::{Conductor, RunWithConnectionTo};
749    //! use schemars::JsonSchema;
750    //! use serde::Deserialize;
751    //!
752    //! #[derive(Debug, Deserialize, JsonSchema)]
753    //! struct Params {}
754    //!
755    //! fn build_restricted_server() -> Result<McpServer<Conductor, impl RunWithConnectionTo<Conductor>>, agent_client_protocol::Error> {
756    //!     McpServer::builder("restricted-server")
757    //!         .tool_fn("safe", "Safe operation",
758    //!             async |_p: Params, _cx| Ok("safe"),
759    //!             agent_client_protocol::tool_fn!())
760    //!         .tool_fn("dangerous", "Dangerous operation",
761    //!             async |_p: Params, _cx| Ok("danger!"),
762    //!             agent_client_protocol::tool_fn!())
763    //!         .tool_fn("experimental", "Experimental feature",
764    //!             async |_p: Params, _cx| Ok("experimental"),
765    //!             agent_client_protocol::tool_fn!())
766    //!         // Start with all tools disabled
767    //!         .disable_all_tools()
768    //!         // Only enable the safe tool
769    //!         .enable_tool("safe")
770    //!         .map(|b| b.build())
771    //! }
772    //! ```
773    //!
774    //! # Error handling
775    //!
776    //! Both [`enable_tool`] and [`disable_tool`] return `Result` and will error
777    //! if the tool name doesn't match any registered tool. This helps catch typos:
778    //!
779    //! ```
780    //! use agent_client_protocol::mcp_server::McpServer;
781    //! use agent_client_protocol_rmcp::McpServerExt;
782    //! use agent_client_protocol::Conductor;
783    //!
784    //! // This will error because "ech" is not a registered tool
785    //! let result = McpServer::<Conductor, _>::builder("server")
786    //!     .disable_tool("ech");  // Typo! Should be "echo"
787    //!
788    //! assert!(result.is_err());
789    //! ```
790    //!
791    //! Calling enable/disable on an already enabled/disabled tool is not an error -
792    //! the operations are idempotent.
793    //!
794    //! [`disable_tool`]: agent_client_protocol_rmcp::McpServerBuilder::disable_tool
795    //! [`enable_tool`]: agent_client_protocol_rmcp::McpServerBuilder::enable_tool
796    //! [`disable_all_tools`]: agent_client_protocol_rmcp::McpServerBuilder::disable_all_tools
797}
798
799pub mod running_proxies_with_conductor {
800    //! Pattern: Running proxies with the conductor.
801    //!
802    //! Proxies don't run standalone. To add an MCP server (or other proxy behavior)
803    //! to an existing agent, you need the **conductor** to orchestrate the connection.
804    //!
805    //! The conductor:
806    //! 1. Accepts connections from clients
807    //! 2. Chains your proxies together
808    //! 3. Connects to the final agent
809    //! 4. Routes messages through the entire chain
810    //!
811    //! # Using the `agent-client-protocol-conductor` binary
812    //!
813    //! The simplest way to run a proxy is with the [`agent-client-protocol-conductor`] binary.
814    //! Pass the proxy commands followed by the final agent command:
815    //!
816    //! ```bash
817    //! agent-client-protocol-conductor agent \
818    //!   "cargo run --bin my-proxy" \
819    //!   "claude-code --agent"
820    //! ```
821    //!
822    //! # Using the conductor as a library
823    //!
824    //! For more control, use [`agent-client-protocol-conductor`] as a library with the `ConductorImpl` type:
825    //!
826    //! ```ignore
827    //! use agent_client_protocol::{AcpAgent, ConnectTo};
828    //! use agent_client_protocol_conductor::{ConductorImpl, ProxiesAndAgent};
829    //!
830    //! // Define your proxy as a ConnectTo<Conductor>
831    //! let my_proxy = MyProxy::new();
832    //!
833    //! // Configure the agent process
834    //! let agent = AcpAgent::from_args(["claude-code", "--agent"])?;
835    //!
836    //! // Create the conductor with your proxy chain
837    //! let conductor = ConductorImpl::new_agent(
838    //!     "my-conductor",
839    //!     ProxiesAndAgent::new(agent).proxy(my_proxy),
840    //! );
841    //!
842    //! // Run the conductor (it will accept client connections on stdin/stdout)
843    //! conductor.connect_to(client_transport).await?;
844    //! ```
845    //!
846    //! # Why can't I just connect my proxy directly to an agent?
847    //!
848    //! ACP uses a message envelope format for proxy chains. When a proxy sends a
849    //! message toward the agent, it gets wrapped in a [`SuccessorMessage`] envelope.
850    //! The conductor handles this wrapping/unwrapping automatically.
851    //!
852    //! If you connected directly to an agent, your proxy would send `SuccessorMessage`
853    //! envelopes that the agent doesn't understand.
854    //!
855    //! # Example: Complete proxy with conductor
856    //!
857    //! See the [`agent-client-protocol-conductor` tests] for complete working examples of proxies
858    //! running with the conductor.
859    //!
860    //! [`agent-client-protocol-conductor`]: https://crates.io/crates/agent-client-protocol-conductor
861    //! [`SuccessorMessage`]: agent_client_protocol::schema::SuccessorMessage
862    //! [`agent-client-protocol-conductor` tests]: https://github.com/agentclientprotocol/rust-sdk/tree/main/src/agent-client-protocol-conductor/tests
863}