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::{
92//!     CreateMessageRequest, CreateMessageResult, Role, SamplingContent, StopReason,
93//! };
94//! use std::future::Future;
95//! use std::pin::Pin;
96//!
97//! #[derive(Debug)]
98//! struct MySamplingHandler {
99//!     // Your LLM client would go here
100//! }
101//!
102//! impl SamplingHandler for MySamplingHandler {
103//!     fn handle_create_message(
104//!         &self,
105//!         request_id: String,
106//!         request: CreateMessageRequest
107//!     ) -> Pin<Box<dyn Future<Output = Result<CreateMessageResult, Box<dyn std::error::Error + Send + Sync>>> + Send + '_>> {
108//!         Box::pin(async move {
109//!             // Forward to your LLM provider (OpenAI, Anthropic, etc.)
110//!             // Use request_id for correlation tracking
111//!             // Allows the server to request LLM sampling through the client
112//!
113//!             Ok(CreateMessageResult {
114//!                 role: Role::Assistant,
115//!                 content: SamplingContent::text("Response from LLM").into(),
116//!                 model: "gpt-4".to_string(),
117//!                 stop_reason: Some(StopReason::EndTurn.to_string()),
118//!                 meta: None,
119//!             })
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_protocol::Result<()> {
133//! # let mut client = Client::new(StdioTransport::new());
134//! match client.call_tool("my_tool", None, None).await {
135//!     Ok(result) => println!("Tool result: {:?}", result),
136//!     Err(e) => eprintln!("Tool call failed: {}", e),
137//! }
138//! # Ok(())
139//! # }
140//! ```
141
142/// TurboMCP Client version from Cargo.toml
143///
144/// This constant provides easy programmatic access to the current version.
145///
146/// # Example
147///
148/// ```rust
149/// println!("TurboMCP Client version: {}", turbomcp_client::VERSION);
150/// ```
151pub const VERSION: &str = env!("CARGO_PKG_VERSION");
152
153/// TurboMCP Client crate name
154pub const CRATE_NAME: &str = env!("CARGO_PKG_NAME");
155
156pub mod client;
157pub mod handlers;
158pub mod integration;
159pub mod prelude;
160pub mod sampling;
161
162// v3.0 Tower-native middleware
163pub mod middleware;
164
165// Re-export key types for convenience
166pub use client::operations::tools::CallToolResponse;
167pub use client::{ConnectionInfo, ConnectionState, ManagerConfig, ServerGroup, SessionManager};
168
169use std::sync::Arc;
170use std::time::Duration;
171
172// Re-export Transport trait for generic bounds in integrations
173pub use turbomcp_transport::Transport;
174
175// ============================================================================
176// TOP-LEVEL RE-EXPORTS FOR ERGONOMIC IMPORTS
177// ============================================================================
178
179// Result/Error types - re-export from protocol for consistency
180pub use turbomcp_protocol::{Error, Result};
181
182// Handler types (most commonly used)
183pub use handlers::{
184    // Cancellation (current MCP spec)
185    CancellationHandler,
186    CancelledNotification,
187    ElicitationAction,
188    // Elicitation
189    ElicitationHandler,
190    ElicitationRequest,
191    ElicitationResponse,
192    // Error handling
193    HandlerError,
194    HandlerResult,
195    // Logging (current MCP spec)
196    LogHandler,
197    LoggingNotification,
198    // Progress (current MCP spec)
199    ProgressHandler,
200    ProgressNotification,
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::{LlmServerInfo, SamplingHandler, 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    progress_handler: Option<Arc<dyn crate::handlers::ProgressHandler>>,
640    // Robustness configuration
641    enable_resilience: bool,
642    retry_config: Option<turbomcp_transport::resilience::RetryConfig>,
643    circuit_breaker_config: Option<turbomcp_transport::resilience::CircuitBreakerConfig>,
644    health_check_config: Option<turbomcp_transport::resilience::HealthCheckConfig>,
645}
646
647// Default implementation is now derived
648
649impl ClientBuilder {
650    /// Create a new client builder
651    ///
652    /// Returns a new builder with default configuration.
653    #[must_use]
654    pub fn new() -> Self {
655        Self::default()
656    }
657
658    // ============================================================================
659    // CAPABILITY CONFIGURATION
660    // ============================================================================
661
662    /// Enable or disable tool support
663    ///
664    /// # Arguments
665    ///
666    /// * `enabled` - Whether to enable tool support
667    #[must_use]
668    pub fn with_tools(mut self, enabled: bool) -> Self {
669        self.capabilities.tools = enabled;
670        self
671    }
672
673    /// Enable or disable prompt support
674    ///
675    /// # Arguments
676    ///
677    /// * `enabled` - Whether to enable prompt support
678    #[must_use]
679    pub fn with_prompts(mut self, enabled: bool) -> Self {
680        self.capabilities.prompts = enabled;
681        self
682    }
683
684    /// Enable or disable resource support
685    ///
686    /// # Arguments
687    ///
688    /// * `enabled` - Whether to enable resource support
689    #[must_use]
690    pub fn with_resources(mut self, enabled: bool) -> Self {
691        self.capabilities.resources = enabled;
692        self
693    }
694
695    /// Enable or disable sampling support
696    ///
697    /// # Arguments
698    ///
699    /// * `enabled` - Whether to enable sampling support
700    #[must_use]
701    pub fn with_sampling(mut self, enabled: bool) -> Self {
702        self.capabilities.sampling = enabled;
703        self
704    }
705
706    /// Set maximum concurrent request/notification handlers
707    ///
708    /// This limits how many server-initiated requests/notifications can be processed simultaneously.
709    /// Provides automatic backpressure when the limit is reached.
710    ///
711    /// # Arguments
712    ///
713    /// * `limit` - Maximum concurrent handlers (default: 100)
714    ///
715    /// # Tuning Guide
716    ///
717    /// - Low-resource clients: 50
718    /// - Standard clients: 100 (default)
719    /// - High-performance: 200-500
720    /// - Maximum recommended: 1000
721    ///
722    /// # Example
723    ///
724    /// ```rust,no_run
725    /// use turbomcp_client::ClientBuilder;
726    /// # use turbomcp_transport::StdioTransport;
727    ///
728    /// let builder = ClientBuilder::new()
729    ///     .with_max_concurrent_handlers(200);
730    /// ```
731    #[must_use]
732    pub fn with_max_concurrent_handlers(mut self, limit: usize) -> Self {
733        self.capabilities.max_concurrent_handlers = limit;
734        self
735    }
736
737    /// Configure all capabilities at once
738    ///
739    /// # Arguments
740    ///
741    /// * `capabilities` - The capabilities configuration
742    #[must_use]
743    pub fn with_capabilities(mut self, capabilities: ClientCapabilities) -> Self {
744        self.capabilities = capabilities;
745        self
746    }
747
748    // ============================================================================
749    // CONNECTION CONFIGURATION
750    // ============================================================================
751
752    /// Configure connection settings
753    ///
754    /// # Arguments
755    ///
756    /// * `config` - The connection configuration
757    #[must_use]
758    pub fn with_connection_config(mut self, config: ConnectionConfig) -> Self {
759        self.connection_config = config;
760        self
761    }
762
763    /// Set request timeout
764    ///
765    /// # Arguments
766    ///
767    /// * `timeout_ms` - Timeout in milliseconds
768    #[must_use]
769    pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
770        self.connection_config.timeout_ms = timeout_ms;
771        self
772    }
773
774    /// Set maximum retry attempts
775    ///
776    /// # Arguments
777    ///
778    /// * `max_retries` - Maximum number of retries
779    #[must_use]
780    pub fn with_max_retries(mut self, max_retries: u32) -> Self {
781        self.connection_config.max_retries = max_retries;
782        self
783    }
784
785    /// Set retry delay
786    ///
787    /// # Arguments
788    ///
789    /// * `delay_ms` - Retry delay in milliseconds
790    #[must_use]
791    pub fn with_retry_delay(mut self, delay_ms: u64) -> Self {
792        self.connection_config.retry_delay_ms = delay_ms;
793        self
794    }
795
796    /// Set keep-alive interval
797    ///
798    /// # Arguments
799    ///
800    /// * `interval_ms` - Keep-alive interval in milliseconds
801    #[must_use]
802    pub fn with_keepalive(mut self, interval_ms: u64) -> Self {
803        self.connection_config.keepalive_ms = interval_ms;
804        self
805    }
806
807    // ============================================================================
808    // ROBUSTNESS & RESILIENCE CONFIGURATION
809    // ============================================================================
810
811    /// Enable resilient transport with circuit breaker, retry, and health checking
812    ///
813    /// When enabled, the transport layer will automatically:
814    /// - Retry failed operations with exponential backoff
815    /// - Use circuit breaker pattern to prevent cascade failures
816    /// - Perform periodic health checks
817    /// - Deduplicate messages
818    ///
819    /// # Examples
820    ///
821    /// ```rust,no_run
822    /// use turbomcp_client::ClientBuilder;
823    /// use turbomcp_transport::stdio::StdioTransport;
824    ///
825    /// let client = ClientBuilder::new()
826    ///     .enable_resilience()
827    ///     .build(StdioTransport::new());
828    /// ```
829    #[must_use]
830    pub fn enable_resilience(mut self) -> Self {
831        self.enable_resilience = true;
832        self
833    }
834
835    /// Configure retry behavior for resilient transport
836    ///
837    /// # Arguments
838    ///
839    /// * `config` - Retry configuration
840    ///
841    /// # Examples
842    ///
843    /// ```rust,no_run
844    /// use turbomcp_client::ClientBuilder;
845    /// use turbomcp_transport::resilience::RetryConfig;
846    /// use turbomcp_transport::stdio::StdioTransport;
847    /// use std::time::Duration;
848    ///
849    /// let client = ClientBuilder::new()
850    ///     .enable_resilience()
851    ///     .with_retry_config(RetryConfig {
852    ///         max_attempts: 5,
853    ///         base_delay: Duration::from_millis(100),
854    ///         max_delay: Duration::from_secs(30),
855    ///         backoff_multiplier: 2.0,
856    ///         jitter_factor: 0.1,
857    ///         retry_on_connection_error: true,
858    ///         retry_on_timeout: true,
859    ///         custom_retry_conditions: Vec::new(),
860    ///     })
861    ///     .build(StdioTransport::new());
862    /// ```
863    #[must_use]
864    pub fn with_retry_config(
865        mut self,
866        config: turbomcp_transport::resilience::RetryConfig,
867    ) -> Self {
868        self.retry_config = Some(config);
869        self.enable_resilience = true; // Auto-enable resilience
870        self
871    }
872
873    /// Configure circuit breaker for resilient transport
874    ///
875    /// # Arguments
876    ///
877    /// * `config` - Circuit breaker configuration
878    ///
879    /// # Examples
880    ///
881    /// ```rust,no_run
882    /// use turbomcp_client::ClientBuilder;
883    /// use turbomcp_transport::resilience::CircuitBreakerConfig;
884    /// use turbomcp_transport::stdio::StdioTransport;
885    /// use std::time::Duration;
886    ///
887    /// let client = ClientBuilder::new()
888    ///     .enable_resilience()
889    ///     .with_circuit_breaker_config(CircuitBreakerConfig {
890    ///         failure_threshold: 5,
891    ///         success_threshold: 2,
892    ///         timeout: Duration::from_secs(60),
893    ///         rolling_window_size: 100,
894    ///         minimum_requests: 10,
895    ///     })
896    ///     .build(StdioTransport::new());
897    /// ```
898    #[must_use]
899    pub fn with_circuit_breaker_config(
900        mut self,
901        config: turbomcp_transport::resilience::CircuitBreakerConfig,
902    ) -> Self {
903        self.circuit_breaker_config = Some(config);
904        self.enable_resilience = true; // Auto-enable resilience
905        self
906    }
907
908    /// Configure health checking for resilient transport
909    ///
910    /// # Arguments
911    ///
912    /// * `config` - Health check configuration
913    ///
914    /// # Examples
915    ///
916    /// ```rust,no_run
917    /// use turbomcp_client::ClientBuilder;
918    /// use turbomcp_transport::resilience::HealthCheckConfig;
919    /// use turbomcp_transport::stdio::StdioTransport;
920    /// use std::time::Duration;
921    ///
922    /// let client = ClientBuilder::new()
923    ///     .enable_resilience()
924    ///     .with_health_check_config(HealthCheckConfig {
925    ///         interval: Duration::from_secs(30),
926    ///         timeout: Duration::from_secs(5),
927    ///         failure_threshold: 3,
928    ///         success_threshold: 1,
929    ///         custom_check: None,
930    ///     })
931    ///     .build(StdioTransport::new());
932    /// ```
933    #[must_use]
934    pub fn with_health_check_config(
935        mut self,
936        config: turbomcp_transport::resilience::HealthCheckConfig,
937    ) -> Self {
938        self.health_check_config = Some(config);
939        self.enable_resilience = true; // Auto-enable resilience
940        self
941    }
942
943    // ============================================================================
944    // HANDLER REGISTRATION
945    // ============================================================================
946
947    /// Register an elicitation handler for processing user input requests
948    ///
949    /// # Arguments
950    ///
951    /// * `handler` - The elicitation handler implementation
952    pub fn with_elicitation_handler(
953        mut self,
954        handler: Arc<dyn crate::handlers::ElicitationHandler>,
955    ) -> Self {
956        self.elicitation_handler = Some(handler);
957        self
958    }
959
960    /// Register a log handler for processing server log messages
961    ///
962    /// # Arguments
963    ///
964    /// * `handler` - The log handler implementation
965    pub fn with_log_handler(mut self, handler: Arc<dyn crate::handlers::LogHandler>) -> Self {
966        self.log_handler = Some(handler);
967        self
968    }
969
970    /// Register a resource update handler for processing resource change notifications
971    ///
972    /// # Arguments
973    ///
974    /// * `handler` - The resource update handler implementation
975    pub fn with_resource_update_handler(
976        mut self,
977        handler: Arc<dyn crate::handlers::ResourceUpdateHandler>,
978    ) -> Self {
979        self.resource_update_handler = Some(handler);
980        self
981    }
982
983    /// Register a progress handler for processing progress notifications
984    ///
985    /// # Arguments
986    ///
987    /// * `handler` - The progress handler implementation
988    pub fn with_progress_handler(
989        mut self,
990        handler: Arc<dyn crate::handlers::ProgressHandler>,
991    ) -> Self {
992        self.progress_handler = Some(handler);
993        self
994    }
995
996    // ============================================================================
997    // BUILD METHODS
998    // ============================================================================
999
1000    /// Build a client with the configured options
1001    ///
1002    /// Creates a new client instance with all the configured options. The client
1003    /// will be initialized with the registered plugins, handlers, and providers.
1004    ///
1005    /// # Arguments
1006    ///
1007    /// * `transport` - The transport to use for the client
1008    ///
1009    /// # Returns
1010    ///
1011    /// Returns a configured `Client` instance wrapped in a Result for async setup.
1012    ///
1013    /// # Examples
1014    ///
1015    /// ```rust,no_run
1016    /// use turbomcp_client::ClientBuilder;
1017    /// use turbomcp_transport::stdio::StdioTransport;
1018    ///
1019    /// # async fn example() -> turbomcp_protocol::Result<()> {
1020    /// let client = ClientBuilder::new()
1021    ///     .with_tools(true)
1022    ///     .with_prompts(true)
1023    ///     .build(StdioTransport::new())
1024    ///     .await?;
1025    /// # Ok(())
1026    /// # }
1027    /// ```
1028    pub async fn build<T: Transport + 'static>(self, transport: T) -> Result<Client<T>> {
1029        if resilience_requested(&self) {
1030            return Err(Error::configuration(
1031                "resilience settings require build_resilient(); build() would otherwise ignore them"
1032                    .to_string(),
1033            ));
1034        }
1035
1036        // Create base client with capabilities
1037        let client = Client::with_capabilities_and_config(
1038            transport,
1039            self.capabilities,
1040            protocol_transport_config(&self.connection_config),
1041        );
1042
1043        // Register handlers
1044        if let Some(handler) = self.elicitation_handler {
1045            client.set_elicitation_handler(handler);
1046        }
1047        if let Some(handler) = self.log_handler {
1048            client.set_log_handler(handler);
1049        }
1050        if let Some(handler) = self.resource_update_handler {
1051            client.set_resource_update_handler(handler);
1052        }
1053        if let Some(handler) = self.progress_handler {
1054            client.set_progress_handler(handler);
1055        }
1056
1057        Ok(client)
1058    }
1059
1060    /// Build a client with resilient transport (circuit breaker, retry, health checking)
1061    ///
1062    /// When resilience features are enabled via `enable_resilience()` or any resilience
1063    /// configuration method, this wraps the transport in a `TurboTransport` that provides:
1064    /// - Automatic retry with exponential backoff
1065    /// - Circuit breaker pattern for fast failure
1066    /// - Health checking and monitoring
1067    /// - Message deduplication
1068    ///
1069    /// # Arguments
1070    ///
1071    /// * `transport` - The base transport to wrap with resilience features
1072    ///
1073    /// # Returns
1074    ///
1075    /// Returns a configured `Client<TurboTransport>` instance.
1076    ///
1077    /// # Errors
1078    ///
1079    /// Returns an error if plugin initialization fails.
1080    ///
1081    /// # Examples
1082    ///
1083    /// ```rust,no_run
1084    /// use turbomcp_client::ClientBuilder;
1085    /// use turbomcp_transport::stdio::StdioTransport;
1086    /// use turbomcp_transport::resilience::{RetryConfig, CircuitBreakerConfig, HealthCheckConfig};
1087    /// use std::time::Duration;
1088    ///
1089    /// # async fn example() -> turbomcp_protocol::Result<()> {
1090    /// let client = ClientBuilder::new()
1091    ///     .with_retry_config(RetryConfig {
1092    ///         max_attempts: 5,
1093    ///         base_delay: Duration::from_millis(200),
1094    ///         ..Default::default()
1095    ///     })
1096    ///     .with_circuit_breaker_config(CircuitBreakerConfig {
1097    ///         failure_threshold: 3,
1098    ///         timeout: Duration::from_secs(30),
1099    ///         ..Default::default()
1100    ///     })
1101    ///     .with_health_check_config(HealthCheckConfig {
1102    ///         interval: Duration::from_secs(15),
1103    ///         timeout: Duration::from_secs(5),
1104    ///         ..Default::default()
1105    ///     })
1106    ///     .build_resilient(StdioTransport::new())
1107    ///     .await?;
1108    /// # Ok(())
1109    /// # }
1110    /// ```
1111    pub async fn build_resilient<T: Transport + 'static>(
1112        self,
1113        transport: T,
1114    ) -> Result<Client<turbomcp_transport::resilience::TurboTransport>> {
1115        use turbomcp_transport::resilience::TurboTransport;
1116
1117        // Get configurations or use defaults
1118        let retry_config =
1119            self.retry_config
1120                .unwrap_or_else(|| turbomcp_transport::resilience::RetryConfig {
1121                    max_attempts: self.connection_config.max_retries.max(1),
1122                    base_delay: Duration::from_millis(self.connection_config.retry_delay_ms),
1123                    ..Default::default()
1124                });
1125        let circuit_config = self.circuit_breaker_config.unwrap_or_default();
1126        let health_config = self.health_check_config.unwrap_or_else(|| {
1127            turbomcp_transport::resilience::HealthCheckConfig {
1128                timeout: Duration::from_millis(self.connection_config.timeout_ms),
1129                ..Default::default()
1130            }
1131        });
1132
1133        // Wrap transport in TurboTransport
1134        let robust_transport = TurboTransport::new(
1135            Box::new(transport),
1136            retry_config,
1137            circuit_config,
1138            health_config,
1139        );
1140
1141        // Create client with resilient transport
1142        let client = Client::with_capabilities_and_config(
1143            robust_transport,
1144            self.capabilities,
1145            protocol_transport_config(&self.connection_config),
1146        );
1147
1148        // Register handlers
1149        if let Some(handler) = self.elicitation_handler {
1150            client.set_elicitation_handler(handler);
1151        }
1152        if let Some(handler) = self.log_handler {
1153            client.set_log_handler(handler);
1154        }
1155        if let Some(handler) = self.resource_update_handler {
1156            client.set_resource_update_handler(handler);
1157        }
1158        if let Some(handler) = self.progress_handler {
1159            client.set_progress_handler(handler);
1160        }
1161
1162        Ok(client)
1163    }
1164
1165    /// Build a client synchronously with basic configuration only
1166    ///
1167    /// This is a convenience method for simple use cases.
1168    ///
1169    /// # Arguments
1170    ///
1171    /// * `transport` - The transport to use for the client
1172    ///
1173    /// # Returns
1174    ///
1175    /// Returns a configured `Client` instance.
1176    ///
1177    /// # Examples
1178    ///
1179    /// ```rust,no_run
1180    /// use turbomcp_client::ClientBuilder;
1181    /// use turbomcp_transport::stdio::StdioTransport;
1182    ///
1183    /// let client = ClientBuilder::new()
1184    ///     .with_tools(true)
1185    ///     .build_sync(StdioTransport::new());
1186    /// ```
1187    pub fn build_sync<T: Transport + 'static>(self, transport: T) -> Client<T> {
1188        assert!(
1189            !resilience_requested(&self),
1190            "resilience settings require build_resilient(); build_sync() would otherwise ignore them"
1191        );
1192
1193        let client = Client::with_capabilities_and_config(
1194            transport,
1195            self.capabilities,
1196            protocol_transport_config(&self.connection_config),
1197        );
1198
1199        // Register synchronous handlers only
1200        if let Some(handler) = self.elicitation_handler {
1201            client.set_elicitation_handler(handler);
1202        }
1203        if let Some(handler) = self.log_handler {
1204            client.set_log_handler(handler);
1205        }
1206        if let Some(handler) = self.resource_update_handler {
1207            client.set_resource_update_handler(handler);
1208        }
1209        if let Some(handler) = self.progress_handler {
1210            client.set_progress_handler(handler);
1211        }
1212
1213        client
1214    }
1215
1216    // ============================================================================
1217    // CONFIGURATION ACCESS
1218    // ============================================================================
1219
1220    /// Get the current capabilities configuration
1221    #[must_use]
1222    pub fn capabilities(&self) -> &ClientCapabilities {
1223        &self.capabilities
1224    }
1225
1226    /// Get the current connection configuration
1227    #[must_use]
1228    pub fn connection_config(&self) -> &ConnectionConfig {
1229        &self.connection_config
1230    }
1231
1232    /// Check if any handlers are registered
1233    #[must_use]
1234    pub fn has_handlers(&self) -> bool {
1235        self.elicitation_handler.is_some()
1236            || self.log_handler.is_some()
1237            || self.resource_update_handler.is_some()
1238            || self.progress_handler.is_some()
1239    }
1240}
1241
1242// Re-export types for public API
1243pub use turbomcp_protocol::types::ServerCapabilities as PublicServerCapabilities;
1244
1245#[cfg(test)]
1246mod tests {
1247    use super::*;
1248    use std::future::Future;
1249    use std::pin::Pin;
1250    use turbomcp_transport::{
1251        TransportCapabilities, TransportConfig, TransportMessage, TransportMetrics,
1252        TransportResult, TransportState, TransportType,
1253    };
1254
1255    #[derive(Debug, Default)]
1256    struct NoopTransport {
1257        capabilities: TransportCapabilities,
1258    }
1259
1260    impl Transport for NoopTransport {
1261        fn transport_type(&self) -> TransportType {
1262            TransportType::Stdio
1263        }
1264
1265        fn capabilities(&self) -> &TransportCapabilities {
1266            &self.capabilities
1267        }
1268
1269        fn state(&self) -> Pin<Box<dyn Future<Output = TransportState> + Send + '_>> {
1270            Box::pin(async { TransportState::Disconnected })
1271        }
1272
1273        fn connect(&self) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1274            Box::pin(async { Ok(()) })
1275        }
1276
1277        fn disconnect(&self) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1278            Box::pin(async { Ok(()) })
1279        }
1280
1281        fn send(
1282            &self,
1283            _message: TransportMessage,
1284        ) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1285            Box::pin(async { Ok(()) })
1286        }
1287
1288        fn receive(
1289            &self,
1290        ) -> Pin<Box<dyn Future<Output = TransportResult<Option<TransportMessage>>> + Send + '_>>
1291        {
1292            Box::pin(async { Ok(None) })
1293        }
1294
1295        fn metrics(&self) -> Pin<Box<dyn Future<Output = TransportMetrics> + Send + '_>> {
1296            Box::pin(async { TransportMetrics::default() })
1297        }
1298
1299        fn configure(
1300            &self,
1301            _config: TransportConfig,
1302        ) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1303            Box::pin(async { Ok(()) })
1304        }
1305    }
1306
1307    #[tokio::test]
1308    async fn build_rejects_resilience_flags() {
1309        let result = ClientBuilder::new()
1310            .enable_resilience()
1311            .build(NoopTransport::default())
1312            .await;
1313
1314        assert!(result.is_err());
1315        let err = match result {
1316            Ok(_) => panic!("expected build() to reject resilience settings"),
1317            Err(err) => err,
1318        };
1319        assert!(err.to_string().contains("build_resilient"));
1320    }
1321}