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_protocol::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.name, resource.uri);
58//! }
59//! # Ok(())
60//! # }
61//! ```
62//!
63//! ## Elicitation Response Handling
64//!
65//! The client 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
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::{ContentBlock, CreateMessageRequest, CreateMessageResult, Role, StopReason, TextContent};
92//! use std::future::Future;
93//! use std::pin::Pin;
94//!
95//! #[derive(Debug)]
96//! struct MySamplingHandler {
97//! // Your LLM client would go here
98//! }
99//!
100//! impl SamplingHandler for MySamplingHandler {
101//! fn handle_create_message(
102//! &self,
103//! request_id: String,
104//! request: CreateMessageRequest
105//! ) -> Pin<Box<dyn Future<Output = Result<CreateMessageResult, Box<dyn std::error::Error + Send + Sync>>> + Send + '_>> {
106//! Box::pin(async move {
107//! // Forward to your LLM provider (OpenAI, Anthropic, etc.)
108//! // Use request_id for correlation tracking
109//! // Allows the server to request LLM sampling through the client
110//!
111//! Ok(CreateMessageResult {
112//! role: Role::Assistant,
113//! content: ContentBlock::Text(
114//! TextContent {
115//! text: "Response from LLM".to_string(),
116//! annotations: None,
117//! meta: None,
118//! }
119//! ),
120//! model: "gpt-4".to_string(),
121//! stop_reason: Some(StopReason::EndTurn),
122//! _meta: None,
123//! })
124//! })
125//! }
126//! }
127//! ```
128//!
129//! ## Error Handling
130//!
131//! The client provides comprehensive error handling with automatic retry logic:
132//!
133//! ```rust,no_run
134//! # use turbomcp_client::Client;
135//! # use turbomcp_transport::stdio::StdioTransport;
136//! # async fn example() -> turbomcp_protocol::Result<()> {
137//! # let mut client = Client::new(StdioTransport::new());
138//! match client.call_tool("my_tool", None, None).await {
139//! Ok(result) => println!("Tool result: {:?}", result),
140//! Err(e) => eprintln!("Tool call failed: {}", e),
141//! }
142//! # Ok(())
143//! # }
144//! ```
145
146/// TurboMCP Client version from Cargo.toml
147///
148/// This constant provides easy programmatic access to the current version.
149///
150/// # Example
151///
152/// ```rust
153/// println!("TurboMCP Client version: {}", turbomcp_client::VERSION);
154/// ```
155pub const VERSION: &str = env!("CARGO_PKG_VERSION");
156
157/// TurboMCP Client crate name
158pub const CRATE_NAME: &str = env!("CARGO_PKG_NAME");
159
160pub mod client;
161pub mod handlers;
162pub mod integration;
163pub mod prelude;
164pub mod sampling;
165
166// v3.0 Tower-native middleware
167pub mod middleware;
168
169// Re-export key types for convenience
170pub use client::{ConnectionInfo, ConnectionState, ManagerConfig, ServerGroup, SessionManager};
171
172use std::sync::Arc;
173use std::time::Duration;
174
175// Re-export Transport trait for generic bounds in integrations
176pub use turbomcp_transport::Transport;
177
178// ============================================================================
179// TOP-LEVEL RE-EXPORTS FOR ERGONOMIC IMPORTS
180// ============================================================================
181
182// Result/Error types - re-export from protocol for consistency
183pub use turbomcp_protocol::{Error, Result};
184
185// Handler types (most commonly used)
186pub use handlers::{
187 // Cancellation (current MCP spec)
188 CancellationHandler,
189 CancelledNotification,
190 ElicitationAction,
191 // Elicitation
192 ElicitationHandler,
193 ElicitationRequest,
194 ElicitationResponse,
195 // Error handling
196 HandlerError,
197 HandlerResult,
198 // Logging (current MCP spec)
199 LogHandler,
200 LoggingNotification,
201 PromptListChangedHandler,
202 // List changed handlers (current MCP spec)
203 ResourceListChangedHandler,
204 // Resource updates (current MCP spec)
205 ResourceUpdateHandler,
206 ResourceUpdatedNotification,
207 // Roots
208 RootsHandler,
209 ToolListChangedHandler,
210};
211
212// Sampling types
213pub use sampling::{SamplingHandler, ServerInfo, UserInteractionHandler};
214
215// v3.0 Tower middleware
216pub use middleware::{
217 Cache, CacheConfig, CacheLayer, CacheService, McpRequest, McpResponse, Metrics, MetricsLayer,
218 MetricsService, MetricsSnapshot, TracingLayer, TracingService,
219};
220
221// Common protocol types
222pub use turbomcp_protocol::types::{
223 // Resource content types (for processing embedded resources)
224 BlobResourceContents,
225 // Tool result types (for LLM integrations like rig)
226 CallToolResult,
227 // Core types
228 ContentBlock,
229 EmbeddedResource,
230 LogLevel,
231 Prompt,
232 Resource,
233 ResourceContent,
234 ResourceContents,
235 Role,
236 TextResourceContents,
237 Tool,
238};
239
240// Transport re-exports (with feature gates)
241#[cfg(feature = "stdio")]
242pub use turbomcp_transport::stdio::StdioTransport;
243
244#[cfg(feature = "http")]
245pub use turbomcp_transport::streamable_http_client::{
246 RetryPolicy, StreamableHttpClientConfig, StreamableHttpClientTransport,
247};
248
249#[cfg(feature = "tcp")]
250pub use turbomcp_transport::tcp::{TcpTransport, TcpTransportBuilder};
251
252#[cfg(feature = "unix")]
253pub use turbomcp_transport::unix::{UnixTransport, UnixTransportBuilder};
254
255#[cfg(feature = "websocket")]
256pub use turbomcp_transport::websocket_bidirectional::{
257 WebSocketBidirectionalConfig, WebSocketBidirectionalTransport,
258};
259
260/// Client capability configuration
261///
262/// Defines the capabilities that this client supports when connecting to MCP servers.
263/// These capabilities are sent during the initialization handshake to negotiate
264/// which features will be available during the session.
265///
266/// # Examples
267///
268/// ```
269/// use turbomcp_client::ClientCapabilities;
270///
271/// let capabilities = ClientCapabilities {
272/// tools: true,
273/// prompts: true,
274/// resources: true,
275/// sampling: false,
276/// max_concurrent_handlers: 100,
277/// };
278/// ```
279#[derive(Debug, Clone)]
280pub struct ClientCapabilities {
281 /// Whether the client supports tool calling
282 pub tools: bool,
283
284 /// Whether the client supports prompts
285 pub prompts: bool,
286
287 /// Whether the client supports resources
288 pub resources: bool,
289
290 /// Whether the client supports sampling
291 pub sampling: bool,
292
293 /// Maximum concurrent request/notification handlers (default: 100)
294 ///
295 /// This limits how many server-initiated requests/notifications can be processed simultaneously.
296 /// Provides automatic backpressure when the limit is reached.
297 ///
298 /// **Tuning Guide:**
299 /// - Low-resource clients: 50
300 /// - Standard clients: 100 (default)
301 /// - High-performance: 200-500
302 /// - Maximum recommended: 1000
303 pub max_concurrent_handlers: usize,
304}
305
306impl Default for ClientCapabilities {
307 fn default() -> Self {
308 Self {
309 tools: false,
310 prompts: false,
311 resources: false,
312 sampling: false,
313 max_concurrent_handlers: 100,
314 }
315 }
316}
317
318impl ClientCapabilities {
319 /// All capabilities enabled (tools, prompts, resources, sampling)
320 ///
321 /// This is the most comprehensive configuration, enabling full MCP protocol support.
322 ///
323 /// # Example
324 ///
325 /// ```rust
326 /// use turbomcp_client::ClientCapabilities;
327 ///
328 /// let capabilities = ClientCapabilities::all();
329 /// assert!(capabilities.tools);
330 /// assert!(capabilities.prompts);
331 /// assert!(capabilities.resources);
332 /// assert!(capabilities.sampling);
333 /// ```
334 #[must_use]
335 pub fn all() -> Self {
336 Self {
337 tools: true,
338 prompts: true,
339 resources: true,
340 sampling: true,
341 max_concurrent_handlers: 100,
342 }
343 }
344
345 /// Core capabilities without sampling (tools, prompts, resources)
346 ///
347 /// This is the recommended default for most applications. It enables
348 /// all standard MCP features except server-initiated sampling requests.
349 ///
350 /// # Example
351 ///
352 /// ```rust
353 /// use turbomcp_client::ClientCapabilities;
354 ///
355 /// let capabilities = ClientCapabilities::core();
356 /// assert!(capabilities.tools);
357 /// assert!(capabilities.prompts);
358 /// assert!(capabilities.resources);
359 /// assert!(!capabilities.sampling);
360 /// ```
361 #[must_use]
362 pub fn core() -> Self {
363 Self {
364 tools: true,
365 prompts: true,
366 resources: true,
367 sampling: false,
368 max_concurrent_handlers: 100,
369 }
370 }
371
372 /// Minimal capabilities (tools only)
373 ///
374 /// Use this for simple tool-calling clients that don't need prompts,
375 /// resources, or sampling support.
376 ///
377 /// # Example
378 ///
379 /// ```rust
380 /// use turbomcp_client::ClientCapabilities;
381 ///
382 /// let capabilities = ClientCapabilities::minimal();
383 /// assert!(capabilities.tools);
384 /// assert!(!capabilities.prompts);
385 /// assert!(!capabilities.resources);
386 /// assert!(!capabilities.sampling);
387 /// ```
388 #[must_use]
389 pub fn minimal() -> Self {
390 Self {
391 tools: true,
392 prompts: false,
393 resources: false,
394 sampling: false,
395 max_concurrent_handlers: 100,
396 }
397 }
398
399 /// Only tools enabled
400 ///
401 /// Same as `minimal()`, provided for clarity.
402 #[must_use]
403 pub fn only_tools() -> Self {
404 Self::minimal()
405 }
406
407 /// Only resources enabled
408 ///
409 /// Use this for resource-focused clients that don't need tools or prompts.
410 ///
411 /// # Example
412 ///
413 /// ```rust
414 /// use turbomcp_client::ClientCapabilities;
415 ///
416 /// let capabilities = ClientCapabilities::only_resources();
417 /// assert!(!capabilities.tools);
418 /// assert!(!capabilities.prompts);
419 /// assert!(capabilities.resources);
420 /// ```
421 #[must_use]
422 pub fn only_resources() -> Self {
423 Self {
424 tools: false,
425 prompts: false,
426 resources: true,
427 sampling: false,
428 max_concurrent_handlers: 100,
429 }
430 }
431
432 /// Only prompts enabled
433 ///
434 /// Use this for prompt-focused clients that don't need tools or resources.
435 ///
436 /// # Example
437 ///
438 /// ```rust
439 /// use turbomcp_client::ClientCapabilities;
440 ///
441 /// let capabilities = ClientCapabilities::only_prompts();
442 /// assert!(!capabilities.tools);
443 /// assert!(capabilities.prompts);
444 /// assert!(!capabilities.resources);
445 /// ```
446 #[must_use]
447 pub fn only_prompts() -> Self {
448 Self {
449 tools: false,
450 prompts: true,
451 resources: false,
452 sampling: false,
453 max_concurrent_handlers: 100,
454 }
455 }
456
457 /// Only sampling enabled
458 ///
459 /// Use this for clients that exclusively handle server-initiated sampling requests.
460 #[must_use]
461 pub fn only_sampling() -> Self {
462 Self {
463 tools: false,
464 prompts: false,
465 resources: false,
466 sampling: true,
467 max_concurrent_handlers: 100,
468 }
469 }
470}
471
472/// JSON-RPC protocol handler for MCP communication
473// Note: ProtocolClient implementation moved to client/protocol.rs for better modularity
474/// MCP client for communicating with servers
475///
476/// The `Client` struct provides an ergonomic interface for interacting with MCP servers.
477/// It handles protocol complexity internally, exposing clean, type-safe methods.
478///
479/// # Type Parameters
480///
481/// * `T` - The transport implementation used for communication
482///
483/// # Examples
484///
485/// ```rust,no_run
486/// use turbomcp_client::Client;
487/// use turbomcp_transport::stdio::StdioTransport;
488///
489/// # async fn example() -> turbomcp_protocol::Result<()> {
490/// let transport = StdioTransport::new();
491/// let mut client = Client::new(transport);
492///
493/// // Initialize and start using the client
494/// client.initialize().await?;
495/// # Ok(())
496/// # }
497/// ```
498// Re-export Client from the core module
499pub use client::core::Client;
500
501// Thread-safe wrapper for sharing Client across async tasks
502//
503// This wrapper encapsulates the Arc/Mutex complexity and provides a clean API
504// for concurrent access to MCP client functionality. It addresses the limitations
505// identified in PR feedback where Client requires `&mut self` for all operations
506// but needs to be shared across multiple async tasks.
507//
508// # Design Rationale
509//
510// All Client methods require `&mut self` because:
511// - MCP connections maintain state (initialized flag, connection status)
512// - Request correlation tracking for JSON-RPC requires mutation
513// - Handler and plugin registries need mutable access
514//
515// Note: SharedClient has been removed in v2 - Client is now directly cloneable via Arc
516
517// ----------------------------------------------------------------------------
518// Re-exports
519// ----------------------------------------------------------------------------
520
521#[doc = "Result of client initialization"]
522#[doc = ""]
523#[doc = "Contains information about the server and the negotiated capabilities"]
524#[doc = "after a successful initialization handshake."]
525pub use client::config::InitializeResult;
526
527// ServerCapabilities is now imported from turbomcp_protocol::types
528
529/// Connection configuration for the client
530#[derive(Debug, Clone)]
531pub struct ConnectionConfig {
532 /// Request timeout in milliseconds
533 pub timeout_ms: u64,
534
535 /// Maximum number of retry attempts
536 pub max_retries: u32,
537
538 /// Retry delay in milliseconds
539 pub retry_delay_ms: u64,
540
541 /// Keep-alive interval in milliseconds
542 pub keepalive_ms: u64,
543}
544
545fn protocol_transport_config(
546 connection_config: &ConnectionConfig,
547) -> turbomcp_transport::TransportConfig {
548 let timeout = Duration::from_millis(connection_config.timeout_ms);
549
550 turbomcp_transport::TransportConfig {
551 connect_timeout: timeout,
552 keep_alive: Some(Duration::from_millis(connection_config.keepalive_ms)),
553 timeouts: turbomcp_transport::config::TimeoutConfig {
554 connect: timeout,
555 request: Some(timeout),
556 total: Some(timeout),
557 read: Some(timeout),
558 },
559 ..Default::default()
560 }
561}
562
563fn resilience_requested(builder: &ClientBuilder) -> bool {
564 builder.enable_resilience
565 || builder.retry_config.is_some()
566 || builder.circuit_breaker_config.is_some()
567 || builder.health_check_config.is_some()
568}
569
570impl Default for ConnectionConfig {
571 fn default() -> Self {
572 Self {
573 timeout_ms: 30_000, // 30 seconds
574 max_retries: 3, // 3 attempts
575 retry_delay_ms: 1_000, // 1 second
576 keepalive_ms: 60_000, // 60 seconds
577 }
578 }
579}
580
581/// Builder for configuring and creating MCP clients
582///
583/// Provides a fluent interface for configuring client options before creation.
584/// The enhanced builder pattern supports comprehensive configuration including:
585/// - Protocol capabilities
586/// - Plugin registration
587/// - Handler registration
588/// - Connection settings
589/// - Resilience configuration
590///
591/// # Examples
592///
593/// Basic usage:
594/// ```rust,no_run
595/// use turbomcp_client::ClientBuilder;
596/// use turbomcp_transport::stdio::StdioTransport;
597///
598/// # async fn example() -> turbomcp_protocol::Result<()> {
599/// let client = ClientBuilder::new()
600/// .with_tools(true)
601/// .with_prompts(true)
602/// .with_resources(false)
603/// .build(StdioTransport::new());
604/// # Ok(())
605/// # }
606/// ```
607///
608/// Advanced configuration with Tower middleware:
609/// ```rust,no_run
610/// use turbomcp_client::{ClientBuilder, ConnectionConfig};
611/// use turbomcp_client::middleware::MetricsLayer;
612/// use turbomcp_transport::stdio::StdioTransport;
613/// use tower::ServiceBuilder;
614///
615/// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
616/// let client = ClientBuilder::new()
617/// .with_tools(true)
618/// .with_prompts(true)
619/// .with_resources(true)
620/// .with_sampling(true)
621/// .with_connection_config(ConnectionConfig {
622/// timeout_ms: 60_000,
623/// max_retries: 5,
624/// retry_delay_ms: 2_000,
625/// keepalive_ms: 30_000,
626/// })
627/// .build(StdioTransport::new())
628/// .await?;
629/// # Ok(())
630/// # }
631/// ```
632#[derive(Debug, Default)]
633pub struct ClientBuilder {
634 capabilities: ClientCapabilities,
635 connection_config: ConnectionConfig,
636 elicitation_handler: Option<Arc<dyn crate::handlers::ElicitationHandler>>,
637 log_handler: Option<Arc<dyn crate::handlers::LogHandler>>,
638 resource_update_handler: Option<Arc<dyn crate::handlers::ResourceUpdateHandler>>,
639 // Robustness configuration
640 enable_resilience: bool,
641 retry_config: Option<turbomcp_transport::resilience::RetryConfig>,
642 circuit_breaker_config: Option<turbomcp_transport::resilience::CircuitBreakerConfig>,
643 health_check_config: Option<turbomcp_transport::resilience::HealthCheckConfig>,
644}
645
646// Default implementation is now derived
647
648impl ClientBuilder {
649 /// Create a new client builder
650 ///
651 /// Returns a new builder with default configuration.
652 #[must_use]
653 pub fn new() -> Self {
654 Self::default()
655 }
656
657 // ============================================================================
658 // CAPABILITY CONFIGURATION
659 // ============================================================================
660
661 /// Enable or disable tool support
662 ///
663 /// # Arguments
664 ///
665 /// * `enabled` - Whether to enable tool support
666 #[must_use]
667 pub fn with_tools(mut self, enabled: bool) -> Self {
668 self.capabilities.tools = enabled;
669 self
670 }
671
672 /// Enable or disable prompt support
673 ///
674 /// # Arguments
675 ///
676 /// * `enabled` - Whether to enable prompt support
677 #[must_use]
678 pub fn with_prompts(mut self, enabled: bool) -> Self {
679 self.capabilities.prompts = enabled;
680 self
681 }
682
683 /// Enable or disable resource support
684 ///
685 /// # Arguments
686 ///
687 /// * `enabled` - Whether to enable resource support
688 #[must_use]
689 pub fn with_resources(mut self, enabled: bool) -> Self {
690 self.capabilities.resources = enabled;
691 self
692 }
693
694 /// Enable or disable sampling support
695 ///
696 /// # Arguments
697 ///
698 /// * `enabled` - Whether to enable sampling support
699 #[must_use]
700 pub fn with_sampling(mut self, enabled: bool) -> Self {
701 self.capabilities.sampling = enabled;
702 self
703 }
704
705 /// Set maximum concurrent request/notification handlers
706 ///
707 /// This limits how many server-initiated requests/notifications can be processed simultaneously.
708 /// Provides automatic backpressure when the limit is reached.
709 ///
710 /// # Arguments
711 ///
712 /// * `limit` - Maximum concurrent handlers (default: 100)
713 ///
714 /// # Tuning Guide
715 ///
716 /// - Low-resource clients: 50
717 /// - Standard clients: 100 (default)
718 /// - High-performance: 200-500
719 /// - Maximum recommended: 1000
720 ///
721 /// # Example
722 ///
723 /// ```rust,no_run
724 /// use turbomcp_client::ClientBuilder;
725 /// # use turbomcp_transport::StdioTransport;
726 ///
727 /// let builder = ClientBuilder::new()
728 /// .with_max_concurrent_handlers(200);
729 /// ```
730 #[must_use]
731 pub fn with_max_concurrent_handlers(mut self, limit: usize) -> Self {
732 self.capabilities.max_concurrent_handlers = limit;
733 self
734 }
735
736 /// Configure all capabilities at once
737 ///
738 /// # Arguments
739 ///
740 /// * `capabilities` - The capabilities configuration
741 #[must_use]
742 pub fn with_capabilities(mut self, capabilities: ClientCapabilities) -> Self {
743 self.capabilities = capabilities;
744 self
745 }
746
747 // ============================================================================
748 // CONNECTION CONFIGURATION
749 // ============================================================================
750
751 /// Configure connection settings
752 ///
753 /// # Arguments
754 ///
755 /// * `config` - The connection configuration
756 #[must_use]
757 pub fn with_connection_config(mut self, config: ConnectionConfig) -> Self {
758 self.connection_config = config;
759 self
760 }
761
762 /// Set request timeout
763 ///
764 /// # Arguments
765 ///
766 /// * `timeout_ms` - Timeout in milliseconds
767 #[must_use]
768 pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
769 self.connection_config.timeout_ms = timeout_ms;
770 self
771 }
772
773 /// Set maximum retry attempts
774 ///
775 /// # Arguments
776 ///
777 /// * `max_retries` - Maximum number of retries
778 #[must_use]
779 pub fn with_max_retries(mut self, max_retries: u32) -> Self {
780 self.connection_config.max_retries = max_retries;
781 self
782 }
783
784 /// Set retry delay
785 ///
786 /// # Arguments
787 ///
788 /// * `delay_ms` - Retry delay in milliseconds
789 #[must_use]
790 pub fn with_retry_delay(mut self, delay_ms: u64) -> Self {
791 self.connection_config.retry_delay_ms = delay_ms;
792 self
793 }
794
795 /// Set keep-alive interval
796 ///
797 /// # Arguments
798 ///
799 /// * `interval_ms` - Keep-alive interval in milliseconds
800 #[must_use]
801 pub fn with_keepalive(mut self, interval_ms: u64) -> Self {
802 self.connection_config.keepalive_ms = interval_ms;
803 self
804 }
805
806 // ============================================================================
807 // ROBUSTNESS & RESILIENCE CONFIGURATION
808 // ============================================================================
809
810 /// Enable resilient transport with circuit breaker, retry, and health checking
811 ///
812 /// When enabled, the transport layer will automatically:
813 /// - Retry failed operations with exponential backoff
814 /// - Use circuit breaker pattern to prevent cascade failures
815 /// - Perform periodic health checks
816 /// - Deduplicate messages
817 ///
818 /// # Examples
819 ///
820 /// ```rust,no_run
821 /// use turbomcp_client::ClientBuilder;
822 /// use turbomcp_transport::stdio::StdioTransport;
823 ///
824 /// let client = ClientBuilder::new()
825 /// .enable_resilience()
826 /// .build(StdioTransport::new());
827 /// ```
828 #[must_use]
829 pub fn enable_resilience(mut self) -> Self {
830 self.enable_resilience = true;
831 self
832 }
833
834 /// Configure retry behavior for resilient transport
835 ///
836 /// # Arguments
837 ///
838 /// * `config` - Retry configuration
839 ///
840 /// # Examples
841 ///
842 /// ```rust,no_run
843 /// use turbomcp_client::ClientBuilder;
844 /// use turbomcp_transport::resilience::RetryConfig;
845 /// use turbomcp_transport::stdio::StdioTransport;
846 /// use std::time::Duration;
847 ///
848 /// let client = ClientBuilder::new()
849 /// .enable_resilience()
850 /// .with_retry_config(RetryConfig {
851 /// max_attempts: 5,
852 /// base_delay: Duration::from_millis(100),
853 /// max_delay: Duration::from_secs(30),
854 /// backoff_multiplier: 2.0,
855 /// jitter_factor: 0.1,
856 /// retry_on_connection_error: true,
857 /// retry_on_timeout: true,
858 /// custom_retry_conditions: Vec::new(),
859 /// })
860 /// .build(StdioTransport::new());
861 /// ```
862 #[must_use]
863 pub fn with_retry_config(
864 mut self,
865 config: turbomcp_transport::resilience::RetryConfig,
866 ) -> Self {
867 self.retry_config = Some(config);
868 self.enable_resilience = true; // Auto-enable resilience
869 self
870 }
871
872 /// Configure circuit breaker for resilient transport
873 ///
874 /// # Arguments
875 ///
876 /// * `config` - Circuit breaker configuration
877 ///
878 /// # Examples
879 ///
880 /// ```rust,no_run
881 /// use turbomcp_client::ClientBuilder;
882 /// use turbomcp_transport::resilience::CircuitBreakerConfig;
883 /// use turbomcp_transport::stdio::StdioTransport;
884 /// use std::time::Duration;
885 ///
886 /// let client = ClientBuilder::new()
887 /// .enable_resilience()
888 /// .with_circuit_breaker_config(CircuitBreakerConfig {
889 /// failure_threshold: 5,
890 /// success_threshold: 2,
891 /// timeout: Duration::from_secs(60),
892 /// rolling_window_size: 100,
893 /// minimum_requests: 10,
894 /// })
895 /// .build(StdioTransport::new());
896 /// ```
897 #[must_use]
898 pub fn with_circuit_breaker_config(
899 mut self,
900 config: turbomcp_transport::resilience::CircuitBreakerConfig,
901 ) -> Self {
902 self.circuit_breaker_config = Some(config);
903 self.enable_resilience = true; // Auto-enable resilience
904 self
905 }
906
907 /// Configure health checking for resilient transport
908 ///
909 /// # Arguments
910 ///
911 /// * `config` - Health check configuration
912 ///
913 /// # Examples
914 ///
915 /// ```rust,no_run
916 /// use turbomcp_client::ClientBuilder;
917 /// use turbomcp_transport::resilience::HealthCheckConfig;
918 /// use turbomcp_transport::stdio::StdioTransport;
919 /// use std::time::Duration;
920 ///
921 /// let client = ClientBuilder::new()
922 /// .enable_resilience()
923 /// .with_health_check_config(HealthCheckConfig {
924 /// interval: Duration::from_secs(30),
925 /// timeout: Duration::from_secs(5),
926 /// failure_threshold: 3,
927 /// success_threshold: 1,
928 /// custom_check: None,
929 /// })
930 /// .build(StdioTransport::new());
931 /// ```
932 #[must_use]
933 pub fn with_health_check_config(
934 mut self,
935 config: turbomcp_transport::resilience::HealthCheckConfig,
936 ) -> Self {
937 self.health_check_config = Some(config);
938 self.enable_resilience = true; // Auto-enable resilience
939 self
940 }
941
942 // ============================================================================
943 // HANDLER REGISTRATION
944 // ============================================================================
945
946 /// Register an elicitation handler for processing user input requests
947 ///
948 /// # Arguments
949 ///
950 /// * `handler` - The elicitation handler implementation
951 pub fn with_elicitation_handler(
952 mut self,
953 handler: Arc<dyn crate::handlers::ElicitationHandler>,
954 ) -> Self {
955 self.elicitation_handler = Some(handler);
956 self
957 }
958
959 /// Register a log handler for processing server log messages
960 ///
961 /// # Arguments
962 ///
963 /// * `handler` - The log handler implementation
964 pub fn with_log_handler(mut self, handler: Arc<dyn crate::handlers::LogHandler>) -> Self {
965 self.log_handler = Some(handler);
966 self
967 }
968
969 /// Register a resource update handler for processing resource change notifications
970 ///
971 /// # Arguments
972 ///
973 /// * `handler` - The resource update handler implementation
974 pub fn with_resource_update_handler(
975 mut self,
976 handler: Arc<dyn crate::handlers::ResourceUpdateHandler>,
977 ) -> Self {
978 self.resource_update_handler = Some(handler);
979 self
980 }
981
982 // ============================================================================
983 // BUILD METHODS
984 // ============================================================================
985
986 /// Build a client with the configured options
987 ///
988 /// Creates a new client instance with all the configured options. The client
989 /// will be initialized with the registered plugins, handlers, and providers.
990 ///
991 /// # Arguments
992 ///
993 /// * `transport` - The transport to use for the client
994 ///
995 /// # Returns
996 ///
997 /// Returns a configured `Client` instance wrapped in a Result for async setup.
998 ///
999 /// # Examples
1000 ///
1001 /// ```rust,no_run
1002 /// use turbomcp_client::ClientBuilder;
1003 /// use turbomcp_transport::stdio::StdioTransport;
1004 ///
1005 /// # async fn example() -> turbomcp_protocol::Result<()> {
1006 /// let client = ClientBuilder::new()
1007 /// .with_tools(true)
1008 /// .with_prompts(true)
1009 /// .build(StdioTransport::new())
1010 /// .await?;
1011 /// # Ok(())
1012 /// # }
1013 /// ```
1014 pub async fn build<T: Transport + 'static>(self, transport: T) -> Result<Client<T>> {
1015 if resilience_requested(&self) {
1016 return Err(Error::configuration(
1017 "resilience settings require build_resilient(); build() would otherwise ignore them"
1018 .to_string(),
1019 ));
1020 }
1021
1022 // Create base client with capabilities
1023 let client = Client::with_capabilities_and_config(
1024 transport,
1025 self.capabilities,
1026 protocol_transport_config(&self.connection_config),
1027 );
1028
1029 // Register handlers
1030 if let Some(handler) = self.elicitation_handler {
1031 client.set_elicitation_handler(handler);
1032 }
1033 if let Some(handler) = self.log_handler {
1034 client.set_log_handler(handler);
1035 }
1036 if let Some(handler) = self.resource_update_handler {
1037 client.set_resource_update_handler(handler);
1038 }
1039
1040 Ok(client)
1041 }
1042
1043 /// Build a client with resilient transport (circuit breaker, retry, health checking)
1044 ///
1045 /// When resilience features are enabled via `enable_resilience()` or any resilience
1046 /// configuration method, this wraps the transport in a `TurboTransport` that provides:
1047 /// - Automatic retry with exponential backoff
1048 /// - Circuit breaker pattern for fast failure
1049 /// - Health checking and monitoring
1050 /// - Message deduplication
1051 ///
1052 /// # Arguments
1053 ///
1054 /// * `transport` - The base transport to wrap with resilience features
1055 ///
1056 /// # Returns
1057 ///
1058 /// Returns a configured `Client<TurboTransport>` instance.
1059 ///
1060 /// # Errors
1061 ///
1062 /// Returns an error if plugin initialization fails.
1063 ///
1064 /// # Examples
1065 ///
1066 /// ```rust,no_run
1067 /// use turbomcp_client::ClientBuilder;
1068 /// use turbomcp_transport::stdio::StdioTransport;
1069 /// use turbomcp_transport::resilience::{RetryConfig, CircuitBreakerConfig, HealthCheckConfig};
1070 /// use std::time::Duration;
1071 ///
1072 /// # async fn example() -> turbomcp_protocol::Result<()> {
1073 /// let client = ClientBuilder::new()
1074 /// .with_retry_config(RetryConfig {
1075 /// max_attempts: 5,
1076 /// base_delay: Duration::from_millis(200),
1077 /// ..Default::default()
1078 /// })
1079 /// .with_circuit_breaker_config(CircuitBreakerConfig {
1080 /// failure_threshold: 3,
1081 /// timeout: Duration::from_secs(30),
1082 /// ..Default::default()
1083 /// })
1084 /// .with_health_check_config(HealthCheckConfig {
1085 /// interval: Duration::from_secs(15),
1086 /// timeout: Duration::from_secs(5),
1087 /// ..Default::default()
1088 /// })
1089 /// .build_resilient(StdioTransport::new())
1090 /// .await?;
1091 /// # Ok(())
1092 /// # }
1093 /// ```
1094 pub async fn build_resilient<T: Transport + 'static>(
1095 self,
1096 transport: T,
1097 ) -> Result<Client<turbomcp_transport::resilience::TurboTransport>> {
1098 use turbomcp_transport::resilience::TurboTransport;
1099
1100 // Get configurations or use defaults
1101 let retry_config =
1102 self.retry_config
1103 .unwrap_or_else(|| turbomcp_transport::resilience::RetryConfig {
1104 max_attempts: self.connection_config.max_retries.max(1),
1105 base_delay: Duration::from_millis(self.connection_config.retry_delay_ms),
1106 ..Default::default()
1107 });
1108 let circuit_config = self.circuit_breaker_config.unwrap_or_default();
1109 let health_config = self.health_check_config.unwrap_or_else(|| {
1110 turbomcp_transport::resilience::HealthCheckConfig {
1111 timeout: Duration::from_millis(self.connection_config.timeout_ms),
1112 ..Default::default()
1113 }
1114 });
1115
1116 // Wrap transport in TurboTransport
1117 let robust_transport = TurboTransport::new(
1118 Box::new(transport),
1119 retry_config,
1120 circuit_config,
1121 health_config,
1122 );
1123
1124 // Create client with resilient transport
1125 let client = Client::with_capabilities_and_config(
1126 robust_transport,
1127 self.capabilities,
1128 protocol_transport_config(&self.connection_config),
1129 );
1130
1131 // Register handlers
1132 if let Some(handler) = self.elicitation_handler {
1133 client.set_elicitation_handler(handler);
1134 }
1135 if let Some(handler) = self.log_handler {
1136 client.set_log_handler(handler);
1137 }
1138 if let Some(handler) = self.resource_update_handler {
1139 client.set_resource_update_handler(handler);
1140 }
1141
1142 Ok(client)
1143 }
1144
1145 /// Build a client synchronously with basic configuration only
1146 ///
1147 /// This is a convenience method for simple use cases.
1148 ///
1149 /// # Arguments
1150 ///
1151 /// * `transport` - The transport to use for the client
1152 ///
1153 /// # Returns
1154 ///
1155 /// Returns a configured `Client` instance.
1156 ///
1157 /// # Examples
1158 ///
1159 /// ```rust,no_run
1160 /// use turbomcp_client::ClientBuilder;
1161 /// use turbomcp_transport::stdio::StdioTransport;
1162 ///
1163 /// let client = ClientBuilder::new()
1164 /// .with_tools(true)
1165 /// .build_sync(StdioTransport::new());
1166 /// ```
1167 pub fn build_sync<T: Transport + 'static>(self, transport: T) -> Client<T> {
1168 assert!(
1169 !resilience_requested(&self),
1170 "resilience settings require build_resilient(); build_sync() would otherwise ignore them"
1171 );
1172
1173 let client = Client::with_capabilities_and_config(
1174 transport,
1175 self.capabilities,
1176 protocol_transport_config(&self.connection_config),
1177 );
1178
1179 // Register synchronous handlers only
1180 if let Some(handler) = self.elicitation_handler {
1181 client.set_elicitation_handler(handler);
1182 }
1183 if let Some(handler) = self.log_handler {
1184 client.set_log_handler(handler);
1185 }
1186 if let Some(handler) = self.resource_update_handler {
1187 client.set_resource_update_handler(handler);
1188 }
1189
1190 client
1191 }
1192
1193 // ============================================================================
1194 // CONFIGURATION ACCESS
1195 // ============================================================================
1196
1197 /// Get the current capabilities configuration
1198 #[must_use]
1199 pub fn capabilities(&self) -> &ClientCapabilities {
1200 &self.capabilities
1201 }
1202
1203 /// Get the current connection configuration
1204 #[must_use]
1205 pub fn connection_config(&self) -> &ConnectionConfig {
1206 &self.connection_config
1207 }
1208
1209 /// Check if any handlers are registered
1210 #[must_use]
1211 pub fn has_handlers(&self) -> bool {
1212 self.elicitation_handler.is_some()
1213 || self.log_handler.is_some()
1214 || self.resource_update_handler.is_some()
1215 }
1216}
1217
1218// Re-export types for public API
1219pub use turbomcp_protocol::types::ServerCapabilities as PublicServerCapabilities;
1220
1221#[cfg(test)]
1222mod tests {
1223 use super::*;
1224 use std::future::Future;
1225 use std::pin::Pin;
1226 use turbomcp_transport::{
1227 TransportCapabilities, TransportConfig, TransportMessage, TransportMetrics,
1228 TransportResult, TransportState, TransportType,
1229 };
1230
1231 #[derive(Debug, Default)]
1232 struct NoopTransport {
1233 capabilities: TransportCapabilities,
1234 }
1235
1236 impl Transport for NoopTransport {
1237 fn transport_type(&self) -> TransportType {
1238 TransportType::Stdio
1239 }
1240
1241 fn capabilities(&self) -> &TransportCapabilities {
1242 &self.capabilities
1243 }
1244
1245 fn state(&self) -> Pin<Box<dyn Future<Output = TransportState> + Send + '_>> {
1246 Box::pin(async { TransportState::Disconnected })
1247 }
1248
1249 fn connect(&self) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1250 Box::pin(async { Ok(()) })
1251 }
1252
1253 fn disconnect(&self) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1254 Box::pin(async { Ok(()) })
1255 }
1256
1257 fn send(
1258 &self,
1259 _message: TransportMessage,
1260 ) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1261 Box::pin(async { Ok(()) })
1262 }
1263
1264 fn receive(
1265 &self,
1266 ) -> Pin<Box<dyn Future<Output = TransportResult<Option<TransportMessage>>> + Send + '_>>
1267 {
1268 Box::pin(async { Ok(None) })
1269 }
1270
1271 fn metrics(&self) -> Pin<Box<dyn Future<Output = TransportMetrics> + Send + '_>> {
1272 Box::pin(async { TransportMetrics::default() })
1273 }
1274
1275 fn configure(
1276 &self,
1277 _config: TransportConfig,
1278 ) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1279 Box::pin(async { Ok(()) })
1280 }
1281 }
1282
1283 #[tokio::test]
1284 async fn build_rejects_resilience_flags() {
1285 let result = ClientBuilder::new()
1286 .enable_resilience()
1287 .build(NoopTransport::default())
1288 .await;
1289
1290 assert!(result.is_err());
1291 let err = match result {
1292 Ok(_) => panic!("expected build() to reject resilience settings"),
1293 Err(err) => err,
1294 };
1295 assert!(err.to_string().contains("build_resilient"));
1296 }
1297}