turbomcp_client/lib.rs
1//! # `TurboMCP` Client
2//!
3//! MCP (Model Context Protocol) client implementation for connecting to MCP servers
4//! and consuming their capabilities (tools, prompts, resources, and sampling).
5//!
6//! ## Features
7//!
8//! - Connection management with automatic reconnection
9//! - Error handling and recovery mechanisms
10//! - Support for all MCP capabilities including bidirectional sampling
11//! - Elicitation response handling for server-initiated user input requests
12//! - Transport-agnostic design (works with any `Transport` implementation)
13//! - Type-safe protocol communication
14//! - Request/response correlation tracking
15//! - Timeout and cancellation support
16//! - Automatic capability negotiation
17//! - Handler support for server-initiated requests (sampling and elicitation)
18//!
19//! ## Architecture
20//!
21//! The client follows a layered architecture:
22//!
23//! ```text
24//! Application Layer
25//! ↓
26//! Client API (this crate)
27//! ↓
28//! Protocol Layer (turbomcp-protocol)
29//! ↓
30//! Transport Layer (turbomcp-transport)
31//! ```
32//!
33//! ## Usage
34//!
35//! ```rust,no_run
36//! use turbomcp_client::{Client, ClientBuilder};
37//! use turbomcp_transport::stdio::StdioTransport;
38//!
39//! # async fn example() -> turbomcp_core::Result<()> {
40//! // Create a client with stdio transport
41//! let transport = StdioTransport::new();
42//! let mut client = Client::new(transport);
43//!
44//! // Initialize connection and negotiate capabilities
45//! let result = client.initialize().await?;
46//! println!("Connected to: {}", result.server_info.name);
47//!
48//! // List and call tools
49//! let tools = client.list_tools().await?;
50//! for tool in tools {
51//! println!("Tool: {} - {}", tool.name, tool.description.as_deref().unwrap_or("No description"));
52//! }
53//!
54//! // Access resources
55//! let resources = client.list_resources().await?;
56//! for resource in resources {
57//! println!("Resource: {}", resource);
58//! }
59//! # Ok(())
60//! # }
61//! ```
62//!
63//! ## Elicitation Response Handling (New in 1.0.3)
64//!
65//! The client now supports handling server-initiated elicitation requests:
66//!
67//! ```rust,no_run
68//! use turbomcp_client::Client;
69//! use std::collections::HashMap;
70//!
71//! // Simple elicitation handling example
72//! async fn handle_server_elicitation() {
73//! // When server requests user input, you would:
74//! // 1. Present the schema to the user
75//! // 2. Collect their input
76//! // 3. Send response back to server
77//!
78//! let user_preferences: HashMap<String, String> = HashMap::new();
79//! // Your UI/CLI interaction logic here
80//! println!("Server requesting user preferences");
81//! }
82//! ```
83//!
84//! ## Sampling Support (New in 1.0.3)
85//!
86//! Handle server-initiated sampling requests for LLM capabilities:
87//!
88//! ```rust,no_run
89//! use turbomcp_client::Client;
90//! use turbomcp_client::sampling::SamplingHandler;
91//! use turbomcp_protocol::types::{CreateMessageRequest, CreateMessageResult};
92//! use async_trait::async_trait;
93//!
94//! #[derive(Debug)]
95//! struct MySamplingHandler {
96//! // Your LLM client would go here
97//! }
98//!
99//! #[async_trait]
100//! impl SamplingHandler for MySamplingHandler {
101//! async fn handle_create_message(
102//! &self,
103//! request: CreateMessageRequest
104//! ) -> Result<CreateMessageResult, Box<dyn std::error::Error + Send + Sync>> {
105//! // Forward to your LLM provider (OpenAI, Anthropic, etc.)
106//! // This enables the server to request LLM sampling through the client
107//!
108//! Ok(CreateMessageResult {
109//! role: turbomcp_protocol::types::Role::Assistant,
110//! content: turbomcp_protocol::types::Content::Text(
111//! turbomcp_protocol::types::TextContent {
112//! text: "Response from LLM".to_string(),
113//! annotations: None,
114//! meta: None,
115//! }
116//! ),
117//! model: Some("gpt-4".to_string()),
118//! stop_reason: Some("end_turn".to_string()),
119//! _meta: None,
120//! })
121//! }
122//! }
123//! ```
124//!
125//! ## Error Handling
126//!
127//! The client provides comprehensive error handling with automatic retry logic:
128//!
129//! ```rust,no_run
130//! # use turbomcp_client::Client;
131//! # use turbomcp_transport::stdio::StdioTransport;
132//! # async fn example() -> turbomcp_core::Result<()> {
133//! # let mut client = Client::new(StdioTransport::new());
134//! match client.call_tool("my_tool", None).await {
135//! Ok(result) => println!("Tool result: {:?}", result),
136//! Err(e) => eprintln!("Tool call failed: {}", e),
137//! }
138//! # Ok(())
139//! # }
140//! ```
141
142pub mod handlers;
143pub mod llm;
144pub mod plugins;
145pub mod sampling;
146
147use std::collections::HashMap;
148use std::sync::Arc;
149use std::sync::atomic::{AtomicU64, Ordering};
150use tokio::sync::Mutex;
151
152use turbomcp_core::{Error, PROTOCOL_VERSION, Result};
153use turbomcp_protocol::jsonrpc::{
154 JsonRpcMessage, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse, JsonRpcVersion,
155};
156use turbomcp_protocol::types::{
157 CallToolRequest,
158 CallToolResult,
159 ClientCapabilities as ProtocolClientCapabilities,
160 CompleteResult,
161 Content,
162 CreateMessageRequest,
163 EmptyResult,
164 GetPromptRequest,
165 GetPromptResult,
166 InitializeRequest,
167 InitializeResult as ProtocolInitializeResult,
168 ListPromptsResult,
169 ListResourceTemplatesResult,
170 ListResourcesResult,
171 ListRootsResult,
172 ListToolsResult,
173 LogLevel,
174 // Missing protocol method types
175 PingResult,
176 Prompt,
177 PromptInput,
178 ReadResourceRequest,
179 ReadResourceResult,
180 ServerCapabilities,
181 SetLevelRequest,
182 SetLevelResult,
183 SubscribeRequest,
184 Tool,
185 UnsubscribeRequest,
186};
187use turbomcp_transport::{Transport, TransportMessage};
188
189use crate::handlers::{
190 ElicitationHandler, HandlerRegistry, LogHandler, ProgressHandler, ResourceUpdateHandler,
191};
192use crate::sampling::SamplingHandler;
193
194/// Client capability configuration
195///
196/// Defines the capabilities that this client supports when connecting to MCP servers.
197/// These capabilities are sent during the initialization handshake to negotiate
198/// which features will be available during the session.
199///
200/// # Examples
201///
202/// ```
203/// use turbomcp_client::ClientCapabilities;
204///
205/// let capabilities = ClientCapabilities {
206/// tools: true,
207/// prompts: true,
208/// resources: true,
209/// sampling: false,
210/// };
211/// ```
212#[derive(Debug, Clone, Default)]
213pub struct ClientCapabilities {
214 /// Whether the client supports tool calling
215 pub tools: bool,
216
217 /// Whether the client supports prompts
218 pub prompts: bool,
219
220 /// Whether the client supports resources
221 pub resources: bool,
222
223 /// Whether the client supports sampling
224 pub sampling: bool,
225}
226
227/// JSON-RPC protocol handler for MCP communication
228///
229/// Handles request/response correlation, serialization, and protocol-level concerns.
230/// This is the missing abstraction layer between raw Transport and high-level Client APIs.
231#[derive(Debug)]
232struct ProtocolClient<T: Transport> {
233 transport: T,
234 next_id: AtomicU64,
235}
236
237impl<T: Transport> ProtocolClient<T> {
238 fn new(transport: T) -> Self {
239 Self {
240 transport,
241 next_id: AtomicU64::new(1),
242 }
243 }
244
245 /// Send JSON-RPC request and await typed response
246 async fn request<R: serde::de::DeserializeOwned>(
247 &mut self,
248 method: &str,
249 params: Option<serde_json::Value>,
250 ) -> Result<R> {
251 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
252 let request = JsonRpcRequest {
253 jsonrpc: JsonRpcVersion,
254 id: turbomcp_core::MessageId::from(id.to_string()),
255 method: method.to_string(),
256 params,
257 };
258
259 // Serialize and send
260 let payload = serde_json::to_vec(&request)
261 .map_err(|e| Error::protocol(format!("Failed to serialize request: {e}")))?;
262
263 let message = TransportMessage::new(
264 turbomcp_core::MessageId::from(format!("req-{id}")),
265 payload.into(),
266 );
267 self.transport
268 .send(message)
269 .await
270 .map_err(|e| Error::transport(format!("Transport send failed: {e}")))?;
271
272 // Receive and deserialize response
273 let response_msg = self
274 .transport
275 .receive()
276 .await
277 .map_err(|e| Error::transport(format!("Transport receive failed: {e}")))?
278 .ok_or_else(|| Error::transport("No response received".to_string()))?;
279
280 let response: JsonRpcResponse = serde_json::from_slice(&response_msg.payload)
281 .map_err(|e| Error::protocol(format!("Invalid JSON-RPC response: {e}")))?;
282
283 if let Some(error) = response.error() {
284 return Err(Error::rpc(error.code, &error.message));
285 }
286
287 let result = response
288 .result()
289 .ok_or_else(|| Error::protocol("Response missing result field".to_string()))?;
290
291 serde_json::from_value(result.clone())
292 .map_err(|e| Error::protocol(format!("Invalid response format: {e}")))
293 }
294
295 /// Send JSON-RPC notification (no response expected)
296 async fn notify(&mut self, method: &str, params: Option<serde_json::Value>) -> Result<()> {
297 let notification = JsonRpcNotification {
298 jsonrpc: JsonRpcVersion,
299 method: method.to_string(),
300 params,
301 };
302
303 let payload = serde_json::to_vec(¬ification)
304 .map_err(|e| Error::protocol(format!("Failed to serialize notification: {e}")))?;
305
306 let message = TransportMessage::new(
307 turbomcp_core::MessageId::from("notification"),
308 payload.into(),
309 );
310 self.transport
311 .send(message)
312 .await
313 .map_err(|e| Error::transport(format!("Transport send failed: {e}")))?;
314
315 Ok(())
316 }
317}
318
319/// MCP client for communicating with servers
320///
321/// The `Client` struct provides a beautiful, ergonomic interface for interacting with MCP servers.
322/// It handles all protocol complexity internally, exposing only clean, type-safe methods.
323///
324/// # Type Parameters
325///
326/// * `T` - The transport implementation used for communication
327///
328/// # Examples
329///
330/// ```rust,no_run
331/// use turbomcp_client::Client;
332/// use turbomcp_transport::stdio::StdioTransport;
333///
334/// # async fn example() -> turbomcp_core::Result<()> {
335/// let transport = StdioTransport::new();
336/// let mut client = Client::new(transport);
337///
338/// // Initialize and start using the client
339/// client.initialize().await?;
340/// # Ok(())
341/// # }
342/// ```
343#[derive(Debug)]
344pub struct Client<T: Transport> {
345 protocol: ProtocolClient<T>,
346 capabilities: ClientCapabilities,
347 initialized: bool,
348 #[allow(dead_code)]
349 sampling_handler: Option<Arc<dyn SamplingHandler>>,
350 /// Handler registry for bidirectional communication
351 handlers: HandlerRegistry,
352 /// Plugin registry for middleware and extensibility
353 plugin_registry: crate::plugins::PluginRegistry,
354}
355
356impl<T: Transport> Client<T> {
357 /// Create a new client with the specified transport
358 ///
359 /// Creates a new MCP client instance with default capabilities.
360 /// The client must be initialized before use by calling `initialize()`.
361 ///
362 /// # Arguments
363 ///
364 /// * `transport` - The transport implementation to use for communication
365 ///
366 /// # Examples
367 ///
368 /// ```rust,no_run
369 /// use turbomcp_client::Client;
370 /// use turbomcp_transport::stdio::StdioTransport;
371 ///
372 /// let transport = StdioTransport::new();
373 /// let client = Client::new(transport);
374 /// ```
375 pub fn new(transport: T) -> Self {
376 Self {
377 protocol: ProtocolClient::new(transport),
378 capabilities: ClientCapabilities::default(),
379 initialized: false,
380 sampling_handler: None,
381 handlers: HandlerRegistry::new(),
382 plugin_registry: crate::plugins::PluginRegistry::new(),
383 }
384 }
385
386 /// Create a new client with custom capabilities
387 ///
388 /// # Arguments
389 ///
390 /// * `transport` - The transport implementation to use
391 /// * `capabilities` - The client capabilities to negotiate
392 ///
393 /// # Examples
394 ///
395 /// ```rust,no_run
396 /// use turbomcp_client::{Client, ClientCapabilities};
397 /// use turbomcp_transport::stdio::StdioTransport;
398 ///
399 /// let capabilities = ClientCapabilities {
400 /// tools: true,
401 /// prompts: true,
402 /// resources: false,
403 /// sampling: false,
404 /// };
405 ///
406 /// let transport = StdioTransport::new();
407 /// let client = Client::with_capabilities(transport, capabilities);
408 /// ```
409 pub fn with_capabilities(transport: T, capabilities: ClientCapabilities) -> Self {
410 Self {
411 protocol: ProtocolClient::new(transport),
412 capabilities,
413 initialized: false,
414 sampling_handler: None,
415 handlers: HandlerRegistry::new(),
416 plugin_registry: crate::plugins::PluginRegistry::new(),
417 }
418 }
419
420 /// Set the sampling handler for processing server-initiated sampling requests
421 ///
422 /// # Arguments
423 ///
424 /// * `handler` - The handler implementation for sampling requests
425 ///
426 /// # Examples
427 ///
428 /// ```rust,no_run
429 /// use turbomcp_client::{Client, sampling::SamplingHandler};
430 /// use turbomcp_transport::stdio::StdioTransport;
431 /// use turbomcp_protocol::types::{CreateMessageRequest, CreateMessageResult};
432 /// use async_trait::async_trait;
433 /// use std::sync::Arc;
434 ///
435 /// #[derive(Debug)]
436 /// struct ExampleHandler;
437 ///
438 /// #[async_trait]
439 /// impl SamplingHandler for ExampleHandler {
440 /// async fn handle_create_message(
441 /// &self,
442 /// _request: CreateMessageRequest,
443 /// ) -> Result<CreateMessageResult, Box<dyn std::error::Error + Send + Sync>> {
444 /// // Handle sampling request
445 /// todo!("Implement sampling logic")
446 /// }
447 /// }
448 ///
449 /// let mut client = Client::new(StdioTransport::new());
450 /// client.set_sampling_handler(Arc::new(ExampleHandler));
451 /// ```
452 pub fn set_sampling_handler(&mut self, handler: Arc<dyn SamplingHandler>) {
453 self.sampling_handler = Some(handler);
454 self.capabilities.sampling = true;
455 }
456
457 /// Process incoming messages from the server
458 ///
459 /// This method should be called in a loop to handle server-initiated requests
460 /// like sampling. It processes one message at a time.
461 ///
462 /// # Returns
463 ///
464 /// Returns `Ok(true)` if a message was processed, `Ok(false)` if no message was available.
465 ///
466 /// # Examples
467 ///
468 /// ```rust,no_run
469 /// # use turbomcp_client::Client;
470 /// # use turbomcp_transport::stdio::StdioTransport;
471 /// # async fn example() -> turbomcp_core::Result<()> {
472 /// let mut client = Client::new(StdioTransport::new());
473 ///
474 /// // Process messages in background
475 /// tokio::spawn(async move {
476 /// loop {
477 /// if let Err(e) = client.process_message().await {
478 /// eprintln!("Error processing message: {}", e);
479 /// }
480 /// }
481 /// });
482 /// # Ok(())
483 /// # }
484 /// ```
485 pub async fn process_message(&mut self) -> Result<bool> {
486 // Try to receive a message without blocking
487 let message = match self.protocol.transport.receive().await {
488 Ok(Some(msg)) => msg,
489 Ok(None) => return Ok(false),
490 Err(e) => {
491 return Err(Error::transport(format!(
492 "Failed to receive message: {}",
493 e
494 )));
495 }
496 };
497
498 // Parse as JSON-RPC message
499 let json_msg: JsonRpcMessage = serde_json::from_slice(&message.payload)
500 .map_err(|e| Error::protocol(format!("Invalid JSON-RPC message: {}", e)))?;
501
502 match json_msg {
503 JsonRpcMessage::Request(request) => {
504 self.handle_request(request).await?;
505 Ok(true)
506 }
507 JsonRpcMessage::Response(_) => {
508 // Responses are handled by the protocol client during request/response flow
509 Ok(true)
510 }
511 JsonRpcMessage::Notification(notification) => {
512 self.handle_notification(notification).await?;
513 Ok(true)
514 }
515 JsonRpcMessage::RequestBatch(_)
516 | JsonRpcMessage::ResponseBatch(_)
517 | JsonRpcMessage::MessageBatch(_) => {
518 // Batch operations not yet supported
519 Ok(true)
520 }
521 }
522 }
523
524 async fn handle_request(&mut self, request: JsonRpcRequest) -> Result<()> {
525 match request.method.as_str() {
526 "sampling/createMessage" => {
527 if let Some(handler) = &self.sampling_handler {
528 let params: CreateMessageRequest =
529 serde_json::from_value(request.params.unwrap_or(serde_json::Value::Null))
530 .map_err(|e| {
531 Error::protocol(format!("Invalid createMessage params: {}", e))
532 })?;
533
534 match handler.handle_create_message(params).await {
535 Ok(result) => {
536 let result_value = serde_json::to_value(result).map_err(|e| {
537 Error::protocol(format!("Failed to serialize response: {}", e))
538 })?;
539 let response = JsonRpcResponse::success(result_value, request.id);
540 self.send_response(response).await?;
541 }
542 Err(e) => {
543 let error = turbomcp_protocol::jsonrpc::JsonRpcError {
544 code: -32603,
545 message: format!("Sampling handler error: {}", e),
546 data: None,
547 };
548 let response = JsonRpcResponse::error_response(error, request.id);
549 self.send_response(response).await?;
550 }
551 }
552 } else {
553 // No handler configured
554 let error = turbomcp_protocol::jsonrpc::JsonRpcError {
555 code: -32601,
556 message: "Sampling not supported".to_string(),
557 data: None,
558 };
559 let response = JsonRpcResponse::error_response(error, request.id);
560 self.send_response(response).await?;
561 }
562 }
563 _ => {
564 // Unknown method
565 let error = turbomcp_protocol::jsonrpc::JsonRpcError {
566 code: -32601,
567 message: format!("Method not found: {}", request.method),
568 data: None,
569 };
570 let response = JsonRpcResponse::error_response(error, request.id);
571 self.send_response(response).await?;
572 }
573 }
574 Ok(())
575 }
576
577 async fn handle_notification(&mut self, _notification: JsonRpcNotification) -> Result<()> {
578 // Handle notifications if needed
579 // Currently MCP doesn't define client-side notifications
580 Ok(())
581 }
582
583 async fn send_response(&mut self, response: JsonRpcResponse) -> Result<()> {
584 let payload = serde_json::to_vec(&response)
585 .map_err(|e| Error::protocol(format!("Failed to serialize response: {}", e)))?;
586
587 let message = TransportMessage::new(
588 turbomcp_core::MessageId::from("response".to_string()),
589 payload.into(),
590 );
591
592 self.protocol
593 .transport
594 .send(message)
595 .await
596 .map_err(|e| Error::transport(format!("Failed to send response: {}", e)))?;
597
598 Ok(())
599 }
600
601 /// Initialize the connection with the MCP server
602 ///
603 /// Performs the initialization handshake with the server, negotiating capabilities
604 /// and establishing the protocol version. This method must be called before
605 /// any other operations can be performed.
606 ///
607 /// # Returns
608 ///
609 /// Returns an `InitializeResult` containing server information and negotiated capabilities.
610 ///
611 /// # Errors
612 ///
613 /// Returns an error if:
614 /// - The transport connection fails
615 /// - The server rejects the initialization request
616 /// - Protocol negotiation fails
617 ///
618 /// # Examples
619 ///
620 /// ```rust,no_run
621 /// # use turbomcp_client::Client;
622 /// # use turbomcp_transport::stdio::StdioTransport;
623 /// # async fn example() -> turbomcp_core::Result<()> {
624 /// let mut client = Client::new(StdioTransport::new());
625 ///
626 /// let result = client.initialize().await?;
627 /// println!("Server: {} v{}", result.server_info.name, result.server_info.version);
628 /// # Ok(())
629 /// # }
630 /// ```
631 pub async fn initialize(&mut self) -> Result<InitializeResult> {
632 // Build client capabilities based on configuration
633 let mut client_caps = ProtocolClientCapabilities::default();
634 if self.capabilities.sampling {
635 client_caps.sampling = Some(turbomcp_protocol::types::SamplingCapabilities);
636 }
637
638 // Send MCP initialization request
639 let request = InitializeRequest {
640 protocol_version: PROTOCOL_VERSION.to_string(),
641 capabilities: client_caps,
642 client_info: turbomcp_protocol::Implementation {
643 name: "turbomcp-client".to_string(),
644 version: env!("CARGO_PKG_VERSION").to_string(),
645 title: Some("TurboMCP Client".to_string()),
646 },
647 _meta: None,
648 };
649
650 let protocol_response: ProtocolInitializeResult = self
651 .protocol
652 .request("initialize", Some(serde_json::to_value(request)?))
653 .await?;
654 self.initialized = true;
655
656 // Send initialized notification
657 self.protocol
658 .notify("notifications/initialized", None)
659 .await?;
660
661 // Convert protocol response to client response type
662 Ok(InitializeResult {
663 server_info: protocol_response.server_info,
664 server_capabilities: protocol_response.capabilities,
665 })
666 }
667
668 /// List all available tools from the MCP server
669 ///
670 /// Returns a list of complete tool definitions with schemas that can be used
671 /// for form generation, validation, and documentation. Tools represent
672 /// executable functions provided by the server.
673 ///
674 /// # Returns
675 ///
676 /// Returns a vector of complete `Tool` objects with schemas and metadata.
677 ///
678 /// # Errors
679 ///
680 /// Returns an error if:
681 /// - The client is not initialized
682 /// - The server doesn't support tools
683 /// - The request fails
684 ///
685 /// # Examples
686 ///
687 /// ```rust,no_run
688 /// # use turbomcp_client::Client;
689 /// # use turbomcp_transport::stdio::StdioTransport;
690 /// # async fn example() -> turbomcp_core::Result<()> {
691 /// let mut client = Client::new(StdioTransport::new());
692 /// client.initialize().await?;
693 ///
694 /// let tools = client.list_tools().await?;
695 /// for tool in tools {
696 /// println!("Tool: {} - {}", tool.name, tool.description.as_deref().unwrap_or("No description"));
697 /// // Access full inputSchema for form generation
698 /// let schema = &tool.input_schema;
699 /// }
700 /// # Ok(())
701 /// # }
702 /// ```
703 pub async fn list_tools(&mut self) -> Result<Vec<Tool>> {
704 if !self.initialized {
705 return Err(Error::bad_request("Client not initialized"));
706 }
707
708 // Send tools/list request with plugin middleware
709 let response: ListToolsResult = self.execute_with_plugins("tools/list", None).await?;
710 Ok(response.tools) // Return full Tool objects with schemas
711 }
712
713 /// List available tool names from the MCP server
714 ///
715 /// Returns only the tool names for cases where full schemas are not needed.
716 /// For most use cases, prefer `list_tools()` which provides complete tool definitions.
717 ///
718 /// # Returns
719 ///
720 /// Returns a vector of tool names available on the server.
721 ///
722 /// # Examples
723 ///
724 /// ```rust,no_run
725 /// # use turbomcp_client::Client;
726 /// # use turbomcp_transport::stdio::StdioTransport;
727 /// # async fn example() -> turbomcp_core::Result<()> {
728 /// let mut client = Client::new(StdioTransport::new());
729 /// client.initialize().await?;
730 ///
731 /// let tool_names = client.list_tool_names().await?;
732 /// for name in tool_names {
733 /// println!("Available tool: {}", name);
734 /// }
735 /// # Ok(())
736 /// # }
737 /// ```
738 pub async fn list_tool_names(&mut self) -> Result<Vec<String>> {
739 let tools = self.list_tools().await?;
740 Ok(tools.into_iter().map(|tool| tool.name).collect())
741 }
742
743 /// Call a tool on the server
744 ///
745 /// Executes a tool on the server with the provided arguments.
746 ///
747 /// # Arguments
748 ///
749 /// * `name` - The name of the tool to call
750 /// * `arguments` - Optional arguments to pass to the tool
751 ///
752 /// # Returns
753 ///
754 /// Returns the result of the tool execution.
755 ///
756 /// # Examples
757 ///
758 /// ```rust,no_run
759 /// # use turbomcp_client::Client;
760 /// # use turbomcp_transport::stdio::StdioTransport;
761 /// # use std::collections::HashMap;
762 /// # async fn example() -> turbomcp_core::Result<()> {
763 /// let mut client = Client::new(StdioTransport::new());
764 /// client.initialize().await?;
765 ///
766 /// let mut args = HashMap::new();
767 /// args.insert("input".to_string(), serde_json::json!("test"));
768 ///
769 /// let result = client.call_tool("my_tool", Some(args)).await?;
770 /// println!("Tool result: {:?}", result);
771 /// # Ok(())
772 /// # }
773 /// ```
774 pub async fn call_tool(
775 &mut self,
776 name: &str,
777 arguments: Option<HashMap<String, serde_json::Value>>,
778 ) -> Result<serde_json::Value> {
779 if !self.initialized {
780 return Err(Error::bad_request("Client not initialized"));
781 }
782
783 // 🎉 TurboMCP v1.0.7: Clean plugin execution with macro!
784 let request_data = CallToolRequest {
785 name: name.to_string(),
786 arguments: Some(arguments.unwrap_or_default()),
787 _meta: None,
788 };
789
790 with_plugins!(self, "tools/call", request_data, {
791 // Core protocol call - plugins execute automatically around this
792 let result: CallToolResult = self
793 .protocol
794 .request("tools/call", Some(serde_json::to_value(&request_data)?))
795 .await?;
796
797 Ok(self.extract_tool_content(&result))
798 })
799 }
800
801 /// Execute a protocol method with plugin middleware
802 ///
803 /// This is a generic helper for wrapping protocol calls with plugin middleware.
804 async fn execute_with_plugins<R>(
805 &mut self,
806 method_name: &str,
807 params: Option<serde_json::Value>,
808 ) -> Result<R>
809 where
810 R: serde::de::DeserializeOwned + serde::Serialize + Clone,
811 {
812 // Create JSON-RPC request for plugin context
813 let json_rpc_request = turbomcp_protocol::jsonrpc::JsonRpcRequest {
814 jsonrpc: turbomcp_protocol::jsonrpc::JsonRpcVersion,
815 id: turbomcp_core::MessageId::Number(1),
816 method: method_name.to_string(),
817 params: params.clone(),
818 };
819
820 // 1. Create request context for plugins
821 let mut req_ctx =
822 crate::plugins::RequestContext::new(json_rpc_request, std::collections::HashMap::new());
823
824 // 2. Execute before_request plugin middleware
825 if let Err(e) = self
826 .plugin_registry
827 .execute_before_request(&mut req_ctx)
828 .await
829 {
830 return Err(Error::bad_request(format!(
831 "Plugin before_request failed: {}",
832 e
833 )));
834 }
835
836 // 3. Execute the actual protocol call
837 let start_time = std::time::Instant::now();
838 let protocol_result: Result<R> = self
839 .protocol
840 .request(method_name, req_ctx.params().cloned())
841 .await;
842 let duration = start_time.elapsed();
843
844 // 4. Prepare response context
845 let mut resp_ctx = match protocol_result {
846 Ok(ref response) => {
847 let response_value = serde_json::to_value(response.clone())?;
848 crate::plugins::ResponseContext::new(req_ctx, Some(response_value), None, duration)
849 }
850 Err(ref e) => {
851 crate::plugins::ResponseContext::new(req_ctx, None, Some(*e.clone()), duration)
852 }
853 };
854
855 // 5. Execute after_response plugin middleware
856 if let Err(e) = self
857 .plugin_registry
858 .execute_after_response(&mut resp_ctx)
859 .await
860 {
861 return Err(Error::bad_request(format!(
862 "Plugin after_response failed: {}",
863 e
864 )));
865 }
866
867 // 6. Return the final result, checking for plugin modifications
868 match protocol_result {
869 Ok(ref response) => {
870 // Check if plugins modified the response
871 if let Some(modified_response) = resp_ctx.response {
872 // Try to deserialize the modified response
873 if let Ok(modified_result) =
874 serde_json::from_value::<R>(modified_response.clone())
875 {
876 return Ok(modified_result);
877 }
878 }
879
880 // No plugin modifications, use original response
881 Ok(response.clone())
882 }
883 Err(e) => {
884 // Check if plugins provided an error recovery response
885 if let Some(recovery_response) = resp_ctx.response {
886 if let Ok(recovery_result) = serde_json::from_value::<R>(recovery_response) {
887 Ok(recovery_result)
888 } else {
889 Err(e)
890 }
891 } else {
892 Err(e)
893 }
894 }
895 }
896 }
897
898 /// Helper method to extract content from CallToolResult
899 fn extract_tool_content(&self, response: &CallToolResult) -> serde_json::Value {
900 // Extract content from response - for simplicity, return the first text content
901 if let Some(content) = response.content.first() {
902 match content {
903 Content::Text(text_content) => serde_json::json!({
904 "text": text_content.text,
905 "is_error": response.is_error.unwrap_or(false)
906 }),
907 Content::Image(image_content) => serde_json::json!({
908 "image": image_content.data,
909 "mime_type": image_content.mime_type,
910 "is_error": response.is_error.unwrap_or(false)
911 }),
912 Content::Resource(resource_content) => serde_json::json!({
913 "resource": resource_content.resource,
914 "annotations": resource_content.annotations,
915 "is_error": response.is_error.unwrap_or(false)
916 }),
917 Content::Audio(audio_content) => serde_json::json!({
918 "audio": audio_content.data,
919 "mime_type": audio_content.mime_type,
920 "is_error": response.is_error.unwrap_or(false)
921 }),
922 Content::ResourceLink(resource_link) => serde_json::json!({
923 "resource_uri": resource_link.uri,
924 "is_error": response.is_error.unwrap_or(false)
925 }),
926 }
927 } else {
928 serde_json::json!({
929 "message": "No content returned",
930 "is_error": response.is_error.unwrap_or(false)
931 })
932 }
933 }
934
935 /// Request completion suggestions from the server
936 ///
937 /// # Arguments
938 ///
939 /// * `handler_name` - The completion handler name
940 /// * `argument_value` - The partial value to complete
941 ///
942 /// # Examples
943 ///
944 /// ```rust,no_run
945 /// # use turbomcp_client::Client;
946 /// # use turbomcp_transport::stdio::StdioTransport;
947 /// # async fn example() -> turbomcp_core::Result<()> {
948 /// let mut client = Client::new(StdioTransport::new());
949 /// client.initialize().await?;
950 ///
951 /// let result = client.complete("complete_path", "/usr/b").await?;
952 /// println!("Completions: {:?}", result.values);
953 /// # Ok(())
954 /// # }
955 /// ```
956 pub async fn complete(
957 &mut self,
958 handler_name: &str,
959 argument_value: &str,
960 ) -> Result<turbomcp_protocol::types::CompletionResponse> {
961 if !self.initialized {
962 return Err(Error::bad_request("Client not initialized"));
963 }
964
965 // Create proper completion request using protocol types
966 use turbomcp_protocol::types::{
967 ArgumentInfo, CompleteRequestParams, CompletionReference, PromptReferenceData,
968 };
969
970 let request_params = CompleteRequestParams {
971 argument: ArgumentInfo {
972 name: "partial".to_string(),
973 value: argument_value.to_string(),
974 },
975 reference: CompletionReference::Prompt(PromptReferenceData {
976 name: handler_name.to_string(),
977 title: None,
978 }),
979 context: None,
980 _meta: None,
981 };
982
983 let serialized_params = serde_json::to_value(&request_params)?;
984
985 with_plugins!(self, "completion/complete", serialized_params, {
986 // Core protocol call - plugins execute automatically around this
987 let result: CompleteResult = self
988 .protocol
989 .request("completion/complete", Some(serialized_params))
990 .await?;
991
992 Ok(result.completion)
993 })
994 }
995
996 /// Complete a prompt argument with full MCP protocol support
997 ///
998 /// This method provides access to the complete MCP completion protocol,
999 /// allowing specification of argument names, prompt references, and context.
1000 ///
1001 /// # Arguments
1002 ///
1003 /// * `prompt_name` - Name of the prompt to complete for
1004 /// * `argument_name` - Name of the argument being completed
1005 /// * `argument_value` - Current value for completion matching
1006 /// * `context` - Optional context with previously resolved arguments
1007 ///
1008 /// # Examples
1009 ///
1010 /// ```rust,no_run
1011 /// # use turbomcp_client::Client;
1012 /// # use turbomcp_transport::stdio::StdioTransport;
1013 /// # use turbomcp_protocol::types::CompletionContext;
1014 /// # use std::collections::HashMap;
1015 /// # async fn example() -> turbomcp_core::Result<()> {
1016 /// let mut client = Client::new(StdioTransport::new());
1017 /// client.initialize().await?;
1018 ///
1019 /// // Complete with context
1020 /// let mut context_args = HashMap::new();
1021 /// context_args.insert("language".to_string(), "rust".to_string());
1022 /// let context = CompletionContext { arguments: Some(context_args) };
1023 ///
1024 /// let completions = client.complete_prompt(
1025 /// "code_review",
1026 /// "framework",
1027 /// "tok",
1028 /// Some(context)
1029 /// ).await?;
1030 ///
1031 /// for completion in completions.values {
1032 /// println!("Suggestion: {}", completion);
1033 /// }
1034 /// # Ok(())
1035 /// # }
1036 /// ```
1037 pub async fn complete_prompt(
1038 &mut self,
1039 prompt_name: &str,
1040 argument_name: &str,
1041 argument_value: &str,
1042 context: Option<turbomcp_protocol::types::CompletionContext>,
1043 ) -> Result<turbomcp_protocol::types::CompletionResponse> {
1044 if !self.initialized {
1045 return Err(Error::bad_request("Client not initialized"));
1046 }
1047
1048 use turbomcp_protocol::types::{
1049 ArgumentInfo, CompleteRequestParams, CompletionReference, PromptReferenceData,
1050 };
1051
1052 let request_params = CompleteRequestParams {
1053 argument: ArgumentInfo {
1054 name: argument_name.to_string(),
1055 value: argument_value.to_string(),
1056 },
1057 reference: CompletionReference::Prompt(PromptReferenceData {
1058 name: prompt_name.to_string(),
1059 title: None,
1060 }),
1061 context,
1062 _meta: None,
1063 };
1064
1065 let serialized_params = serde_json::to_value(&request_params)?;
1066
1067 with_plugins!(self, "completion/complete", serialized_params, {
1068 let result: CompleteResult = self
1069 .protocol
1070 .request("completion/complete", Some(serialized_params))
1071 .await?;
1072
1073 Ok(result.completion)
1074 })
1075 }
1076
1077 /// Complete a resource template URI with full MCP protocol support
1078 ///
1079 /// This method provides completion for resource template URIs, allowing
1080 /// servers to suggest values for URI template variables.
1081 ///
1082 /// # Arguments
1083 ///
1084 /// * `resource_uri` - Resource template URI (e.g., "/files/{path}")
1085 /// * `argument_name` - Name of the argument being completed
1086 /// * `argument_value` - Current value for completion matching
1087 /// * `context` - Optional context with previously resolved arguments
1088 ///
1089 /// # Examples
1090 ///
1091 /// ```rust,no_run
1092 /// # use turbomcp_client::Client;
1093 /// # use turbomcp_transport::stdio::StdioTransport;
1094 /// # async fn example() -> turbomcp_core::Result<()> {
1095 /// let mut client = Client::new(StdioTransport::new());
1096 /// client.initialize().await?;
1097 ///
1098 /// let completions = client.complete_resource(
1099 /// "/files/{path}",
1100 /// "path",
1101 /// "/home/user/doc",
1102 /// None
1103 /// ).await?;
1104 ///
1105 /// for completion in completions.values {
1106 /// println!("Path suggestion: {}", completion);
1107 /// }
1108 /// # Ok(())
1109 /// # }
1110 /// ```
1111 pub async fn complete_resource(
1112 &mut self,
1113 resource_uri: &str,
1114 argument_name: &str,
1115 argument_value: &str,
1116 context: Option<turbomcp_protocol::types::CompletionContext>,
1117 ) -> Result<turbomcp_protocol::types::CompletionResponse> {
1118 if !self.initialized {
1119 return Err(Error::bad_request("Client not initialized"));
1120 }
1121
1122 use turbomcp_protocol::types::{
1123 ArgumentInfo, CompleteRequestParams, CompletionReference, ResourceTemplateReferenceData,
1124 };
1125
1126 let request_params = CompleteRequestParams {
1127 argument: ArgumentInfo {
1128 name: argument_name.to_string(),
1129 value: argument_value.to_string(),
1130 },
1131 reference: CompletionReference::ResourceTemplate(ResourceTemplateReferenceData {
1132 uri: resource_uri.to_string(),
1133 }),
1134 context,
1135 _meta: None,
1136 };
1137
1138 let serialized_params = serde_json::to_value(&request_params)?;
1139
1140 with_plugins!(self, "completion/complete", serialized_params, {
1141 let result: CompleteResult = self
1142 .protocol
1143 .request("completion/complete", Some(serialized_params))
1144 .await?;
1145
1146 Ok(result.completion)
1147 })
1148 }
1149
1150 /// List available resources from the server
1151 ///
1152 /// # Examples
1153 ///
1154 /// ```rust,no_run
1155 /// # use turbomcp_client::Client;
1156 /// # use turbomcp_transport::stdio::StdioTransport;
1157 /// # async fn example() -> turbomcp_core::Result<()> {
1158 /// let mut client = Client::new(StdioTransport::new());
1159 /// client.initialize().await?;
1160 ///
1161 /// let resources = client.list_resources().await?;
1162 /// for resource in resources {
1163 /// println!("Available resource: {}", resource);
1164 /// }
1165 /// # Ok(())
1166 /// # }
1167 /// ```
1168 pub async fn list_resources(&mut self) -> Result<Vec<String>> {
1169 if !self.initialized {
1170 return Err(Error::bad_request("Client not initialized"));
1171 }
1172
1173 // Execute with plugin middleware
1174 let response: ListResourcesResult =
1175 self.execute_with_plugins("resources/list", None).await?;
1176
1177 let resource_uris = response
1178 .resources
1179 .into_iter()
1180 .map(|resource| resource.uri)
1181 .collect();
1182 Ok(resource_uris)
1183 }
1184
1185 /// Send a ping request to check server health and connectivity
1186 ///
1187 /// Sends a ping request to the server to verify the connection is active
1188 /// and the server is responding. This is useful for health checks and
1189 /// connection validation.
1190 ///
1191 /// # Returns
1192 ///
1193 /// Returns `PingResult` on successful ping.
1194 ///
1195 /// # Errors
1196 ///
1197 /// Returns an error if:
1198 /// - The client is not initialized
1199 /// - The server is not responding
1200 /// - The connection has failed
1201 ///
1202 /// # Examples
1203 ///
1204 /// ```rust,no_run
1205 /// # use turbomcp_client::Client;
1206 /// # use turbomcp_transport::stdio::StdioTransport;
1207 /// # async fn example() -> turbomcp_core::Result<()> {
1208 /// let mut client = Client::new(StdioTransport::new());
1209 /// client.initialize().await?;
1210 ///
1211 /// let result = client.ping().await?;
1212 /// println!("Server is responding");
1213 /// # Ok(())
1214 /// # }
1215 /// ```
1216 pub async fn ping(&mut self) -> Result<PingResult> {
1217 if !self.initialized {
1218 return Err(Error::bad_request("Client not initialized"));
1219 }
1220
1221 // Send ping request with plugin middleware (no parameters needed)
1222 let response: PingResult = self.execute_with_plugins("ping", None).await?;
1223 Ok(response)
1224 }
1225
1226 /// Read the content of a specific resource by URI
1227 ///
1228 /// Retrieves the content of a resource identified by its URI. This allows
1229 /// clients to access specific files, documents, or other resources
1230 /// provided by the server.
1231 ///
1232 /// # Arguments
1233 ///
1234 /// * `uri` - The URI of the resource to read
1235 ///
1236 /// # Returns
1237 ///
1238 /// Returns `ReadResourceResult` containing the resource content.
1239 ///
1240 /// # Errors
1241 ///
1242 /// Returns an error if:
1243 /// - The client is not initialized
1244 /// - The URI is invalid or empty
1245 /// - The resource doesn't exist
1246 /// - Access to the resource is denied
1247 ///
1248 /// # Examples
1249 ///
1250 /// ```rust,no_run
1251 /// # use turbomcp_client::Client;
1252 /// # use turbomcp_transport::stdio::StdioTransport;
1253 /// # async fn example() -> turbomcp_core::Result<()> {
1254 /// let mut client = Client::new(StdioTransport::new());
1255 /// client.initialize().await?;
1256 ///
1257 /// let result = client.read_resource("file:///path/to/document.txt").await?;
1258 /// for content in result.contents {
1259 /// println!("Resource content: {:?}", content);
1260 /// }
1261 /// # Ok(())
1262 /// # }
1263 /// ```
1264 pub async fn read_resource(&mut self, uri: &str) -> Result<ReadResourceResult> {
1265 if !self.initialized {
1266 return Err(Error::bad_request("Client not initialized"));
1267 }
1268
1269 if uri.is_empty() {
1270 return Err(Error::bad_request("Resource URI cannot be empty"));
1271 }
1272
1273 // Send read_resource request
1274 let request = ReadResourceRequest {
1275 uri: uri.to_string(),
1276 _meta: None,
1277 };
1278
1279 let response: ReadResourceResult = self
1280 .execute_with_plugins("resources/read", Some(serde_json::to_value(request)?))
1281 .await?;
1282 Ok(response)
1283 }
1284
1285 /// List available prompt templates from the server
1286 ///
1287 /// Retrieves the complete list of prompt templates that the server provides,
1288 /// including all metadata: title, description, and argument schemas. This is
1289 /// the MCP-compliant implementation that provides everything needed for UI generation
1290 /// and dynamic form creation.
1291 ///
1292 /// # Returns
1293 ///
1294 /// Returns a vector of `Prompt` objects containing:
1295 /// - `name`: Programmatic identifier
1296 /// - `title`: Human-readable display name (optional)
1297 /// - `description`: Description of what the prompt does (optional)
1298 /// - `arguments`: Array of argument schemas with validation info (optional)
1299 ///
1300 /// # Errors
1301 ///
1302 /// Returns an error if:
1303 /// - The client is not initialized
1304 /// - The server doesn't support prompts
1305 /// - The request fails
1306 ///
1307 /// # Examples
1308 ///
1309 /// ```rust,no_run
1310 /// # use turbomcp_client::Client;
1311 /// # use turbomcp_transport::stdio::StdioTransport;
1312 /// # async fn example() -> turbomcp_core::Result<()> {
1313 /// let mut client = Client::new(StdioTransport::new());
1314 /// client.initialize().await?;
1315 ///
1316 /// let prompts = client.list_prompts().await?;
1317 /// for prompt in prompts {
1318 /// println!("Prompt: {} ({})", prompt.name, prompt.title.unwrap_or("No title".to_string()));
1319 /// if let Some(args) = prompt.arguments {
1320 /// println!(" Arguments: {:?}", args);
1321 /// for arg in args {
1322 /// let required = arg.required.unwrap_or(false);
1323 /// println!(" - {}: {} (required: {})", arg.name,
1324 /// arg.description.unwrap_or("No description".to_string()), required);
1325 /// }
1326 /// }
1327 /// }
1328 /// # Ok(())
1329 /// # }
1330 /// ```
1331 pub async fn list_prompts(&mut self) -> Result<Vec<Prompt>> {
1332 if !self.initialized {
1333 return Err(Error::bad_request("Client not initialized"));
1334 }
1335
1336 // Execute with plugin middleware - return full Prompt objects per MCP spec
1337 let response: ListPromptsResult = self.execute_with_plugins("prompts/list", None).await?;
1338 Ok(response.prompts)
1339 }
1340
1341 /// Get a specific prompt template with argument support
1342 ///
1343 /// Retrieves a specific prompt template from the server with support for
1344 /// parameter substitution. When arguments are provided, the server will
1345 /// substitute them into the prompt template using {parameter} syntax.
1346 ///
1347 /// This is the MCP-compliant implementation that supports the full protocol specification.
1348 ///
1349 /// # Arguments
1350 ///
1351 /// * `name` - The name of the prompt to retrieve
1352 /// * `arguments` - Optional parameters for template substitution
1353 ///
1354 /// # Returns
1355 ///
1356 /// Returns `GetPromptResult` containing the prompt template with parameters substituted.
1357 ///
1358 /// # Errors
1359 ///
1360 /// Returns an error if:
1361 /// - The client is not initialized
1362 /// - The prompt name is empty
1363 /// - The prompt doesn't exist
1364 /// - Required arguments are missing
1365 /// - Argument types don't match schema
1366 /// - The request fails
1367 ///
1368 /// # Examples
1369 ///
1370 /// ```rust,no_run
1371 /// # use turbomcp_client::Client;
1372 /// # use turbomcp_transport::stdio::StdioTransport;
1373 /// # use turbomcp_protocol::PromptInput;
1374 /// # use std::collections::HashMap;
1375 /// # async fn example() -> turbomcp_core::Result<()> {
1376 /// let mut client = Client::new(StdioTransport::new());
1377 /// client.initialize().await?;
1378 ///
1379 /// // Get prompt without arguments (template form)
1380 /// let template = client.get_prompt("greeting", None).await?;
1381 /// println!("Template has {} messages", template.messages.len());
1382 ///
1383 /// // Get prompt with arguments (substituted form)
1384 /// let mut args = HashMap::new();
1385 /// args.insert("name".to_string(), serde_json::Value::String("Alice".to_string()));
1386 /// args.insert("greeting".to_string(), serde_json::Value::String("Hello".to_string()));
1387 ///
1388 /// let result = client.get_prompt("greeting", Some(args)).await?;
1389 /// println!("Generated prompt with {} messages", result.messages.len());
1390 /// # Ok(())
1391 /// # }
1392 /// ```
1393 pub async fn get_prompt(
1394 &mut self,
1395 name: &str,
1396 arguments: Option<PromptInput>,
1397 ) -> Result<GetPromptResult> {
1398 if !self.initialized {
1399 return Err(Error::bad_request("Client not initialized"));
1400 }
1401
1402 if name.is_empty() {
1403 return Err(Error::bad_request("Prompt name cannot be empty"));
1404 }
1405
1406 // Send prompts/get request with full argument support
1407 let request = GetPromptRequest {
1408 name: name.to_string(),
1409 arguments, // Support for parameter substitution
1410 _meta: None,
1411 };
1412
1413 self.execute_with_plugins("prompts/get", Some(serde_json::to_value(request).unwrap()))
1414 .await
1415 }
1416
1417 /// List available filesystem root directories
1418 ///
1419 /// Retrieves the list of root directories that the server has access to.
1420 /// This is useful for understanding what parts of the filesystem are
1421 /// available for resource access.
1422 ///
1423 /// # Returns
1424 ///
1425 /// Returns a vector of root directory URIs available on the server.
1426 ///
1427 /// # Errors
1428 ///
1429 /// Returns an error if:
1430 /// - The client is not initialized
1431 /// - The server doesn't support filesystem access
1432 /// - The request fails
1433 ///
1434 /// # Examples
1435 ///
1436 /// ```rust,no_run
1437 /// # use turbomcp_client::Client;
1438 /// # use turbomcp_transport::stdio::StdioTransport;
1439 /// # async fn example() -> turbomcp_core::Result<()> {
1440 /// let mut client = Client::new(StdioTransport::new());
1441 /// client.initialize().await?;
1442 ///
1443 /// let roots = client.list_roots().await?;
1444 /// for root_uri in roots {
1445 /// println!("Available root: {}", root_uri);
1446 /// }
1447 /// # Ok(())
1448 /// # }
1449 /// ```
1450 pub async fn list_roots(&mut self) -> Result<Vec<String>> {
1451 if !self.initialized {
1452 return Err(Error::bad_request("Client not initialized"));
1453 }
1454
1455 // Send roots/list request with plugin middleware
1456 let response: ListRootsResult = self.execute_with_plugins("roots/list", None).await?;
1457 let root_uris = response.roots.into_iter().map(|root| root.uri).collect();
1458 Ok(root_uris)
1459 }
1460
1461 /// Set the logging level for the server
1462 ///
1463 /// Controls the verbosity of server logging. This allows clients to
1464 /// adjust the amount of log information they receive from the server.
1465 ///
1466 /// # Arguments
1467 ///
1468 /// * `level` - The desired logging level (Error, Warn, Info, Debug)
1469 ///
1470 /// # Returns
1471 ///
1472 /// Returns `SetLevelResult` on successful level change.
1473 ///
1474 /// # Errors
1475 ///
1476 /// Returns an error if:
1477 /// - The client is not initialized
1478 /// - The server doesn't support logging control
1479 /// - The request fails
1480 ///
1481 /// # Examples
1482 ///
1483 /// ```rust,no_run
1484 /// # use turbomcp_client::Client;
1485 /// # use turbomcp_transport::stdio::StdioTransport;
1486 /// # use turbomcp_protocol::types::LogLevel;
1487 /// # async fn example() -> turbomcp_core::Result<()> {
1488 /// let mut client = Client::new(StdioTransport::new());
1489 /// client.initialize().await?;
1490 ///
1491 /// // Set server to debug logging
1492 /// client.set_log_level(LogLevel::Debug).await?;
1493 /// println!("Server logging level set to debug");
1494 /// # Ok(())
1495 /// # }
1496 /// ```
1497 pub async fn set_log_level(&mut self, level: LogLevel) -> Result<SetLevelResult> {
1498 if !self.initialized {
1499 return Err(Error::bad_request("Client not initialized"));
1500 }
1501
1502 // Send logging/setLevel request
1503 let request = SetLevelRequest { level };
1504
1505 let response: SetLevelResult = self
1506 .execute_with_plugins("logging/setLevel", Some(serde_json::to_value(request)?))
1507 .await?;
1508 Ok(response)
1509 }
1510
1511 /// Subscribe to resource change notifications
1512 ///
1513 /// Registers interest in receiving notifications when the specified
1514 /// resource changes. The server will send notifications when the
1515 /// resource is modified, created, or deleted.
1516 ///
1517 /// # Arguments
1518 ///
1519 /// * `uri` - The URI of the resource to monitor
1520 ///
1521 /// # Returns
1522 ///
1523 /// Returns `EmptyResult` on successful subscription.
1524 ///
1525 /// # Errors
1526 ///
1527 /// Returns an error if:
1528 /// - The client is not initialized
1529 /// - The URI is invalid or empty
1530 /// - The server doesn't support subscriptions
1531 /// - The request fails
1532 ///
1533 /// # Examples
1534 ///
1535 /// ```rust,no_run
1536 /// # use turbomcp_client::Client;
1537 /// # use turbomcp_transport::stdio::StdioTransport;
1538 /// # async fn example() -> turbomcp_core::Result<()> {
1539 /// let mut client = Client::new(StdioTransport::new());
1540 /// client.initialize().await?;
1541 ///
1542 /// // Subscribe to file changes
1543 /// client.subscribe("file:///watch/directory").await?;
1544 /// println!("Subscribed to resource changes");
1545 /// # Ok(())
1546 /// # }
1547 /// ```
1548 pub async fn subscribe(&mut self, uri: &str) -> Result<EmptyResult> {
1549 if !self.initialized {
1550 return Err(Error::bad_request("Client not initialized"));
1551 }
1552
1553 if uri.is_empty() {
1554 return Err(Error::bad_request("Subscription URI cannot be empty"));
1555 }
1556
1557 // Send resources/subscribe request with plugin middleware
1558 let request = SubscribeRequest {
1559 uri: uri.to_string(),
1560 };
1561
1562 self.execute_with_plugins(
1563 "resources/subscribe",
1564 Some(serde_json::to_value(request).unwrap()),
1565 )
1566 .await
1567 }
1568
1569 /// Unsubscribe from resource change notifications
1570 ///
1571 /// Cancels a previous subscription to resource changes. After unsubscribing,
1572 /// the client will no longer receive notifications for the specified resource.
1573 ///
1574 /// # Arguments
1575 ///
1576 /// * `uri` - The URI of the resource to stop monitoring
1577 ///
1578 /// # Returns
1579 ///
1580 /// Returns `EmptyResult` on successful unsubscription.
1581 ///
1582 /// # Errors
1583 ///
1584 /// Returns an error if:
1585 /// - The client is not initialized
1586 /// - The URI is invalid or empty
1587 /// - No active subscription exists for the URI
1588 /// - The request fails
1589 ///
1590 /// # Examples
1591 ///
1592 /// ```rust,no_run
1593 /// # use turbomcp_client::Client;
1594 /// # use turbomcp_transport::stdio::StdioTransport;
1595 /// # async fn example() -> turbomcp_core::Result<()> {
1596 /// let mut client = Client::new(StdioTransport::new());
1597 /// client.initialize().await?;
1598 ///
1599 /// // Unsubscribe from file changes
1600 /// client.unsubscribe("file:///watch/directory").await?;
1601 /// println!("Unsubscribed from resource changes");
1602 /// # Ok(())
1603 /// # }
1604 /// ```
1605 pub async fn unsubscribe(&mut self, uri: &str) -> Result<EmptyResult> {
1606 if !self.initialized {
1607 return Err(Error::bad_request("Client not initialized"));
1608 }
1609
1610 if uri.is_empty() {
1611 return Err(Error::bad_request("Unsubscription URI cannot be empty"));
1612 }
1613
1614 // Send resources/unsubscribe request with plugin middleware
1615 let request = UnsubscribeRequest {
1616 uri: uri.to_string(),
1617 };
1618
1619 self.execute_with_plugins(
1620 "resources/unsubscribe",
1621 Some(serde_json::to_value(request).unwrap()),
1622 )
1623 .await
1624 }
1625
1626 /// List available resource templates
1627 ///
1628 /// Retrieves the list of resource templates that define URI patterns
1629 /// for accessing different types of resources. Templates help clients
1630 /// understand what resources are available and how to access them.
1631 ///
1632 /// # Returns
1633 ///
1634 /// Returns a vector of resource template URI patterns.
1635 ///
1636 /// # Errors
1637 ///
1638 /// Returns an error if:
1639 /// - The client is not initialized
1640 /// - The server doesn't support resource templates
1641 /// - The request fails
1642 ///
1643 /// # Examples
1644 ///
1645 /// ```rust,no_run
1646 /// # use turbomcp_client::Client;
1647 /// # use turbomcp_transport::stdio::StdioTransport;
1648 /// # async fn example() -> turbomcp_core::Result<()> {
1649 /// let mut client = Client::new(StdioTransport::new());
1650 /// client.initialize().await?;
1651 ///
1652 /// let templates = client.list_resource_templates().await?;
1653 /// for template in templates {
1654 /// println!("Resource template: {}", template);
1655 /// }
1656 /// # Ok(())
1657 /// # }
1658 /// ```
1659 pub async fn list_resource_templates(&mut self) -> Result<Vec<String>> {
1660 if !self.initialized {
1661 return Err(Error::bad_request("Client not initialized"));
1662 }
1663
1664 // Send resources/templates request with plugin middleware
1665 let response: ListResourceTemplatesResult = self
1666 .execute_with_plugins("resources/templates", None)
1667 .await?;
1668 let template_uris = response
1669 .resource_templates
1670 .into_iter()
1671 .map(|template| template.uri_template)
1672 .collect();
1673 Ok(template_uris)
1674 }
1675
1676 // ============================================================================
1677 // HANDLER REGISTRATION METHODS
1678 // ============================================================================
1679
1680 /// Register an elicitation handler for processing user input requests
1681 ///
1682 /// Elicitation handlers are called when the server needs user input during
1683 /// operations. The handler should present the request to the user and
1684 /// collect their response according to the provided schema.
1685 ///
1686 /// # Arguments
1687 ///
1688 /// * `handler` - The elicitation handler implementation
1689 ///
1690 /// # Examples
1691 ///
1692 /// ```rust,no_run
1693 /// use turbomcp_client::Client;
1694 /// use turbomcp_client::handlers::{ElicitationHandler, ElicitationRequest, ElicitationResponse, ElicitationAction, HandlerResult};
1695 /// use turbomcp_transport::stdio::StdioTransport;
1696 /// use async_trait::async_trait;
1697 /// use std::sync::Arc;
1698 /// use serde_json::json;
1699 ///
1700 /// #[derive(Debug)]
1701 /// struct MyElicitationHandler;
1702 ///
1703 /// #[async_trait]
1704 /// impl ElicitationHandler for MyElicitationHandler {
1705 /// async fn handle_elicitation(
1706 /// &self,
1707 /// request: ElicitationRequest,
1708 /// ) -> HandlerResult<ElicitationResponse> {
1709 /// Ok(ElicitationResponse {
1710 /// action: ElicitationAction::Accept,
1711 /// content: Some(json!({"user_input": "example"})),
1712 /// })
1713 /// }
1714 /// }
1715 ///
1716 /// let mut client = Client::new(StdioTransport::new());
1717 /// client.on_elicitation(Arc::new(MyElicitationHandler));
1718 /// ```
1719 pub fn on_elicitation(&mut self, handler: Arc<dyn ElicitationHandler>) {
1720 self.handlers.set_elicitation_handler(handler);
1721 }
1722
1723 /// Register a progress handler for processing operation progress updates
1724 ///
1725 /// Progress handlers receive notifications about long-running server operations.
1726 /// This allows clients to display progress bars, status updates, or other
1727 /// feedback to users.
1728 ///
1729 /// # Arguments
1730 ///
1731 /// * `handler` - The progress handler implementation
1732 ///
1733 /// # Examples
1734 ///
1735 /// ```rust,no_run
1736 /// use turbomcp_client::Client;
1737 /// use turbomcp_client::handlers::{ProgressHandler, ProgressNotification, HandlerResult};
1738 /// use turbomcp_transport::stdio::StdioTransport;
1739 /// use async_trait::async_trait;
1740 /// use std::sync::Arc;
1741 ///
1742 /// #[derive(Debug)]
1743 /// struct MyProgressHandler;
1744 ///
1745 /// #[async_trait]
1746 /// impl ProgressHandler for MyProgressHandler {
1747 /// async fn handle_progress(&self, notification: ProgressNotification) -> HandlerResult<()> {
1748 /// println!("Progress: {:?}", notification);
1749 /// Ok(())
1750 /// }
1751 /// }
1752 ///
1753 /// let mut client = Client::new(StdioTransport::new());
1754 /// client.on_progress(Arc::new(MyProgressHandler));
1755 /// ```
1756 pub fn on_progress(&mut self, handler: Arc<dyn ProgressHandler>) {
1757 self.handlers.set_progress_handler(handler);
1758 }
1759
1760 /// Register a log handler for processing server log messages
1761 ///
1762 /// Log handlers receive log messages from the server and can route them
1763 /// to the client's logging system. This is useful for debugging and
1764 /// maintaining a unified log across client and server.
1765 ///
1766 /// # Arguments
1767 ///
1768 /// * `handler` - The log handler implementation
1769 ///
1770 /// # Examples
1771 ///
1772 /// ```rust,no_run
1773 /// use turbomcp_client::Client;
1774 /// use turbomcp_client::handlers::{LogHandler, LogMessage, HandlerResult};
1775 /// use turbomcp_transport::stdio::StdioTransport;
1776 /// use async_trait::async_trait;
1777 /// use std::sync::Arc;
1778 ///
1779 /// #[derive(Debug)]
1780 /// struct MyLogHandler;
1781 ///
1782 /// #[async_trait]
1783 /// impl LogHandler for MyLogHandler {
1784 /// async fn handle_log(&self, log: LogMessage) -> HandlerResult<()> {
1785 /// println!("Server log: {}", log.message);
1786 /// Ok(())
1787 /// }
1788 /// }
1789 ///
1790 /// let mut client = Client::new(StdioTransport::new());
1791 /// client.on_log(Arc::new(MyLogHandler));
1792 /// ```
1793 pub fn on_log(&mut self, handler: Arc<dyn LogHandler>) {
1794 self.handlers.set_log_handler(handler);
1795 }
1796
1797 /// Register a resource update handler for processing resource change notifications
1798 ///
1799 /// Resource update handlers receive notifications when subscribed resources
1800 /// change on the server. This enables reactive updates to cached data or
1801 /// UI refreshes when server-side resources change.
1802 ///
1803 /// # Arguments
1804 ///
1805 /// * `handler` - The resource update handler implementation
1806 ///
1807 /// # Examples
1808 ///
1809 /// ```rust,no_run
1810 /// use turbomcp_client::Client;
1811 /// use turbomcp_client::handlers::{ResourceUpdateHandler, ResourceUpdateNotification, HandlerResult};
1812 /// use turbomcp_transport::stdio::StdioTransport;
1813 /// use async_trait::async_trait;
1814 /// use std::sync::Arc;
1815 ///
1816 /// #[derive(Debug)]
1817 /// struct MyResourceUpdateHandler;
1818 ///
1819 /// #[async_trait]
1820 /// impl ResourceUpdateHandler for MyResourceUpdateHandler {
1821 /// async fn handle_resource_update(
1822 /// &self,
1823 /// notification: ResourceUpdateNotification,
1824 /// ) -> HandlerResult<()> {
1825 /// println!("Resource updated: {}", notification.uri);
1826 /// Ok(())
1827 /// }
1828 /// }
1829 ///
1830 /// let mut client = Client::new(StdioTransport::new());
1831 /// client.on_resource_update(Arc::new(MyResourceUpdateHandler));
1832 /// ```
1833 pub fn on_resource_update(&mut self, handler: Arc<dyn ResourceUpdateHandler>) {
1834 self.handlers.set_resource_update_handler(handler);
1835 }
1836
1837 /// Check if an elicitation handler is registered
1838 pub fn has_elicitation_handler(&self) -> bool {
1839 self.handlers.has_elicitation_handler()
1840 }
1841
1842 /// Check if a progress handler is registered
1843 pub fn has_progress_handler(&self) -> bool {
1844 self.handlers.has_progress_handler()
1845 }
1846
1847 /// Check if a log handler is registered
1848 pub fn has_log_handler(&self) -> bool {
1849 self.handlers.has_log_handler()
1850 }
1851
1852 /// Check if a resource update handler is registered
1853 pub fn has_resource_update_handler(&self) -> bool {
1854 self.handlers.has_resource_update_handler()
1855 }
1856
1857 /// Get the client's capabilities configuration
1858 pub fn capabilities(&self) -> &ClientCapabilities {
1859 &self.capabilities
1860 }
1861
1862 // ============================================================================
1863 // PLUGIN MANAGEMENT
1864 // ============================================================================
1865
1866 /// Register a plugin with the client
1867 ///
1868 /// # Arguments
1869 ///
1870 /// * `plugin` - The plugin to register
1871 ///
1872 /// # Examples
1873 ///
1874 /// ```rust,no_run
1875 /// use turbomcp_client::plugins::{MetricsPlugin, PluginConfig};
1876 /// use std::sync::Arc;
1877 ///
1878 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1879 /// # let mut client = turbomcp_client::Client::new(turbomcp_transport::stdio::StdioTransport::new());
1880 /// let metrics_plugin = Arc::new(MetricsPlugin::new(PluginConfig::Metrics));
1881 /// client.register_plugin(metrics_plugin).await?;
1882 /// # Ok(())
1883 /// # }
1884 /// ```
1885 pub async fn register_plugin(
1886 &mut self,
1887 plugin: std::sync::Arc<dyn crate::plugins::ClientPlugin>,
1888 ) -> Result<()> {
1889 self.plugin_registry
1890 .register_plugin(plugin)
1891 .await
1892 .map_err(|e| Error::bad_request(format!("Failed to register plugin: {}", e)))
1893 }
1894
1895 /// Check if a plugin is registered
1896 ///
1897 /// # Arguments
1898 ///
1899 /// * `name` - The name of the plugin to check
1900 pub fn has_plugin(&self, name: &str) -> bool {
1901 self.plugin_registry.has_plugin(name)
1902 }
1903
1904 /// Get plugin data for a specific plugin type
1905 ///
1906 /// # Arguments
1907 ///
1908 /// * `name` - The name of the plugin
1909 ///
1910 /// # Examples
1911 ///
1912 /// ```rust,no_run
1913 /// use turbomcp_client::plugins::MetricsPlugin;
1914 ///
1915 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1916 /// # let client = turbomcp_client::Client::new(turbomcp_transport::stdio::StdioTransport::new());
1917 /// if let Some(plugin) = client.get_plugin("metrics") {
1918 /// // Use plugin data
1919 /// }
1920 /// # Ok(())
1921 /// # }
1922 /// ```
1923 pub fn get_plugin(
1924 &self,
1925 name: &str,
1926 ) -> Option<std::sync::Arc<dyn crate::plugins::ClientPlugin>> {
1927 self.plugin_registry.get_plugin(name)
1928 }
1929
1930 /// Initialize all registered plugins
1931 ///
1932 /// This should be called after registration but before using the client.
1933 pub async fn initialize_plugins(&mut self) -> Result<()> {
1934 // Set up client context for plugins with actual client capabilities
1935 let mut capabilities = std::collections::HashMap::new();
1936 capabilities.insert(
1937 "protocol_version".to_string(),
1938 serde_json::json!("2024-11-05"),
1939 );
1940 capabilities.insert(
1941 "mcp_version".to_string(),
1942 serde_json::json!(env!("CARGO_PKG_VERSION")),
1943 );
1944 capabilities.insert(
1945 "supports_notifications".to_string(),
1946 serde_json::json!(true),
1947 );
1948 capabilities.insert(
1949 "supports_sampling".to_string(),
1950 serde_json::json!(self.sampling_handler.is_some()),
1951 );
1952 capabilities.insert("supports_progress".to_string(), serde_json::json!(true));
1953 capabilities.insert("supports_roots".to_string(), serde_json::json!(true));
1954
1955 // Extract client configuration
1956 let mut config = std::collections::HashMap::new();
1957 config.insert(
1958 "client_name".to_string(),
1959 serde_json::json!("turbomcp-client"),
1960 );
1961 config.insert(
1962 "initialized".to_string(),
1963 serde_json::json!(self.initialized),
1964 );
1965 config.insert(
1966 "plugin_count".to_string(),
1967 serde_json::json!(self.plugin_registry.plugin_count()),
1968 );
1969
1970 let context = crate::plugins::PluginContext::new(
1971 "turbomcp-client".to_string(),
1972 env!("CARGO_PKG_VERSION").to_string(),
1973 capabilities,
1974 config,
1975 vec![], // Will be populated by the registry
1976 );
1977
1978 self.plugin_registry.set_client_context(context);
1979
1980 // Note: Individual plugins are initialized automatically during registration
1981 // via PluginRegistry::register_plugin(). This method ensures the registry
1982 // has proper client context for any future plugin registrations.
1983 Ok(())
1984 }
1985
1986 /// Cleanup all registered plugins
1987 ///
1988 /// This should be called when the client is being shut down.
1989 pub async fn cleanup_plugins(&mut self) -> Result<()> {
1990 // Clear the plugin registry - plugins will be dropped and cleaned up automatically
1991 // The Rust ownership system ensures proper cleanup when the Arc<dyn ClientPlugin>
1992 // references are dropped.
1993
1994 // Note: The plugin system uses RAII (Resource Acquisition Is Initialization)
1995 // pattern where plugins clean up their resources in their Drop implementation.
1996 // No explicit cleanup is needed beyond clearing the registry.
1997
1998 self.plugin_registry = crate::plugins::PluginRegistry::new();
1999 Ok(())
2000 }
2001}
2002
2003/// Thread-safe wrapper for sharing Client across async tasks
2004///
2005/// This wrapper encapsulates the Arc/Mutex complexity and provides a clean API
2006/// for concurrent access to MCP client functionality. It addresses the limitations
2007/// identified in PR feedback where Client requires `&mut self` for all operations
2008/// but needs to be shared across multiple async tasks.
2009///
2010/// # Design Rationale
2011///
2012/// All Client methods require `&mut self` because:
2013/// - MCP connections maintain state (initialized flag, connection status)
2014/// - Request correlation tracking for JSON-RPC requires mutation
2015/// - Handler and plugin registries need mutable access
2016///
2017/// While Client implements Send + Sync, this only means it's safe to move/share
2018/// between threads, not that multiple tasks can mutate it concurrently.
2019///
2020/// # Examples
2021///
2022/// ```rust,no_run
2023/// use turbomcp_client::{Client, SharedClient};
2024/// use turbomcp_transport::stdio::StdioTransport;
2025///
2026/// # async fn example() -> turbomcp_core::Result<()> {
2027/// let transport = StdioTransport::new();
2028/// let client = Client::new(transport);
2029/// let shared = SharedClient::new(client);
2030///
2031/// // Initialize once
2032/// shared.initialize().await?;
2033///
2034/// // Clone for sharing across tasks
2035/// let shared1 = shared.clone();
2036/// let shared2 = shared.clone();
2037///
2038/// // Both tasks can use the client concurrently
2039/// let handle1 = tokio::spawn(async move {
2040/// shared1.list_tools().await
2041/// });
2042///
2043/// let handle2 = tokio::spawn(async move {
2044/// shared2.list_prompts().await
2045/// });
2046///
2047/// let (tools, prompts) = tokio::try_join!(handle1, handle2).unwrap();
2048/// # Ok(())
2049/// # }
2050/// ```
2051pub struct SharedClient<T: Transport> {
2052 inner: Arc<Mutex<Client<T>>>,
2053}
2054
2055impl<T: Transport> SharedClient<T> {
2056 /// Create a new shared client wrapper
2057 ///
2058 /// Takes ownership of a Client and wraps it for thread-safe sharing.
2059 /// The original client can no longer be accessed directly after this call.
2060 pub fn new(client: Client<T>) -> Self {
2061 Self {
2062 inner: Arc::new(Mutex::new(client)),
2063 }
2064 }
2065
2066 /// Initialize the MCP connection
2067 ///
2068 /// This method should be called once before using any other client operations.
2069 /// It negotiates capabilities with the server and establishes the communication protocol.
2070 pub async fn initialize(&self) -> Result<InitializeResult> {
2071 self.inner.lock().await.initialize().await
2072 }
2073
2074 /// List all available tools from the MCP server
2075 ///
2076 /// Returns a list of complete tool definitions with schemas that can be used
2077 /// for form generation, validation, and documentation. Tools represent
2078 /// executable functions provided by the server.
2079 pub async fn list_tools(&self) -> Result<Vec<Tool>> {
2080 self.inner.lock().await.list_tools().await
2081 }
2082
2083 /// List available tool names from the MCP server
2084 ///
2085 /// Returns only the tool names for cases where full schemas are not needed.
2086 /// For most use cases, prefer `list_tools()` which provides complete tool definitions.
2087 pub async fn list_tool_names(&self) -> Result<Vec<String>> {
2088 self.inner.lock().await.list_tool_names().await
2089 }
2090
2091 /// Execute a tool with the given name and arguments
2092 ///
2093 /// Calls a specific tool on the MCP server with the provided arguments.
2094 /// The arguments should match the tool's expected parameter schema.
2095 pub async fn call_tool(
2096 &self,
2097 name: &str,
2098 arguments: Option<HashMap<String, serde_json::Value>>,
2099 ) -> Result<serde_json::Value> {
2100 self.inner.lock().await.call_tool(name, arguments).await
2101 }
2102
2103 /// List all available prompts from the MCP server
2104 ///
2105 /// Returns full Prompt objects with metadata including name, title, description,
2106 /// and argument schemas. This information can be used to generate UI forms
2107 /// for prompt parameter collection.
2108 pub async fn list_prompts(&self) -> Result<Vec<Prompt>> {
2109 self.inner.lock().await.list_prompts().await
2110 }
2111
2112 /// Get a prompt with optional argument substitution
2113 ///
2114 /// Retrieves a prompt from the server. If arguments are provided, template
2115 /// parameters (e.g., `{parameter}`) will be substituted with the given values.
2116 /// Pass `None` for arguments to get the raw template form.
2117 pub async fn get_prompt(
2118 &self,
2119 name: &str,
2120 arguments: Option<PromptInput>,
2121 ) -> Result<GetPromptResult> {
2122 self.inner.lock().await.get_prompt(name, arguments).await
2123 }
2124
2125 /// List available resources from the MCP server
2126 ///
2127 /// Resources represent data or content that can be read by the client.
2128 /// Returns a list of resource identifiers and metadata.
2129 pub async fn list_resources(&self) -> Result<Vec<String>> {
2130 self.inner.lock().await.list_resources().await
2131 }
2132
2133 /// Read a specific resource from the MCP server
2134 ///
2135 /// Retrieves the content of a resource identified by its URI.
2136 /// The content format depends on the specific resource type.
2137 pub async fn read_resource(&self, uri: &str) -> Result<ReadResourceResult> {
2138 self.inner.lock().await.read_resource(uri).await
2139 }
2140
2141 /// List resource templates from the MCP server
2142 ///
2143 /// Resource templates define patterns for generating resource URIs.
2144 /// They allow servers to describe families of related resources.
2145 pub async fn list_resource_templates(&self) -> Result<Vec<String>> {
2146 self.inner.lock().await.list_resource_templates().await
2147 }
2148
2149 /// Set the logging level for the MCP server
2150 ///
2151 /// Controls the verbosity of logs sent from the server to the client.
2152 /// Higher log levels provide more detailed information.
2153 pub async fn set_log_level(&self, level: LogLevel) -> Result<SetLevelResult> {
2154 self.inner.lock().await.set_log_level(level).await
2155 }
2156
2157 /// Subscribe to notifications from a specific URI
2158 ///
2159 /// Registers interest in receiving notifications when the specified
2160 /// resource or endpoint changes. Used for real-time updates.
2161 pub async fn subscribe(&self, uri: &str) -> Result<EmptyResult> {
2162 self.inner.lock().await.subscribe(uri).await
2163 }
2164
2165 /// Unsubscribe from notifications for a specific URI
2166 ///
2167 /// Removes a previously registered subscription to stop receiving
2168 /// notifications for the specified resource or endpoint.
2169 pub async fn unsubscribe(&self, uri: &str) -> Result<EmptyResult> {
2170 self.inner.lock().await.unsubscribe(uri).await
2171 }
2172
2173 /// Send a ping to test connection health
2174 ///
2175 /// Verifies that the MCP connection is still active and responsive.
2176 /// Used for health checking and keepalive functionality.
2177 pub async fn ping(&self) -> Result<PingResult> {
2178 self.inner.lock().await.ping().await
2179 }
2180
2181 /// Get the client's configured capabilities
2182 ///
2183 /// Returns the capabilities that this client supports.
2184 /// These are negotiated during initialization.
2185 pub async fn capabilities(&self) -> ClientCapabilities {
2186 let client = self.inner.lock().await;
2187 client.capabilities().clone()
2188 }
2189
2190 /// Request argument completion from the MCP server
2191 ///
2192 /// Provides autocompletion suggestions for prompt arguments and resource URIs.
2193 /// This enables rich, IDE-like experiences with contextual suggestions.
2194 ///
2195 /// # Arguments
2196 ///
2197 /// * `handler_name` - The completion handler name
2198 /// * `argument_value` - The partial value to complete
2199 ///
2200 /// # Examples
2201 ///
2202 /// ```rust,no_run
2203 /// # use turbomcp_client::{Client, SharedClient};
2204 /// # use turbomcp_transport::stdio::StdioTransport;
2205 /// # async fn example() -> turbomcp_core::Result<()> {
2206 /// let shared = SharedClient::new(Client::new(StdioTransport::new()));
2207 /// shared.initialize().await?;
2208 ///
2209 /// let result = shared.complete("complete_path", "/usr/b").await?;
2210 /// println!("Completions: {:?}", result.values);
2211 /// # Ok(())
2212 /// # }
2213 /// ```
2214 pub async fn complete(
2215 &self,
2216 handler_name: &str,
2217 argument_value: &str,
2218 ) -> Result<turbomcp_protocol::types::CompletionResponse> {
2219 self.inner
2220 .lock()
2221 .await
2222 .complete(handler_name, argument_value)
2223 .await
2224 }
2225
2226 /// Complete a prompt argument with full MCP protocol support
2227 ///
2228 /// This method provides access to the complete MCP completion protocol,
2229 /// allowing specification of argument names, prompt references, and context.
2230 ///
2231 /// # Arguments
2232 ///
2233 /// * `prompt_name` - Name of the prompt to complete for
2234 /// * `argument_name` - Name of the argument being completed
2235 /// * `argument_value` - Current value for completion matching
2236 /// * `context` - Optional context with previously resolved arguments
2237 ///
2238 /// # Examples
2239 ///
2240 /// ```rust,no_run
2241 /// # use turbomcp_client::{Client, SharedClient};
2242 /// # use turbomcp_transport::stdio::StdioTransport;
2243 /// # use turbomcp_protocol::types::CompletionContext;
2244 /// # use std::collections::HashMap;
2245 /// # async fn example() -> turbomcp_core::Result<()> {
2246 /// let shared = SharedClient::new(Client::new(StdioTransport::new()));
2247 /// shared.initialize().await?;
2248 ///
2249 /// // Complete with context
2250 /// let mut context_args = HashMap::new();
2251 /// context_args.insert("language".to_string(), "rust".to_string());
2252 /// let context = CompletionContext { arguments: Some(context_args) };
2253 ///
2254 /// let completions = shared.complete_prompt(
2255 /// "code_review",
2256 /// "framework",
2257 /// "tok",
2258 /// Some(context)
2259 /// ).await?;
2260 ///
2261 /// for completion in completions.values {
2262 /// println!("Suggestion: {}", completion);
2263 /// }
2264 /// # Ok(())
2265 /// # }
2266 /// ```
2267 pub async fn complete_prompt(
2268 &self,
2269 prompt_name: &str,
2270 argument_name: &str,
2271 argument_value: &str,
2272 context: Option<turbomcp_protocol::types::CompletionContext>,
2273 ) -> Result<turbomcp_protocol::types::CompletionResponse> {
2274 self.inner
2275 .lock()
2276 .await
2277 .complete_prompt(prompt_name, argument_name, argument_value, context)
2278 .await
2279 }
2280
2281 /// Complete a resource template URI with full MCP protocol support
2282 ///
2283 /// This method provides completion for resource template URIs, allowing
2284 /// servers to suggest values for URI template variables.
2285 ///
2286 /// # Arguments
2287 ///
2288 /// * `resource_uri` - Resource template URI (e.g., "/files/{path}")
2289 /// * `argument_name` - Name of the argument being completed
2290 /// * `argument_value` - Current value for completion matching
2291 /// * `context` - Optional context with previously resolved arguments
2292 ///
2293 /// # Examples
2294 ///
2295 /// ```rust,no_run
2296 /// # use turbomcp_client::{Client, SharedClient};
2297 /// # use turbomcp_transport::stdio::StdioTransport;
2298 /// # async fn example() -> turbomcp_core::Result<()> {
2299 /// let shared = SharedClient::new(Client::new(StdioTransport::new()));
2300 /// shared.initialize().await?;
2301 ///
2302 /// let completions = shared.complete_resource(
2303 /// "/files/{path}",
2304 /// "path",
2305 /// "/home/user/doc",
2306 /// None
2307 /// ).await?;
2308 ///
2309 /// for completion in completions.values {
2310 /// println!("Path suggestion: {}", completion);
2311 /// }
2312 /// # Ok(())
2313 /// # }
2314 /// ```
2315 pub async fn complete_resource(
2316 &self,
2317 resource_uri: &str,
2318 argument_name: &str,
2319 argument_value: &str,
2320 context: Option<turbomcp_protocol::types::CompletionContext>,
2321 ) -> Result<turbomcp_protocol::types::CompletionResponse> {
2322 self.inner
2323 .lock()
2324 .await
2325 .complete_resource(resource_uri, argument_name, argument_value, context)
2326 .await
2327 }
2328
2329 /// List filesystem roots available to the server
2330 ///
2331 /// Returns filesystem root directories that the server has access to.
2332 /// This helps servers understand their operating boundaries and available
2333 /// resources within the filesystem.
2334 ///
2335 /// # Examples
2336 ///
2337 /// ```rust,no_run
2338 /// # use turbomcp_client::{Client, SharedClient};
2339 /// # use turbomcp_transport::stdio::StdioTransport;
2340 /// # async fn example() -> turbomcp_core::Result<()> {
2341 /// let shared = SharedClient::new(Client::new(StdioTransport::new()));
2342 /// shared.initialize().await?;
2343 ///
2344 /// let roots = shared.list_roots().await?;
2345 /// for root_uri in roots {
2346 /// println!("Available root: {}", root_uri);
2347 /// }
2348 /// # Ok(())
2349 /// # }
2350 /// ```
2351 pub async fn list_roots(&self) -> Result<Vec<String>> {
2352 self.inner.lock().await.list_roots().await
2353 }
2354
2355 /// Register an elicitation handler for processing server requests for user information
2356 ///
2357 /// Elicitation handlers respond to server requests for additional information
2358 /// from users during interactions. This enables interactive workflows where
2359 /// servers can gather necessary information dynamically.
2360 ///
2361 /// # Arguments
2362 ///
2363 /// * `handler` - The elicitation handler implementation
2364 ///
2365 /// # Examples
2366 ///
2367 /// ```rust,no_run
2368 /// use turbomcp_client::{Client, SharedClient};
2369 /// use turbomcp_client::handlers::{ElicitationHandler, ElicitationRequest, ElicitationResponse, ElicitationAction, HandlerResult};
2370 /// use turbomcp_transport::stdio::StdioTransport;
2371 /// use async_trait::async_trait;
2372 /// use std::sync::Arc;
2373 ///
2374 /// #[derive(Debug)]
2375 /// struct MyElicitationHandler;
2376 ///
2377 /// #[async_trait]
2378 /// impl ElicitationHandler for MyElicitationHandler {
2379 /// async fn handle_elicitation(&self, request: ElicitationRequest) -> HandlerResult<ElicitationResponse> {
2380 /// // Process user input request and return response
2381 /// Ok(ElicitationResponse {
2382 /// action: ElicitationAction::Accept,
2383 /// content: Some(serde_json::json!({"name": "example"})),
2384 /// })
2385 /// }
2386 /// }
2387 ///
2388 /// # async fn example() -> turbomcp_core::Result<()> {
2389 /// let shared = SharedClient::new(Client::new(StdioTransport::new()));
2390 /// shared.on_elicitation(Arc::new(MyElicitationHandler)).await;
2391 /// # Ok(())
2392 /// # }
2393 /// ```
2394 pub async fn on_elicitation(&self, handler: Arc<dyn crate::handlers::ElicitationHandler>) {
2395 self.inner.lock().await.on_elicitation(handler);
2396 }
2397
2398 /// Register a progress handler for processing server progress notifications
2399 ///
2400 /// Progress handlers receive updates about long-running operations on the server.
2401 /// This enables progress bars, status updates, and better user experience during
2402 /// extended operations.
2403 ///
2404 /// # Arguments
2405 ///
2406 /// * `handler` - The progress handler implementation
2407 pub async fn on_progress(&self, handler: Arc<dyn crate::handlers::ProgressHandler>) {
2408 self.inner.lock().await.on_progress(handler);
2409 }
2410
2411 /// Register a log handler for processing server log messages
2412 ///
2413 /// Log handlers receive log messages from the server and can route them
2414 /// to the client's logging system. This is useful for debugging and
2415 /// maintaining a unified log across client and server.
2416 ///
2417 /// # Arguments
2418 ///
2419 /// * `handler` - The log handler implementation
2420 pub async fn on_log(&self, handler: Arc<dyn crate::handlers::LogHandler>) {
2421 self.inner.lock().await.on_log(handler);
2422 }
2423
2424 /// Register a resource update handler for processing resource change notifications
2425 ///
2426 /// Resource update handlers receive notifications when subscribed resources
2427 /// change on the server. This enables reactive updates to cached data or
2428 /// UI refreshes when server-side resources change.
2429 ///
2430 /// # Arguments
2431 ///
2432 /// * `handler` - The resource update handler implementation
2433 pub async fn on_resource_update(
2434 &self,
2435 handler: Arc<dyn crate::handlers::ResourceUpdateHandler>,
2436 ) {
2437 self.inner.lock().await.on_resource_update(handler);
2438 }
2439
2440 /// Check if an elicitation handler is registered
2441 pub async fn has_elicitation_handler(&self) -> bool {
2442 self.inner.lock().await.has_elicitation_handler()
2443 }
2444
2445 /// Check if a progress handler is registered
2446 pub async fn has_progress_handler(&self) -> bool {
2447 self.inner.lock().await.has_progress_handler()
2448 }
2449
2450 /// Check if a log handler is registered
2451 pub async fn has_log_handler(&self) -> bool {
2452 self.inner.lock().await.has_log_handler()
2453 }
2454
2455 /// Check if a resource update handler is registered
2456 pub async fn has_resource_update_handler(&self) -> bool {
2457 self.inner.lock().await.has_resource_update_handler()
2458 }
2459}
2460
2461impl<T: Transport> Clone for SharedClient<T> {
2462 /// Clone the shared client for use in multiple async tasks
2463 ///
2464 /// This creates a new reference to the same underlying client,
2465 /// allowing multiple tasks to share access safely.
2466 fn clone(&self) -> Self {
2467 Self {
2468 inner: Arc::clone(&self.inner),
2469 }
2470 }
2471}
2472
2473/// Result of client initialization
2474///
2475/// Contains information about the server and the negotiated capabilities
2476/// after a successful initialization handshake.
2477///
2478/// # Examples
2479///
2480/// ```rust,no_run
2481/// # use turbomcp_client::Client;
2482/// # use turbomcp_transport::stdio::StdioTransport;
2483/// # async fn example() -> turbomcp_core::Result<()> {
2484/// let mut client = Client::new(StdioTransport::new());
2485/// let result = client.initialize().await?;
2486///
2487/// println!("Server: {}", result.server_info.name);
2488/// println!("Version: {}", result.server_info.version);
2489/// if let Some(title) = result.server_info.title {
2490/// println!("Title: {}", title);
2491/// }
2492/// # Ok(())
2493/// # }
2494/// ```
2495#[derive(Debug)]
2496pub struct InitializeResult {
2497 /// Information about the server
2498 pub server_info: turbomcp_protocol::Implementation,
2499
2500 /// Capabilities supported by the server
2501 pub server_capabilities: ServerCapabilities,
2502}
2503
2504// ServerCapabilities is now imported from turbomcp_protocol::types
2505
2506/// Connection configuration for the client
2507#[derive(Debug, Clone)]
2508pub struct ConnectionConfig {
2509 /// Request timeout in milliseconds
2510 pub timeout_ms: u64,
2511
2512 /// Maximum number of retry attempts
2513 pub max_retries: u32,
2514
2515 /// Retry delay in milliseconds
2516 pub retry_delay_ms: u64,
2517
2518 /// Keep-alive interval in milliseconds
2519 pub keepalive_ms: u64,
2520}
2521
2522impl Default for ConnectionConfig {
2523 fn default() -> Self {
2524 Self {
2525 timeout_ms: 30_000, // 30 seconds
2526 max_retries: 3, // 3 attempts
2527 retry_delay_ms: 1_000, // 1 second
2528 keepalive_ms: 60_000, // 60 seconds
2529 }
2530 }
2531}
2532
2533/// Builder for configuring and creating MCP clients
2534///
2535/// Provides a fluent interface for configuring client options before creation.
2536/// The enhanced builder pattern supports comprehensive configuration including:
2537/// - Protocol capabilities
2538/// - Plugin registration
2539/// - LLM provider configuration
2540/// - Handler registration
2541/// - Connection settings
2542/// - Session management
2543///
2544/// # Examples
2545///
2546/// Basic usage:
2547/// ```rust,no_run
2548/// use turbomcp_client::ClientBuilder;
2549/// use turbomcp_transport::stdio::StdioTransport;
2550///
2551/// # async fn example() -> turbomcp_core::Result<()> {
2552/// let client = ClientBuilder::new()
2553/// .with_tools(true)
2554/// .with_prompts(true)
2555/// .with_resources(false)
2556/// .build(StdioTransport::new());
2557/// # Ok(())
2558/// # }
2559/// ```
2560///
2561/// Advanced configuration:
2562/// ```rust,no_run
2563/// use turbomcp_client::{ClientBuilder, ConnectionConfig};
2564/// use turbomcp_client::plugins::{MetricsPlugin, PluginConfig};
2565/// use turbomcp_client::llm::{OpenAIProvider, LLMProviderConfig};
2566/// use turbomcp_transport::stdio::StdioTransport;
2567/// use std::sync::Arc;
2568///
2569/// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
2570/// let client = ClientBuilder::new()
2571/// .with_tools(true)
2572/// .with_prompts(true)
2573/// .with_resources(true)
2574/// .with_sampling(true)
2575/// .with_connection_config(ConnectionConfig {
2576/// timeout_ms: 60_000,
2577/// max_retries: 5,
2578/// retry_delay_ms: 2_000,
2579/// keepalive_ms: 30_000,
2580/// })
2581/// .with_plugin(Arc::new(MetricsPlugin::new(PluginConfig::Metrics)))
2582/// .with_llm_provider("openai", Arc::new(OpenAIProvider::new(LLMProviderConfig {
2583/// api_key: std::env::var("OPENAI_API_KEY")?,
2584/// model: "gpt-4".to_string(),
2585/// ..Default::default()
2586/// })?))
2587/// .build(StdioTransport::new())
2588/// .await?;
2589/// # Ok(())
2590/// # }
2591/// ```
2592#[derive(Debug, Default)]
2593pub struct ClientBuilder {
2594 capabilities: ClientCapabilities,
2595 connection_config: ConnectionConfig,
2596 plugins: Vec<Arc<dyn crate::plugins::ClientPlugin>>,
2597 llm_providers: HashMap<String, Arc<dyn crate::llm::LLMProvider>>,
2598 elicitation_handler: Option<Arc<dyn crate::handlers::ElicitationHandler>>,
2599 progress_handler: Option<Arc<dyn crate::handlers::ProgressHandler>>,
2600 log_handler: Option<Arc<dyn crate::handlers::LogHandler>>,
2601 resource_update_handler: Option<Arc<dyn crate::handlers::ResourceUpdateHandler>>,
2602 session_config: Option<crate::llm::SessionConfig>,
2603}
2604
2605// Default implementation is now derived
2606
2607impl ClientBuilder {
2608 /// Create a new client builder
2609 ///
2610 /// Returns a new builder with default configuration.
2611 pub fn new() -> Self {
2612 Self::default()
2613 }
2614
2615 // ============================================================================
2616 // CAPABILITY CONFIGURATION
2617 // ============================================================================
2618
2619 /// Enable or disable tool support
2620 ///
2621 /// # Arguments
2622 ///
2623 /// * `enabled` - Whether to enable tool support
2624 pub fn with_tools(mut self, enabled: bool) -> Self {
2625 self.capabilities.tools = enabled;
2626 self
2627 }
2628
2629 /// Enable or disable prompt support
2630 ///
2631 /// # Arguments
2632 ///
2633 /// * `enabled` - Whether to enable prompt support
2634 pub fn with_prompts(mut self, enabled: bool) -> Self {
2635 self.capabilities.prompts = enabled;
2636 self
2637 }
2638
2639 /// Enable or disable resource support
2640 ///
2641 /// # Arguments
2642 ///
2643 /// * `enabled` - Whether to enable resource support
2644 pub fn with_resources(mut self, enabled: bool) -> Self {
2645 self.capabilities.resources = enabled;
2646 self
2647 }
2648
2649 /// Enable or disable sampling support
2650 ///
2651 /// # Arguments
2652 ///
2653 /// * `enabled` - Whether to enable sampling support
2654 pub fn with_sampling(mut self, enabled: bool) -> Self {
2655 self.capabilities.sampling = enabled;
2656 self
2657 }
2658
2659 /// Configure all capabilities at once
2660 ///
2661 /// # Arguments
2662 ///
2663 /// * `capabilities` - The capabilities configuration
2664 pub fn with_capabilities(mut self, capabilities: ClientCapabilities) -> Self {
2665 self.capabilities = capabilities;
2666 self
2667 }
2668
2669 // ============================================================================
2670 // CONNECTION CONFIGURATION
2671 // ============================================================================
2672
2673 /// Configure connection settings
2674 ///
2675 /// # Arguments
2676 ///
2677 /// * `config` - The connection configuration
2678 pub fn with_connection_config(mut self, config: ConnectionConfig) -> Self {
2679 self.connection_config = config;
2680 self
2681 }
2682
2683 /// Set request timeout
2684 ///
2685 /// # Arguments
2686 ///
2687 /// * `timeout_ms` - Timeout in milliseconds
2688 pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
2689 self.connection_config.timeout_ms = timeout_ms;
2690 self
2691 }
2692
2693 /// Set maximum retry attempts
2694 ///
2695 /// # Arguments
2696 ///
2697 /// * `max_retries` - Maximum number of retries
2698 pub fn with_max_retries(mut self, max_retries: u32) -> Self {
2699 self.connection_config.max_retries = max_retries;
2700 self
2701 }
2702
2703 /// Set retry delay
2704 ///
2705 /// # Arguments
2706 ///
2707 /// * `delay_ms` - Retry delay in milliseconds
2708 pub fn with_retry_delay(mut self, delay_ms: u64) -> Self {
2709 self.connection_config.retry_delay_ms = delay_ms;
2710 self
2711 }
2712
2713 /// Set keep-alive interval
2714 ///
2715 /// # Arguments
2716 ///
2717 /// * `interval_ms` - Keep-alive interval in milliseconds
2718 pub fn with_keepalive(mut self, interval_ms: u64) -> Self {
2719 self.connection_config.keepalive_ms = interval_ms;
2720 self
2721 }
2722
2723 // ============================================================================
2724 // PLUGIN SYSTEM CONFIGURATION
2725 // ============================================================================
2726
2727 /// Register a plugin with the client
2728 ///
2729 /// Plugins provide middleware functionality for request/response processing,
2730 /// metrics collection, retry logic, caching, and other cross-cutting concerns.
2731 ///
2732 /// # Arguments
2733 ///
2734 /// * `plugin` - The plugin implementation
2735 ///
2736 /// # Examples
2737 ///
2738 /// ```rust,no_run
2739 /// use turbomcp_client::{ClientBuilder, ConnectionConfig};
2740 /// use turbomcp_client::plugins::{MetricsPlugin, RetryPlugin, PluginConfig, RetryConfig};
2741 /// use std::sync::Arc;
2742 ///
2743 /// let client = ClientBuilder::new()
2744 /// .with_plugin(Arc::new(MetricsPlugin::new(PluginConfig::Metrics)))
2745 /// .with_plugin(Arc::new(RetryPlugin::new(PluginConfig::Retry(RetryConfig {
2746 /// max_retries: 5,
2747 /// base_delay_ms: 1000,
2748 /// max_delay_ms: 30000,
2749 /// backoff_multiplier: 2.0,
2750 /// retry_on_timeout: true,
2751 /// retry_on_connection_error: true,
2752 /// }))));
2753 /// ```
2754 pub fn with_plugin(mut self, plugin: Arc<dyn crate::plugins::ClientPlugin>) -> Self {
2755 self.plugins.push(plugin);
2756 self
2757 }
2758
2759 /// Register multiple plugins at once
2760 ///
2761 /// # Arguments
2762 ///
2763 /// * `plugins` - Vector of plugin implementations
2764 pub fn with_plugins(mut self, plugins: Vec<Arc<dyn crate::plugins::ClientPlugin>>) -> Self {
2765 self.plugins.extend(plugins);
2766 self
2767 }
2768
2769 // ============================================================================
2770 // LLM PROVIDER CONFIGURATION
2771 // ============================================================================
2772
2773 /// Register an LLM provider
2774 ///
2775 /// LLM providers handle server-initiated sampling requests by forwarding them
2776 /// to language model services like OpenAI, Anthropic, or local models.
2777 ///
2778 /// # Arguments
2779 ///
2780 /// * `name` - Unique name for the provider
2781 /// * `provider` - The LLM provider implementation
2782 ///
2783 /// # Examples
2784 ///
2785 /// ```rust,no_run
2786 /// use turbomcp_client::ClientBuilder;
2787 /// use turbomcp_client::llm::{OpenAIProvider, AnthropicProvider, LLMProviderConfig};
2788 /// use std::sync::Arc;
2789 ///
2790 /// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
2791 /// let client = ClientBuilder::new()
2792 /// .with_llm_provider("openai", Arc::new(OpenAIProvider::new(LLMProviderConfig {
2793 /// api_key: std::env::var("OPENAI_API_KEY")?,
2794 /// model: "gpt-4".to_string(),
2795 /// ..Default::default()
2796 /// })?))
2797 /// .with_llm_provider("anthropic", Arc::new(AnthropicProvider::new(LLMProviderConfig {
2798 /// api_key: std::env::var("ANTHROPIC_API_KEY")?,
2799 /// model: "claude-3-5-sonnet-20241022".to_string(),
2800 /// ..Default::default()
2801 /// })?));
2802 /// # Ok(())
2803 /// # }
2804 /// ```
2805 pub fn with_llm_provider(
2806 mut self,
2807 name: impl Into<String>,
2808 provider: Arc<dyn crate::llm::LLMProvider>,
2809 ) -> Self {
2810 self.llm_providers.insert(name.into(), provider);
2811 self
2812 }
2813
2814 /// Register multiple LLM providers at once
2815 ///
2816 /// # Arguments
2817 ///
2818 /// * `providers` - Map of provider names to implementations
2819 pub fn with_llm_providers(
2820 mut self,
2821 providers: HashMap<String, Arc<dyn crate::llm::LLMProvider>>,
2822 ) -> Self {
2823 self.llm_providers.extend(providers);
2824 self
2825 }
2826
2827 /// Configure session management for conversations
2828 ///
2829 /// # Arguments
2830 ///
2831 /// * `config` - Session configuration for conversation tracking
2832 pub fn with_session_config(mut self, config: crate::llm::SessionConfig) -> Self {
2833 self.session_config = Some(config);
2834 self
2835 }
2836
2837 // ============================================================================
2838 // HANDLER REGISTRATION
2839 // ============================================================================
2840
2841 /// Register an elicitation handler for processing user input requests
2842 ///
2843 /// # Arguments
2844 ///
2845 /// * `handler` - The elicitation handler implementation
2846 pub fn with_elicitation_handler(
2847 mut self,
2848 handler: Arc<dyn crate::handlers::ElicitationHandler>,
2849 ) -> Self {
2850 self.elicitation_handler = Some(handler);
2851 self
2852 }
2853
2854 /// Register a progress handler for processing operation progress updates
2855 ///
2856 /// # Arguments
2857 ///
2858 /// * `handler` - The progress handler implementation
2859 pub fn with_progress_handler(
2860 mut self,
2861 handler: Arc<dyn crate::handlers::ProgressHandler>,
2862 ) -> Self {
2863 self.progress_handler = Some(handler);
2864 self
2865 }
2866
2867 /// Register a log handler for processing server log messages
2868 ///
2869 /// # Arguments
2870 ///
2871 /// * `handler` - The log handler implementation
2872 pub fn with_log_handler(mut self, handler: Arc<dyn crate::handlers::LogHandler>) -> Self {
2873 self.log_handler = Some(handler);
2874 self
2875 }
2876
2877 /// Register a resource update handler for processing resource change notifications
2878 ///
2879 /// # Arguments
2880 ///
2881 /// * `handler` - The resource update handler implementation
2882 pub fn with_resource_update_handler(
2883 mut self,
2884 handler: Arc<dyn crate::handlers::ResourceUpdateHandler>,
2885 ) -> Self {
2886 self.resource_update_handler = Some(handler);
2887 self
2888 }
2889
2890 // ============================================================================
2891 // BUILD METHODS
2892 // ============================================================================
2893
2894 /// Build a client with the configured options
2895 ///
2896 /// Creates a new client instance with all the configured options. The client
2897 /// will be initialized with the registered plugins, handlers, and providers.
2898 ///
2899 /// # Arguments
2900 ///
2901 /// * `transport` - The transport to use for the client
2902 ///
2903 /// # Returns
2904 ///
2905 /// Returns a configured `Client` instance wrapped in a Result for async setup.
2906 ///
2907 /// # Examples
2908 ///
2909 /// ```rust,no_run
2910 /// use turbomcp_client::ClientBuilder;
2911 /// use turbomcp_transport::stdio::StdioTransport;
2912 ///
2913 /// # async fn example() -> turbomcp_core::Result<()> {
2914 /// let client = ClientBuilder::new()
2915 /// .with_tools(true)
2916 /// .with_prompts(true)
2917 /// .build(StdioTransport::new())
2918 /// .await?;
2919 /// # Ok(())
2920 /// # }
2921 /// ```
2922 pub async fn build<T: Transport>(self, transport: T) -> Result<Client<T>> {
2923 // Create base client with capabilities
2924 let mut client = Client::with_capabilities(transport, self.capabilities);
2925
2926 // Register handlers
2927 if let Some(handler) = self.elicitation_handler {
2928 client.on_elicitation(handler);
2929 }
2930 if let Some(handler) = self.progress_handler {
2931 client.on_progress(handler);
2932 }
2933 if let Some(handler) = self.log_handler {
2934 client.on_log(handler);
2935 }
2936 if let Some(handler) = self.resource_update_handler {
2937 client.on_resource_update(handler);
2938 }
2939
2940 // Set up LLM providers if any are configured
2941 if !self.llm_providers.is_empty() {
2942 // Create LLM registry and register providers
2943 let mut registry = crate::llm::LLMRegistry::new();
2944 for (name, provider) in self.llm_providers {
2945 registry
2946 .register_provider(&name, provider)
2947 .await
2948 .map_err(|e| {
2949 Error::configuration(format!(
2950 "Failed to register LLM provider '{}': {}",
2951 name, e
2952 ))
2953 })?;
2954 }
2955
2956 // Configure session management if provided
2957 if let Some(session_config) = self.session_config {
2958 registry
2959 .configure_sessions(session_config)
2960 .await
2961 .map_err(|e| {
2962 Error::configuration(format!("Failed to configure sessions: {}", e))
2963 })?;
2964 }
2965
2966 // Set up the registry as the sampling handler
2967 let sampling_handler = Arc::new(registry);
2968 client.set_sampling_handler(sampling_handler);
2969 }
2970
2971 // Apply connection configuration (store for future use in actual connections)
2972 // Note: The current Client doesn't expose connection config setters,
2973 // so we'll store this for when the transport supports it
2974
2975 // Register plugins with the client
2976 let has_plugins = !self.plugins.is_empty();
2977 for plugin in self.plugins {
2978 client.register_plugin(plugin).await.map_err(|e| {
2979 Error::bad_request(format!("Failed to register plugin during build: {}", e))
2980 })?;
2981 }
2982
2983 // Initialize plugins after registration
2984 if has_plugins {
2985 client.initialize_plugins().await.map_err(|e| {
2986 Error::bad_request(format!("Failed to initialize plugins during build: {}", e))
2987 })?;
2988 }
2989
2990 Ok(client)
2991 }
2992
2993 /// Build a client synchronously with basic configuration only
2994 ///
2995 /// This is a convenience method for simple use cases where no async setup
2996 /// is required. For advanced features like LLM providers, use `build()` instead.
2997 ///
2998 /// # Arguments
2999 ///
3000 /// * `transport` - The transport to use for the client
3001 ///
3002 /// # Returns
3003 ///
3004 /// Returns a configured `Client` instance.
3005 ///
3006 /// # Examples
3007 ///
3008 /// ```rust,no_run
3009 /// use turbomcp_client::ClientBuilder;
3010 /// use turbomcp_transport::stdio::StdioTransport;
3011 ///
3012 /// let client = ClientBuilder::new()
3013 /// .with_tools(true)
3014 /// .build_sync(StdioTransport::new());
3015 /// ```
3016 pub fn build_sync<T: Transport>(self, transport: T) -> Client<T> {
3017 let mut client = Client::with_capabilities(transport, self.capabilities);
3018
3019 // Register synchronous handlers only
3020 if let Some(handler) = self.elicitation_handler {
3021 client.on_elicitation(handler);
3022 }
3023 if let Some(handler) = self.progress_handler {
3024 client.on_progress(handler);
3025 }
3026 if let Some(handler) = self.log_handler {
3027 client.on_log(handler);
3028 }
3029 if let Some(handler) = self.resource_update_handler {
3030 client.on_resource_update(handler);
3031 }
3032
3033 client
3034 }
3035
3036 // ============================================================================
3037 // CONFIGURATION ACCESS
3038 // ============================================================================
3039
3040 /// Get the current capabilities configuration
3041 pub fn capabilities(&self) -> &ClientCapabilities {
3042 &self.capabilities
3043 }
3044
3045 /// Get the current connection configuration
3046 pub fn connection_config(&self) -> &ConnectionConfig {
3047 &self.connection_config
3048 }
3049
3050 /// Get the number of registered plugins
3051 pub fn plugin_count(&self) -> usize {
3052 self.plugins.len()
3053 }
3054
3055 /// Get the number of registered LLM providers
3056 pub fn llm_provider_count(&self) -> usize {
3057 self.llm_providers.len()
3058 }
3059
3060 /// Check if any handlers are registered
3061 pub fn has_handlers(&self) -> bool {
3062 self.elicitation_handler.is_some()
3063 || self.progress_handler.is_some()
3064 || self.log_handler.is_some()
3065 || self.resource_update_handler.is_some()
3066 }
3067}
3068
3069// Re-export types for public API
3070pub use turbomcp_protocol::types::ServerCapabilities as PublicServerCapabilities;