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//! - [`v2_one_shot_prompt`] - Send a draft-v2 prompt and wait for the independent idle update
19//! - [`connecting_as_client`] - More details on connection setup and permission handling
20//! - [`ordered_application_dispatch`] - Apply updates, response barriers, and closure on one executor
21//! - [`v2_session_coordination`] - Prototype shared resume/replay/close policy on one executor
22//!
23//! # Building Proxies
24//!
25//! A proxy sits between client and agent, intercepting and optionally modifying
26//! messages. The most common use case is adding MCP tools. Use
27//! [`Proxy.builder()`](agent_client_protocol::Proxy) for stable protocol v1
28//! proxy connections. With the core SDK's `unstable_protocol_v2` feature, use
29//! `Proxy.v2()` for a draft-v2-only proxy, or `Proxy.protocol_router()` to
30//! expose separate strict v1 and v2 implementations as one component.
31//!
32//! **Important:** Proxies don't run standalone—they need the [`agent-client-protocol-conductor`] to
33//! orchestrate the connection between client, proxies, and agent. See
34//! [`running_proxies_with_conductor`] for how to put the pieces together.
35//!
36//! - [`global_mcp_server`] - Add tools that work across all sessions
37//! - [`per_session_mcp_server`] - Add tools with session-specific state
38//! - [`filtering_tools`] - Enable or disable tools dynamically
39//! - [`reusable_components`] - Package your proxy as a [`ConnectTo`] for composition
40//! - [`running_proxies_with_conductor`] - Run your proxy with an agent
41//!
42//! [`agent-client-protocol-conductor`]: https://crates.io/crates/agent-client-protocol-conductor
43//!
44//! # Building Agents
45//!
46//! An agent receives prompts and generates responses. Use [`Agent.builder()`](agent_client_protocol::Agent)
47//! to build agent connections.
48//!
49//! - [`building_an_agent`] - Handle initialization, sessions, and prompts
50//! - [`reusable_components`] - Package your agent as a [`ConnectTo`]
51//! - [`custom_message_handlers`] - Fine-grained control over message routing
52//!
53//! [`agent_client_protocol::concepts`]: agent_client_protocol::concepts
54//! [`Client`]: agent_client_protocol::Client
55//! [`Agent`]: agent_client_protocol::Agent
56//! [`Proxy`]: agent_client_protocol::Proxy
57//! [`ConnectTo`]: agent_client_protocol::ConnectTo
58
59pub mod ordered_application_dispatch;
60
61pub mod v2_session_coordination {
62    //! Pattern: Coordinate shared draft-v2 resume, replay, abandonment, and close.
63    //!
64    //! This is a cookbook prototype for one application's single-threaded
65    //! executor, not a public SDK coordinator. The complete ownership, ordering,
66    //! failure, and limitation policy is documented in the
67    //! [Session Operation Coordination guide][guide]. The runnable
68    //! [`v2_session_coordination` source example][source] is the implementation;
69    //! it is linked rather than duplicated here.
70    //!
71    //! [guide]: https://agentclientprotocol.github.io/rust-sdk/session-operation-coordination.html
72    //! [source]: https://github.com/agentclientprotocol/rust-sdk/blob/main/src/agent-client-protocol/examples/v2_session_coordination.rs
73}
74
75pub mod one_shot_prompt {
76    //! Pattern: You Only Prompt Once.
77    //!
78    //! The simplest client pattern: connect to an agent, send one prompt, get the
79    //! response. This is useful for CLI tools, scripts, or any case where you just
80    //! need a single interaction with an agent.
81    //!
82    //! # Example
83    //!
84    //! ```
85    //! use agent_client_protocol::{Client, Agent, ConnectTo};
86    //! use agent_client_protocol::schema::{ProtocolVersion, v1::InitializeRequest};
87    //!
88    //! async fn ask_agent(
89    //!     transport: impl ConnectTo<Client> + 'static,
90    //!     prompt: &str,
91    //! ) -> Result<String, agent_client_protocol::Error> {
92    //!     Client.builder()
93    //!         .name("my-client")
94    //!         .connect_with(transport, async |connection| {
95    //!             // Initialize the connection
96    //!             connection.send_request(InitializeRequest::new(ProtocolVersion::V1))
97    //!                 .block_task().await?;
98    //!
99    //!             // Create a session, send prompt, read response
100    //!             let mut session = connection.build_session_cwd()?
101    //!                 .block_task()
102    //!                 .start_session()
103    //!                 .await?;
104    //!
105    //!             session.send_prompt(prompt)?;
106    //!             session.read_to_string().await
107    //!         })
108    //!         .await
109    //! }
110    //! ```
111    //!
112    //! # How it works
113    //!
114    //! 1. **[`connect_with`]** establishes the transport connection and runs your
115    //!    code while handling messages in the background
116    //! 2. **[`send_request`]** + **[`block_task`]** sends the initialize request
117    //!    and waits for the response
118    //! 3. **[`build_session_cwd`]** creates a session builder using the current working directory
119    //! 4. **[`start_session`]** sends the `NewSessionRequest` and returns an
120    //!    [`ActiveSession`] handle
121    //! 5. **[`send_prompt`]** queues the prompt to send to the agent
122    //! 6. **[`read_to_string`]** reads all text chunks until the agent finishes
123    //!
124    //! # Handling permission requests
125    //!
126    //! Most agents will ask for permission before taking actions like running
127    //! commands or writing files. See [`connecting_as_client`] for how to handle
128    //! [`RequestPermissionRequest`] messages.
129    //!
130    //! [`connect_with`]: agent_client_protocol::Builder::connect_with
131    //! [`send_request`]: agent_client_protocol::ConnectionTo::send_request
132    //! [`block_task`]: agent_client_protocol::SentRequest::block_task
133    //! [`build_session_cwd`]: agent_client_protocol::ConnectionTo::build_session_cwd
134    //! [`start_session`]: agent_client_protocol::SessionBuilder::start_session
135    //! [`ActiveSession`]: agent_client_protocol::ActiveSession
136    //! [`send_prompt`]: agent_client_protocol::ActiveSession::send_prompt
137    //! [`read_to_string`]: agent_client_protocol::ActiveSession::read_to_string
138    //! [`connecting_as_client`]: super::connecting_as_client
139    //! [`RequestPermissionRequest`]: agent_client_protocol::schema::v1::RequestPermissionRequest
140}
141
142pub mod v2_one_shot_prompt {
143    //! Pattern: One prompt with the draft protocol v2 lifecycle.
144    //!
145    //! A successful v2 `session/prompt` response acknowledges acceptance; it
146    //! does not contain output and does not mean the work is complete. Install
147    //! update handlers before connecting, then consume matching updates until
148    //! the new session reports `running` and subsequently reports `idle`.
149    //!
150    //! This module is compiled with the cookbook's v2 feature coverage. For a
151    //! runnable CLI pair, see the SDK's `simple_agent_v2` and
152    //! `v2_one_shot_client` examples.
153    //!
154    //! # Example
155    //!
156    //! ```
157    //! use std::collections::HashMap;
158    //! use agent_client_protocol::{Agent, Client, ConnectTo, Error, Responder, V2ConnectionTo};
159    //! use agent_client_protocol::schema::{MaybeUndefined, ProtocolVersion, v2};
160    //! use futures::{StreamExt, channel::mpsc};
161    //!
162    //! #[derive(Default)]
163    //! struct AgentTextProjection {
164    //!     order: Vec<v2::MessageId>,
165    //!     messages: HashMap<v2::MessageId, Vec<v2::ContentBlock>>,
166    //! }
167    //!
168    //! impl AgentTextProjection {
169    //!     fn apply(&mut self, update: v2::SessionUpdate) {
170    //!         match update {
171    //!             v2::SessionUpdate::AgentMessageChunk(chunk) => {
172    //!                 self.message_content(chunk.message_id).push(chunk.content);
173    //!             }
174    //!             v2::SessionUpdate::AgentMessage(message) => {
175    //!                 let content = self.message_content(message.message_id);
176    //!                 match message.content {
177    //!                     // Snapshots patch chunks accumulated for the same message ID.
178    //!                     MaybeUndefined::Undefined => {}
179    //!                     MaybeUndefined::Null => content.clear(),
180    //!                     MaybeUndefined::Value(replacement) => *content = replacement,
181    //!                 }
182    //!             }
183    //!             _ => {}
184    //!         }
185    //!     }
186    //!
187    //!     fn message_content(
188    //!         &mut self,
189    //!         message_id: v2::MessageId,
190    //!     ) -> &mut Vec<v2::ContentBlock> {
191    //!         if !self.messages.contains_key(&message_id) {
192    //!             self.order.push(message_id.clone());
193    //!         }
194    //!         self.messages.entry(message_id).or_default()
195    //!     }
196    //!
197    //!     fn text(&self) -> String {
198    //!         self.order
199    //!             .iter()
200    //!             .filter_map(|message_id| self.messages.get(message_id))
201    //!             .flatten()
202    //!             .filter_map(|content| match content {
203    //!                 v2::ContentBlock::Text(text) => Some(text.text.as_str()),
204    //!                 _ => None,
205    //!             })
206    //!             .collect()
207    //!     }
208    //! }
209    //!
210    //! async fn ask_agent(
211    //!     transport: impl ConnectTo<Client> + 'static,
212    //!     prompt: &str,
213    //! ) -> Result<String, Error> {
214    //!     let (update_tx, mut update_rx) = mpsc::unbounded();
215    //!
216    //!     Client.v2()
217    //!         .on_receive_notification(
218    //!             async move |update: v2::UpdateSessionNotification,
219    //!                         _connection: V2ConnectionTo<Agent>| {
220    //!                 update_tx
221    //!                     .unbounded_send(update)
222    //!                     .map_err(Error::into_internal_error)
223    //!             },
224    //!             agent_client_protocol::on_receive_notification!(),
225    //!         )
226    //!         .on_receive_request(
227    //!             async move |_request: v2::RequestPermissionRequest,
228    //!                         responder: Responder<v2::RequestPermissionResponse>,
229    //!                         _connection: V2ConnectionTo<Agent>| {
230    //!                 // This non-interactive recipe rejects permission requests.
231    //!                 responder.respond(v2::RequestPermissionResponse::new(
232    //!                     v2::RequestPermissionOutcome::Cancelled,
233    //!                 ))
234    //!             },
235    //!             agent_client_protocol::on_receive_request!(),
236    //!         )
237    //!         .connect_with(transport, async move |connection| {
238    //!             let initialized = connection
239    //!                 .send_request(v2::InitializeRequest::new(
240    //!                     ProtocolVersion::V2,
241    //!                     v2::Implementation::new("example-client", "0.1.0"),
242    //!                 ))
243    //!                 .block_task()
244    //!                 .await?;
245    //!             if initialized.capabilities.session.is_none() {
246    //!                 return Err(Error::invalid_params()
247    //!                     .data("agent did not advertise session support"));
248    //!             }
249    //!
250    //!             let session = connection
251    //!                 .build_session_cwd()?
252    //!                 .start_session()
253    //!                 .block_task()
254    //!                 .await?
255    //!                 .into_session();
256    //!
257    //!             // This response only means that the prompt was accepted.
258    //!             session.send_prompt(prompt).block_task().await?;
259    //!
260    //!             let mut projection = AgentTextProjection::default();
261    //!             let mut observed_running = false;
262    //!             while let Some(notification) = update_rx.next().await {
263    //!                 if &notification.session_id != session.session_id() {
264    //!                     continue;
265    //!                 }
266    //!                 match notification.update {
267    //!                     v2::SessionUpdate::StateUpdate(v2::StateUpdate::Running(_)) => {
268    //!                         observed_running = true;
269    //!                     }
270    //!                     v2::SessionUpdate::StateUpdate(v2::StateUpdate::Idle(_))
271    //!                         if observed_running =>
272    //!                     {
273    //!                         session.close().block_task().await?;
274    //!                         return Ok(projection.text());
275    //!                     }
276    //!                     update if observed_running => projection.apply(update),
277    //!                     _ => {}
278    //!                 }
279    //!             }
280    //!             Err(Error::internal_error()
281    //!                 .data("agent disconnected before the prompt ran to completion"))
282    //!         })
283    //!         .await
284    //! }
285    //! ```
286}
287
288pub mod connecting_as_client {
289    //! Pattern: Connecting as a client.
290    //!
291    //! To connect to an ACP agent and send requests, use [`connect_with`].
292    //! This runs your code while the connection handles incoming messages
293    //! in the background.
294    //!
295    //! # Basic Example
296    //!
297    //! ```
298    //! use agent_client_protocol::{Client, Agent, ConnectTo};
299    //! use agent_client_protocol::schema::{ProtocolVersion, v1::InitializeRequest};
300    //!
301    //! async fn connect_to_agent(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
302    //!     Client.builder()
303    //!         .name("my-client")
304    //!         .connect_with(transport, async |connection| {
305    //!             // Initialize the connection
306    //!             connection.send_request(InitializeRequest::new(ProtocolVersion::V1))
307    //!                 .block_task().await?;
308    //!
309    //!             // Create a session and send a prompt
310    //!             connection.build_session_cwd()?
311    //!                 .block_task()
312    //!                 .run_until(async |mut session| {
313    //!                     session.send_prompt("Hello, agent!")?;
314    //!                     let response = session.read_to_string().await?;
315    //!                     println!("Agent said: {}", response);
316    //!                     Ok(())
317    //!                 })
318    //!                 .await
319    //!         })
320    //!         .await
321    //! }
322    //! ```
323    //!
324    //! # Using the Session Builder
325    //!
326    //! The [`build_session`] method creates a [`SessionBuilder`] that handles
327    //! session creation and provides convenient methods for interacting with
328    //! the session:
329    //!
330    //! - [`send_prompt`] - Send a text prompt to the agent
331    //! - [`read_update`] - Read the next update (text chunk, tool call, etc.)
332    //! - [`read_to_string`] - Read all text until the turn ends
333    //!
334    //! With the core SDK's `unstable_mcp_over_acp` feature, the session builder
335    //! also supports adding MCP servers with [`with_mcp_server`].
336    //!
337    //! Existing stable-v1 sessions can be reopened with [`load_session`] to
338    //! replay history or [`resume_session`] to continue without replay. Their
339    //! blocking `start_session` returns a [`RestoredSession`] containing the
340    //! active session and complete operation response; `on_session_start`
341    //! delivers the same value to its callback. Use either restore operation
342    //! only after initialization advertises its matching capability.
343    //!
344    //! # Handling Permission Requests
345    //!
346    //! Agents may send [`RequestPermissionRequest`] to ask for user approval
347    //! before taking actions. Handle these with [`on_receive_request`]:
348    //!
349    //! ```ignore
350    //! Client.builder()
351    //!     .on_receive_request(async |req: RequestPermissionRequest, responder, _connection| {
352    //!         // Auto-approve by selecting the first option (YOLO mode)
353    //!         let option_id = req.options.first().map(|opt| opt.option_id.clone());
354    //!         responder.respond(RequestPermissionResponse::new(
355    //!             match option_id {
356    //!                 Some(id) => RequestPermissionOutcome::Selected(
357    //!                     SelectedPermissionOutcome::new(id),
358    //!                 ),
359    //!                 None => RequestPermissionOutcome::Cancelled,
360    //!             }
361    //!         ))
362    //!     }, agent_client_protocol::on_receive_request!())
363    //!     .connect_with(transport, async |connection| { /* ... */ })
364    //!     .await
365    //! ```
366    //!
367    //! # Note on `block_task`
368    //!
369    //! Using [`block_task`] is safe inside `connect_with` because its foreground
370    //! future runs alongside, rather than inside, the dispatch loop. The loop
371    //! continues processing messages (including the response you're waiting for)
372    //! while the foreground future waits.
373    //!
374    //! [`connect_with`]: agent_client_protocol::Builder::connect_with
375    //! [`block_task`]: agent_client_protocol::SentRequest::block_task
376    //! [`build_session`]: agent_client_protocol::ConnectionTo::build_session
377    //! [`load_session`]: agent_client_protocol::ConnectionTo::load_session
378    //! [`resume_session`]: agent_client_protocol::ConnectionTo::resume_session
379    //! [`SessionBuilder`]: agent_client_protocol::SessionBuilder
380    //! [`RestoredSession`]: agent_client_protocol::RestoredSession
381    //! [`send_prompt`]: agent_client_protocol::ActiveSession::send_prompt
382    //! [`read_update`]: agent_client_protocol::ActiveSession::read_update
383    //! [`read_to_string`]: agent_client_protocol::ActiveSession::read_to_string
384    //! [`with_mcp_server`]: agent_client_protocol::SessionBuilder::with_mcp_server
385    //! [`RequestPermissionRequest`]: agent_client_protocol::schema::v1::RequestPermissionRequest
386    //! [`on_receive_request`]: agent_client_protocol::Builder::on_receive_request
387}
388
389pub mod building_an_agent {
390    //! Pattern: Building an agent.
391    //!
392    //! An agent handles prompts and generates responses. At minimum, an agent must:
393    //!
394    //! 1. Handle [`InitializeRequest`] to establish the connection
395    //! 2. Handle [`NewSessionRequest`] to create sessions
396    //! 3. Handle [`PromptRequest`] to process prompts
397    //!
398    //! Use [`Agent.builder()`](agent_client_protocol::Agent) to build agent connections.
399    //!
400    //! # Minimal Example
401    //!
402    //! ```
403    //! use agent_client_protocol::{Agent, ConnectTo};
404    //! use agent_client_protocol::schema::v1::{
405    //!     InitializeRequest, InitializeResponse, AgentCapabilities,
406    //!     NewSessionRequest, NewSessionResponse, SessionId,
407    //!     PromptRequest, PromptResponse, StopReason,
408    //! };
409    //!
410    //! async fn run_agent(transport: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
411    //!     Agent.builder()
412    //!         .name("my-agent")
413    //!         // Handle initialization
414    //!         .on_receive_request(async |req: InitializeRequest, responder, _connection| {
415    //!             responder.respond(
416    //!                 InitializeResponse::new(req.protocol_version)
417    //!                     .agent_capabilities(AgentCapabilities::new())
418    //!             )
419    //!         }, agent_client_protocol::on_receive_request!())
420    //!         // Handle session creation
421    //!         .on_receive_request(async |req: NewSessionRequest, responder, _connection| {
422    //!             responder.respond(NewSessionResponse::new(SessionId::new("session-1")))
423    //!         }, agent_client_protocol::on_receive_request!())
424    //!         // Handle prompts
425    //!         .on_receive_request(async |req: PromptRequest, responder, connection| {
426    //!             // Send streaming updates via notifications
427    //!             // connection.send_notification(SessionNotification { ... })?;
428    //!
429    //!             // Return final response
430    //!             responder.respond(PromptResponse::new(StopReason::EndTurn))
431    //!         }, agent_client_protocol::on_receive_request!())
432    //!         // Unknown requests receive Method not found automatically;
433    //!         // unhandled notifications are ignored.
434    //!         .connect_to(transport)
435    //!         .await
436    //! }
437    //! ```
438    //!
439    //! # Streaming Responses
440    //!
441    //! To stream text or other updates to the client, send [`SessionNotification`]s
442    //! while processing a prompt:
443    //!
444    //! ```ignore
445    //! .on_receive_request(async |req: PromptRequest, responder, connection| {
446    //!     // Stream some text
447    //!     connection.send_notification(SessionNotification::new(
448    //!         req.session_id.clone(),
449    //!         SessionUpdate::AgentMessageChunk(ContentChunk::new("Hello, ".into())),
450    //!     ))?;
451    //!
452    //!     connection.send_notification(SessionNotification::new(
453    //!         req.session_id.clone(),
454    //!         SessionUpdate::AgentMessageChunk(ContentChunk::new("world!".into())),
455    //!     ))?;
456    //!
457    //!     responder.respond(PromptResponse::new(StopReason::EndTurn))
458    //! }, agent_client_protocol::on_receive_request!())
459    //! ```
460    //!
461    //! # Requesting Permissions
462    //!
463    //! Before taking actions that require user approval (like running commands
464    //! or writing files), send a [`RequestPermissionRequest`]:
465    //!
466    //! ```ignore
467    //! let response = connection.send_request(RequestPermissionRequest::new(
468    //!     session_id.clone(),
469    //!     ToolCallUpdate::new(
470    //!         "dangerous-command",
471    //!         ToolCallUpdateFields::new()
472    //!             .title("Run rm -rf /")
473    //!             .kind(ToolKind::Execute)
474    //!             .status(ToolCallStatus::Pending),
475    //!     ),
476    //!     vec![
477    //!         PermissionOption::new("allow", "Allow", PermissionOptionKind::AllowOnce),
478    //!         PermissionOption::new("deny", "Deny", PermissionOptionKind::RejectOnce),
479    //!     ],
480    //! )).block_task().await?;
481    //!
482    //! match response.outcome {
483    //!     RequestPermissionOutcome::Selected(selected) if selected.option_id == "allow" => {
484    //!         // User approved, proceed with action
485    //!     }
486    //!     _ => {
487    //!         // User denied or cancelled
488    //!     }
489    //! }
490    //! ```
491    //!
492    //! # As a Reusable Component
493    //!
494    //! For agents that will be composed with proxies, implement [`ConnectTo`].
495    //! See [`reusable_components`] for the pattern.
496    //!
497    //! [`InitializeRequest`]: agent_client_protocol::schema::v1::InitializeRequest
498    //! [`NewSessionRequest`]: agent_client_protocol::schema::v1::NewSessionRequest
499    //! [`PromptRequest`]: agent_client_protocol::schema::v1::PromptRequest
500    //! [`SessionNotification`]: agent_client_protocol::schema::v1::SessionNotification
501    //! [`RequestPermissionRequest`]: agent_client_protocol::schema::v1::RequestPermissionRequest
502    //! [`Agent`]: agent_client_protocol::Agent
503    //! [`ConnectTo`]: agent_client_protocol::ConnectTo
504    //! [`reusable_components`]: super::reusable_components
505}
506
507pub mod reusable_components {
508    //! Pattern: Defining reusable components.
509    //!
510    //! When building agents or proxies that will be composed together (for example,
511    //! with [`agent-client-protocol-conductor`]), define a struct that implements [`ConnectTo`].
512    //! This allows your component to be connected to other components in a type-safe way.
513    //!
514    //! # Example
515    //!
516    //! ```
517    //! use agent_client_protocol::{ConnectTo, Agent, Client};
518    //! use agent_client_protocol::schema::v1::{
519    //!     InitializeRequest, InitializeResponse, AgentCapabilities,
520    //! };
521    //!
522    //! struct MyAgent {
523    //!     name: String,
524    //! }
525    //!
526    //! impl ConnectTo<Client> for MyAgent {
527    //!     async fn connect_to(self, client: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
528    //!         Agent.builder()
529    //!             .name(&self.name)
530    //!             .on_receive_request(async move |req: InitializeRequest, responder, _connection| {
531    //!                 responder.respond(
532    //!                     InitializeResponse::new(req.protocol_version)
533    //!                         .agent_capabilities(AgentCapabilities::new())
534    //!                 )
535    //!             }, agent_client_protocol::on_receive_request!())
536    //!             .connect_to(client)
537    //!             .await
538    //!     }
539    //! }
540    //!
541    //! let agent = MyAgent { name: "my-agent".into() };
542    //! ```
543    //!
544    //! # Important: Don't block the event loop
545    //!
546    //! Message handlers run on the event loop. Blocking in a handler prevents the
547    //! connection from processing new messages:
548    //!
549    //! - Use [`ConnectionTo::spawn`] to offload work to a background task
550    //! - Use [`on_receiving_result`] for bounded, ordered response handling; if it
551    //!   must await later traffic, spawn that work from the callback and return
552    //!
553    //! [`ConnectTo`]: agent_client_protocol::ConnectTo
554    //! [`ConnectionTo::spawn`]: agent_client_protocol::ConnectionTo::spawn
555    //! [`on_receiving_result`]: agent_client_protocol::SentRequest::on_receiving_result
556    //! [`agent-client-protocol-conductor`]: https://crates.io/crates/agent-client-protocol-conductor
557}
558
559pub mod custom_message_handlers {
560    //! Pattern: Custom message handlers.
561    //!
562    //! For reusable message handling logic, implement [`HandleDispatchFrom`] and use
563    //! [`MatchDispatch`] or [`MatchDispatchFrom`] for type-safe dispatching.
564    //!
565    //! This is useful when you need to:
566    //! - Share message handling logic across multiple components
567    //! - Build complex routing logic that doesn't fit the builder pattern
568    //! - Integrate with existing handler infrastructure
569    //!
570    //! # Example
571    //!
572    //! ```
573    //! use agent_client_protocol::{HandleDispatchFrom, Dispatch, Handled, ConnectionTo, UntypedRole};
574    //! use agent_client_protocol::schema::v1::{AgentCapabilities, InitializeRequest, InitializeResponse};
575    //! use agent_client_protocol::util::MatchDispatch;
576    //!
577    //! struct MyHandler;
578    //!
579    //! impl HandleDispatchFrom<UntypedRole> for MyHandler {
580    //!     async fn handle_dispatch_from(
581    //!         &mut self,
582    //!         message: Dispatch,
583    //!         _connection: ConnectionTo<UntypedRole>,
584    //!     ) -> Result<Handled<Dispatch>, agent_client_protocol::Error> {
585    //!         MatchDispatch::new(message)
586    //!             .if_request(async |req: InitializeRequest, responder| {
587    //!                 responder.respond(
588    //!                     InitializeResponse::new(req.protocol_version)
589    //!                         .agent_capabilities(AgentCapabilities::new())
590    //!                 )
591    //!             })
592    //!             .await
593    //!             .done()
594    //!     }
595    //!
596    //!     fn describe_chain(&self) -> impl std::fmt::Debug {
597    //!         "MyHandler"
598    //!     }
599    //! }
600    //! ```
601    //!
602    //! # When to use `MatchDispatch` vs `MatchDispatchFrom`
603    //!
604    //! - [`MatchDispatch`] - Use when you don't need peer-aware handling
605    //! - [`MatchDispatchFrom`] - Use in proxies where messages come from different
606    //!   peers (`Client` vs `Agent`) and may need different handling
607    //!
608    //! [`HandleDispatchFrom`]: agent_client_protocol::HandleDispatchFrom
609    //! [`MatchDispatch`]: agent_client_protocol::util::MatchDispatch
610    //! [`MatchDispatchFrom`]: agent_client_protocol::util::MatchDispatchFrom
611}
612
613pub mod global_mcp_server {
614    //! Pattern: Global MCP server in handler chain.
615    //!
616    //! Use this pattern when you want a single MCP server that handles tool calls
617    //! for all sessions. The server is added to the connection's handler chain and
618    //! automatically injects itself into every supported session setup request.
619    //! This pattern requires the core SDK's `unstable_mcp_over_acp` feature (or
620    //! the rmcp crate's matching passthrough feature). Draft v2 additionally
621    //! requires `unstable_protocol_v2`.
622    //!
623    //! # When to use
624    //!
625    //! - The MCP server provides stateless tools (no per-session state needed)
626    //! - You want the simplest setup with minimal boilerplate
627    //! - Tools don't need access to session-specific context
628    //!
629    //! # Using the builder API
630    //!
631    //! The simplest way to create an MCP server is with [`McpServer::builder`]:
632    //!
633    //! ```
634    //! use agent_client_protocol::mcp_server::McpServer;
635    //! use agent_client_protocol_rmcp::McpServerExt;
636    //! use agent_client_protocol::{ConnectTo, RunWithConnectionTo, Proxy, Conductor};
637    //! use schemars::JsonSchema;
638    //! use serde::{Deserialize, Serialize};
639    //!
640    //! #[derive(Debug, Deserialize, JsonSchema)]
641    //! struct EchoParams { message: String }
642    //!
643    //! #[derive(Debug, Serialize, JsonSchema)]
644    //! struct EchoOutput { echoed: String }
645    //!
646    //! // Build the MCP server with tools
647    //! let mcp_server = McpServer::builder("my-tools")
648    //!     .tool_fn("echo", "Echoes the input",
649    //!         async |params: EchoParams, _cx| {
650    //!             Ok(EchoOutput { echoed: params.message })
651    //!         },
652    //!         agent_client_protocol::tool_fn!())
653    //!     .build();
654    //!
655    //! // The proxy component is generic over the MCP server's runner type
656    //! struct MyProxy<R> {
657    //!     mcp_server: McpServer<Conductor, R>,
658    //! }
659    //!
660    //! impl<R: RunWithConnectionTo<Conductor> + Send + 'static> ConnectTo<Conductor> for MyProxy<R> {
661    //!     async fn connect_to(self, conductor: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
662    //!         Proxy.builder()
663    //!             .with_mcp_server(self.mcp_server)
664    //!             .connect_to(conductor)
665    //!             .await
666    //!     }
667    //! }
668    //!
669    //! let proxy = MyProxy { mcp_server };
670    //! ```
671    //!
672    //! The example uses stable protocol v1. A draft v2 proxy selects its API
673    //! before attaching the server:
674    //!
675    //! ```rust,ignore
676    //! Proxy.v2()
677    //!     .with_mcp_server(mcp_server)
678    //!     .connect_to(conductor)
679    //!     .await?;
680    //! ```
681    //!
682    //! # Using rmcp
683    //!
684    //! If you have an existing [rmcp](https://docs.rs/rmcp) server implementation,
685    //! use [`McpServer::from_rmcp`] from the `agent-client-protocol-rmcp` crate:
686    //!
687    //! ```
688    //! use rmcp::{ServerHandler, tool, tool_router, tool_handler};
689    //! use rmcp::handler::server::router::tool::ToolRouter;
690    //! use rmcp::handler::server::wrapper::Parameters;
691    //! use rmcp::model::*;
692    //! use agent_client_protocol::mcp_server::McpServer;
693    //! use agent_client_protocol::Conductor;
694    //! use agent_client_protocol_rmcp::McpServerExt;
695    //! use serde::{Deserialize, Serialize};
696    //!
697    //! #[derive(Debug, Serialize, Deserialize, schemars::JsonSchema)]
698    //! struct EchoParams {
699    //!     message: String,
700    //! }
701    //!
702    //! #[derive(Clone)]
703    //! struct MyMcpServer {
704    //!     tool_router: ToolRouter<Self>,
705    //! }
706    //!
707    //! impl MyMcpServer {
708    //!     fn new() -> Self {
709    //!         Self { tool_router: Self::tool_router() }
710    //!     }
711    //! }
712    //!
713    //! #[tool_router]
714    //! impl MyMcpServer {
715    //!     #[tool(description = "Echoes back the input message")]
716    //!     async fn echo(&self, Parameters(params): Parameters<EchoParams>) -> Result<CallToolResult, rmcp::ErrorData> {
717    //!         Ok(CallToolResult::success(vec![ContentBlock::text(format!("Echo: {}", params.message))]))
718    //!     }
719    //! }
720    //!
721    //! #[tool_handler]
722    //! impl ServerHandler for MyMcpServer {
723    //!     fn get_info(&self) -> ServerConfig {
724    //!         ServerConfig::new(ServerCapabilities::builder().enable_tools().build())
725    //!             .with_protocol_version(ProtocolVersion::V_2024_11_05)
726    //!             .with_server_info(Implementation::from_build_env())
727    //!     }
728    //! }
729    //!
730    //! // Create an MCP server from the rmcp service
731    //! let mcp_server = McpServer::<Conductor, _>::from_rmcp("my-server", MyMcpServer::new);
732    //! ```
733    //!
734    //! The `from_rmcp` factory initializes one reusable application service lazily
735    //! for native ACP requests. Standalone MCP connections retain per-connection
736    //! construction. Each native operation has its own logical ID, metadata,
737    //! notifications, and cancellation lifetime.
738    //!
739    //! # How it works
740    //!
741    //! When you call [`with_mcp_server`], the MCP server is added as a message
742    //! handler. It:
743    //!
744    //! 1. Intercepts session setup requests and adds a schema-native
745    //!    `McpServer::Acp` declaration with one connection-scoped server ID.
746    //!    V1 injects it into `session/new`, `session/load`, `session/resume`,
747    //!    and feature-gated `session/fork`; v2 injects it into
748    //!    `session/new`, `session/resume`, and feature-gated `session/fork`
749    //!    while preserving unrelated request fields
750    //! 2. Passes the modified request through to the next handler
751    //! 3. Handles independent `mcp/message` operations for that server ID, without
752    //!    an initialization prerequisite or connect/disconnect exchange
753    //!
754    //! [`McpServer::builder`]: agent_client_protocol_rmcp::McpServerExt::builder
755    //! [`McpServer::from_rmcp`]: agent_client_protocol_rmcp::McpServerExt::from_rmcp
756    //! [`with_mcp_server`]: agent_client_protocol::Builder::with_mcp_server
757}
758
759pub mod per_session_mcp_server {
760    //! Pattern: Per-session MCP server with workspace context.
761    //!
762    //! Use this pattern when each session needs its own MCP server instance
763    //! with access to session-specific context like the working directory.
764    //! It requires the core SDK's `unstable_mcp_over_acp` feature (or the rmcp
765    //! crate's matching passthrough feature). Draft v2 additionally requires
766    //! `unstable_protocol_v2`.
767    //!
768    //! # When to use
769    //!
770    //! - Tools need access to the session's working directory
771    //! - You want eventual active-session tracking that does not need to precede later traffic
772    //! - Tools need to customize behavior based on session parameters
773    //!
774    //! # Stable v1 pattern with `on_proxy_session_start`
775    //!
776    //! The most common pattern intercepts [`NewSessionRequest`], extracts context,
777    //! creates a per-session MCP server, and uses [`on_proxy_session_start`] to
778    //! run code after the session is established:
779    //!
780    //! ```
781    //! use agent_client_protocol::mcp_server::McpServer;
782    //! use agent_client_protocol_rmcp::McpServerExt;
783    //! use agent_client_protocol::schema::v1::NewSessionRequest;
784    //! use agent_client_protocol::{Client, Proxy, Conductor, ConnectTo};
785    //!
786    //! async fn run_proxy(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
787    //!     Proxy.builder()
788    //!         .on_receive_request_from(Client, async move |request: NewSessionRequest, responder, connection| {
789    //!             // Extract session context from the request
790    //!             let workspace_path = request.cwd.clone();
791    //!
792    //!             // Create tools that capture the workspace path
793    //!             let mcp_server = McpServer::builder("workspace-tools")
794    //!                 .tool_fn("get_workspace", "Returns the session's workspace directory", {
795    //!                     async move |_params: (), _cx| {
796    //!                         Ok(workspace_path.display().to_string())
797    //!                     }
798    //!                 }, agent_client_protocol::tool_fn!())
799    //!                 .build();
800    //!
801    //!             // Build the session and run code after it starts
802    //!             connection.build_session_from(request)
803    //!                 .with_mcp_server(mcp_server)?
804    //!                 .on_proxy_session_start(responder, async move |session_id| {
805    //!                     // Session proxying is installed before this callback is spawned.
806    //!                     //
807    //!                     // Use this for follow-up work that may wait for later connection
808    //!                     // traffic. Register ID-independent state in the request handler.
809    //!                     // For ID-keyed state, preinstall a gate that later handlers await,
810    //!                     // then populate it here.
811    //!                     tracing::info!(%session_id, "Session started");
812    //!                     Ok(())
813    //!                 })
814    //!         }, agent_client_protocol::on_receive_request!())
815    //!         .connect_to(transport)
816    //!         .await
817    //! }
818    //! ```
819    //!
820    //! # How `on_proxy_session_start` works
821    //!
822    //! [`on_proxy_session_start`] is the non-blocking way to set up a proxy session:
823    //!
824    //! 1. Sends `NewSessionRequest` to the agent
825    //! 2. When the response arrives, responds to the client automatically
826    //! 3. Sets up message proxying for the session
827    //! 4. Runs your callback with the `SessionId`
828    //!
829    //! The callback runs after the session is established but doesn't block
830    //! the message handler. It is suitable for follow-up work and eventual
831    //! session tracking. Because it runs concurrently with later traffic, it
832    //! does not guarantee that bookkeeping keyed by `SessionId` completes
833    //! first. Register ID-independent state before calling the helper. For
834    //! ID-keyed state, preinstall a gate or placeholder that later handlers
835    //! await, then populate it from the callback.
836    //!
837    //! # Draft v2 pattern
838    //!
839    //! `Proxy.v2()` exposes the same non-blocking setup shape with v2 schema
840    //! types. `V2SessionBuilder` handles `session/new`, while
841    //! `V2ResumeSessionBuilder` handles `session/resume`. With
842    //! `unstable_session_fork`, `V2ForkSessionBuilder` handles `session/fork`.
843    //! Their `on_proxy_session_start` callbacks receive an `OpenedV2Session`,
844    //! not just a session ID, so they retain both the command-only handle and
845    //! the complete operation-specific response:
846    //!
847    //! ```rust,ignore
848    //! use agent_client_protocol::schema::v2;
849    //!
850    //! Proxy.v2()
851    //!     .on_receive_request_from(
852    //!         Client,
853    //!         async |request: v2::NewSessionRequest, responder, connection| {
854    //!             let workspace_path = request.cwd.clone();
855    //!             let mcp_server = build_workspace_server(workspace_path);
856    //!
857    //!             connection
858    //!                 .build_session_from(request)
859    //!                 .with_mcp_server(mcp_server)?
860    //!                 .on_proxy_session_start(responder, async move |opened| {
861    //!                     let (session, setup_response) = opened.into_parts();
862    //!                     tracing::info!(
863    //!                         session_id = %session.session_id(),
864    //!                         ?setup_response,
865    //!                         "Session started"
866    //!                     );
867    //!                     Ok(())
868    //!                 })
869    //!         },
870    //!         agent_client_protocol::on_receive_request!(),
871    //!     );
872    //! ```
873    //!
874    //! Resume uses the same terminal helper after constructing the per-session
875    //! server from the resume request:
876    //!
877    //! ```rust,ignore
878    //! Proxy.v2()
879    //!     .on_receive_request_from(
880    //!         Client,
881    //!         async |request: v2::ResumeSessionRequest, responder, connection| {
882    //!             let mcp_server = build_workspace_server(request.cwd.clone());
883    //!             connection
884    //!                 .resume_session_from(request)
885    //!                 .with_mcp_server(mcp_server)?
886    //!                 .on_proxy_session_start(responder, async move |opened| {
887    //!                     tracing::info!(session_id = %opened.session().session_id(), "Session resumed");
888    //!                     Ok(())
889    //!                 })
890    //!         },
891    //!         agent_client_protocol::on_receive_request!(),
892    //!     );
893    //! ```
894    //!
895    //! Fork uses the same terminal helper after
896    //! `connection.fork_session_from(request)`. Its returned handle and route
897    //! use the newly allocated ID from the complete `ForkSessionResponse`, not
898    //! the source session ID.
899    //!
900    //! All helpers forward upstream cancellation and the complete setup
901    //! response. New-session and fork routing are installed before later
902    //! inbound traffic; resume routing and MCP readiness are established
903    //! before the request is published so replay can precede its response. The
904    //! callback runs outside the ordering barrier. V2 updates and interactive
905    //! requests remain independent connection traffic.
906    //!
907    //! # Stable v1 alternative: spawning `start_session_proxy`
908    //!
909    //! If you need the linear [`start_session_proxy`] API, move it into a
910    //! spawned task. Awaiting it directly in the request handler would block
911    //! the dispatch loop that must receive the agent's response:
912    //!
913    //! ```
914    //! # use agent_client_protocol::mcp_server::McpServer;
915    //! # use agent_client_protocol_rmcp::McpServerExt;
916    //! # use agent_client_protocol::schema::v1::NewSessionRequest;
917    //! # use agent_client_protocol::{Client, Proxy, Conductor, ConnectTo};
918    //! # async fn run_proxy(transport: impl ConnectTo<Proxy>) -> Result<(), agent_client_protocol::Error> {
919    //!     Proxy.builder()
920    //!         .on_receive_request_from(Client, async |request: NewSessionRequest, responder, connection| {
921    //!             let cwd = request.cwd.clone();
922    //!             let mcp_server = McpServer::builder("tools")
923    //!                 .tool_fn("get_cwd", "Returns working directory", {
924    //!                     async move |_params: (), _cx| Ok(cwd.display().to_string())
925    //!                 }, agent_client_protocol::tool_fn!())
926    //!                 .build();
927    //!
928    //!             let task_connection = connection.clone();
929    //!             connection.spawn(async move {
930    //!                 let session_id = task_connection.build_session_from(request)
931    //!                     .with_mcp_server(mcp_server)?
932    //!                     .block_task()
933    //!                     .start_session_proxy(responder)
934    //!                     .await?;
935    //!
936    //!                 tracing::info!(%session_id, "Session started");
937    //!                 Ok(())
938    //!             })?;
939    //!             Ok(())
940    //!         }, agent_client_protocol::on_receive_request!())
941    //!         .connect_to(transport)
942    //!         .await
943    //! # }
944    //! ```
945    //!
946    //! For patterns where you need to interact with the session before proxying,
947    //! use [`start_session`] + [`proxy_remaining_messages`] instead.
948    //!
949    //! [`start_session`]: agent_client_protocol::SessionBuilder::start_session
950    //! [`proxy_remaining_messages`]: agent_client_protocol::ActiveSession::proxy_remaining_messages
951    //!
952    //! [`NewSessionRequest`]: agent_client_protocol::schema::v1::NewSessionRequest
953    //! [`on_proxy_session_start`]: agent_client_protocol::SessionBuilder::on_proxy_session_start
954    //! [`block_task`]: agent_client_protocol::SessionBuilder::block_task
955    //! [`start_session_proxy`]: agent_client_protocol::SessionBuilder::start_session_proxy
956}
957
958pub mod filtering_tools {
959    //! Pattern: Filtering which tools are available.
960    //!
961    //! Use [`disable_tool`] and [`enable_tool`] to control which tools are
962    //! visible to clients. This is useful when:
963    //!
964    //! - Some tools should only be available in certain configurations
965    //! - You want to conditionally expose tools based on runtime settings
966    //! - You need to restrict access to sensitive tools
967    //!
968    //! # Disabling specific tools (deny-list)
969    //!
970    //! By default, all registered tools are enabled. Use [`disable_tool`] to
971    //! hide specific tools:
972    //!
973    //! ```
974    //! use agent_client_protocol::mcp_server::McpServer;
975    //! use agent_client_protocol_rmcp::McpServerExt;
976    //! use agent_client_protocol::{Conductor, RunWithConnectionTo};
977    //! use schemars::JsonSchema;
978    //! use serde::Deserialize;
979    //!
980    //! #[derive(Debug, Deserialize, JsonSchema)]
981    //! struct Params {}
982    //!
983    //! fn build_server(enable_admin: bool) -> Result<McpServer<Conductor, impl RunWithConnectionTo<Conductor>>, agent_client_protocol::Error> {
984    //!     let mut builder = McpServer::builder("my-server")
985    //!         .tool_fn("echo", "Echo a message",
986    //!             async |_p: Params, _cx| Ok("echoed"),
987    //!             agent_client_protocol::tool_fn!())
988    //!         .tool_fn("admin", "Admin-only tool",
989    //!             async |_p: Params, _cx| Ok("admin action"),
990    //!             agent_client_protocol::tool_fn!());
991    //!
992    //!     // Conditionally disable the admin tool
993    //!     if !enable_admin {
994    //!         builder = builder.disable_tool("admin")?;
995    //!     }
996    //!
997    //!     Ok(builder.build())
998    //! }
999    //! ```
1000    //!
1001    //! Disabled tools:
1002    //! - Don't appear in `list_tools` responses
1003    //! - Return "tool not found" errors if called directly
1004    //!
1005    //! # Enabling only specific tools (allow-list)
1006    //!
1007    //! Use [`disable_all_tools`] followed by [`enable_tool`] to create an
1008    //! allow-list where only explicitly enabled tools are available:
1009    //!
1010    //! ```
1011    //! use agent_client_protocol::mcp_server::McpServer;
1012    //! use agent_client_protocol_rmcp::McpServerExt;
1013    //! use agent_client_protocol::{Conductor, RunWithConnectionTo};
1014    //! use schemars::JsonSchema;
1015    //! use serde::Deserialize;
1016    //!
1017    //! #[derive(Debug, Deserialize, JsonSchema)]
1018    //! struct Params {}
1019    //!
1020    //! fn build_restricted_server() -> Result<McpServer<Conductor, impl RunWithConnectionTo<Conductor>>, agent_client_protocol::Error> {
1021    //!     McpServer::builder("restricted-server")
1022    //!         .tool_fn("safe", "Safe operation",
1023    //!             async |_p: Params, _cx| Ok("safe"),
1024    //!             agent_client_protocol::tool_fn!())
1025    //!         .tool_fn("dangerous", "Dangerous operation",
1026    //!             async |_p: Params, _cx| Ok("danger!"),
1027    //!             agent_client_protocol::tool_fn!())
1028    //!         .tool_fn("experimental", "Experimental feature",
1029    //!             async |_p: Params, _cx| Ok("experimental"),
1030    //!             agent_client_protocol::tool_fn!())
1031    //!         // Start with all tools disabled
1032    //!         .disable_all_tools()
1033    //!         // Only enable the safe tool
1034    //!         .enable_tool("safe")
1035    //!         .map(|b| b.build())
1036    //! }
1037    //! ```
1038    //!
1039    //! # Error handling
1040    //!
1041    //! Both [`enable_tool`] and [`disable_tool`] return `Result` and will error
1042    //! if the tool name doesn't match any registered tool. This helps catch typos:
1043    //!
1044    //! ```
1045    //! use agent_client_protocol::mcp_server::McpServer;
1046    //! use agent_client_protocol_rmcp::McpServerExt;
1047    //! use agent_client_protocol::Conductor;
1048    //!
1049    //! // This will error because "ech" is not a registered tool
1050    //! let result = McpServer::<Conductor, _>::builder("server")
1051    //!     .disable_tool("ech");  // Typo! Should be "echo"
1052    //!
1053    //! assert!(result.is_err());
1054    //! ```
1055    //!
1056    //! Calling enable/disable on an already enabled/disabled tool is not an error -
1057    //! the operations are idempotent.
1058    //!
1059    //! [`disable_tool`]: agent_client_protocol_rmcp::McpServerBuilder::disable_tool
1060    //! [`enable_tool`]: agent_client_protocol_rmcp::McpServerBuilder::enable_tool
1061    //! [`disable_all_tools`]: agent_client_protocol_rmcp::McpServerBuilder::disable_all_tools
1062}
1063
1064pub mod running_proxies_with_conductor {
1065    //! Pattern: Running proxies with the conductor.
1066    //!
1067    //! Proxies don't run standalone. To add an MCP server (or other proxy behavior)
1068    //! to an existing agent, you need the **conductor** to orchestrate the connection.
1069    //!
1070    //! The conductor:
1071    //! 1. Accepts connections from clients
1072    //! 2. Chains your proxies together
1073    //! 3. Connects to the final agent
1074    //! 4. Routes messages through the entire chain
1075    //!
1076    //! # Using the `agent-client-protocol-conductor` binary
1077    //!
1078    //! The simplest way to run a proxy is with the [`agent-client-protocol-conductor`] binary.
1079    //! Pass the proxy commands followed by the final agent command:
1080    //!
1081    //! ```bash
1082    //! agent-client-protocol-conductor agent \
1083    //!   "cargo run --bin my-proxy" \
1084    //!   "claude-code --agent"
1085    //! ```
1086    //!
1087    //! # Using the conductor as a library
1088    //!
1089    //! For more control, use [`agent-client-protocol-conductor`] as a library with the `ConductorImpl` type:
1090    //!
1091    //! ```ignore
1092    //! use agent_client_protocol::{AcpAgent, ConnectTo};
1093    //! use agent_client_protocol_conductor::{ConductorImpl, ProxiesAndAgent};
1094    //!
1095    //! // Define your proxy as a ConnectTo<Conductor>
1096    //! let my_proxy = MyProxy::new();
1097    //!
1098    //! // Configure the agent process
1099    //! let agent = AcpAgent::from_args(["claude-code", "--agent"])?;
1100    //!
1101    //! // Create the conductor with your proxy chain
1102    //! let conductor = ConductorImpl::new_agent(
1103    //!     "my-conductor",
1104    //!     ProxiesAndAgent::new(agent).proxy(my_proxy),
1105    //! );
1106    //!
1107    //! // Run the conductor (it will accept client connections on stdin/stdout)
1108    //! conductor.connect_to(client_transport).await?;
1109    //! ```
1110    //!
1111    //! # Why can't I just connect my proxy directly to an agent?
1112    //!
1113    //! ACP uses a message envelope format for proxy chains. When a proxy sends a
1114    //! message toward the agent, it gets wrapped in a [`SuccessorMessage`] envelope.
1115    //! The conductor handles this wrapping/unwrapping automatically.
1116    //!
1117    //! If you connected directly to an agent, your proxy would send `SuccessorMessage`
1118    //! envelopes that the agent doesn't understand.
1119    //!
1120    //! # Example: Complete proxy with conductor
1121    //!
1122    //! See the [`agent-client-protocol-conductor` tests] for complete working examples of proxies
1123    //! running with the conductor.
1124    //!
1125    //! [`agent-client-protocol-conductor`]: https://crates.io/crates/agent-client-protocol-conductor
1126    //! [`SuccessorMessage`]: agent_client_protocol::schema::SuccessorMessage
1127    //! [`agent-client-protocol-conductor` tests]: https://github.com/agentclientprotocol/rust-sdk/tree/main/src/agent-client-protocol-conductor/tests
1128}