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