Skip to main content

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