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::{ConnectionInfo, ConnectionState, ManagerConfig, ServerGroup, SessionManager};
167
168use std::sync::Arc;
169use std::time::Duration;
170
171// Re-export Transport trait for generic bounds in integrations
172pub use turbomcp_transport::Transport;
173
174// ============================================================================
175// TOP-LEVEL RE-EXPORTS FOR ERGONOMIC IMPORTS
176// ============================================================================
177
178// Result/Error types - re-export from protocol for consistency
179pub use turbomcp_protocol::{Error, Result};
180
181// Handler types (most commonly used)
182pub use handlers::{
183 // Cancellation (current MCP spec)
184 CancellationHandler,
185 CancelledNotification,
186 ElicitationAction,
187 // Elicitation
188 ElicitationHandler,
189 ElicitationRequest,
190 ElicitationResponse,
191 // Error handling
192 HandlerError,
193 HandlerResult,
194 // Logging (current MCP spec)
195 LogHandler,
196 LoggingNotification,
197 // Progress (current MCP spec)
198 ProgressHandler,
199 ProgressNotification,
200 PromptListChangedHandler,
201 // List changed handlers (current MCP spec)
202 ResourceListChangedHandler,
203 // Resource updates (current MCP spec)
204 ResourceUpdateHandler,
205 ResourceUpdatedNotification,
206 // Roots
207 RootsHandler,
208 ToolListChangedHandler,
209};
210
211// Sampling types
212pub use sampling::{LlmServerInfo, SamplingHandler, UserInteractionHandler};
213
214// v3.0 Tower middleware
215pub use middleware::{
216 Cache, CacheConfig, CacheLayer, CacheService, McpRequest, McpResponse, Metrics, MetricsLayer,
217 MetricsService, MetricsSnapshot, TracingLayer, TracingService,
218};
219
220// Common protocol types
221pub use turbomcp_protocol::types::{
222 // Resource content types (for processing embedded resources)
223 BlobResourceContents,
224 // Tool result types (for LLM integrations like rig)
225 CallToolResult,
226 // Core types
227 ContentBlock,
228 EmbeddedResource,
229 LogLevel,
230 Prompt,
231 Resource,
232 ResourceContent,
233 ResourceContents,
234 Role,
235 TextResourceContents,
236 Tool,
237};
238
239// Transport re-exports (with feature gates)
240#[cfg(feature = "stdio")]
241pub use turbomcp_transport::stdio::StdioTransport;
242
243#[cfg(feature = "http")]
244pub use turbomcp_transport::streamable_http_client::{
245 RetryPolicy, StreamableHttpClientConfig, StreamableHttpClientTransport,
246};
247
248#[cfg(feature = "tcp")]
249pub use turbomcp_transport::tcp::{TcpTransport, TcpTransportBuilder};
250
251#[cfg(feature = "unix")]
252pub use turbomcp_transport::unix::{UnixTransport, UnixTransportBuilder};
253
254#[cfg(feature = "websocket")]
255pub use turbomcp_transport::websocket_bidirectional::{
256 WebSocketBidirectionalConfig, WebSocketBidirectionalTransport,
257};
258
259/// Client capability configuration
260///
261/// Defines the capabilities that this client supports when connecting to MCP servers.
262/// These capabilities are sent during the initialization handshake to negotiate
263/// which features will be available during the session.
264///
265/// # Examples
266///
267/// ```
268/// use turbomcp_client::ClientCapabilities;
269///
270/// let capabilities = ClientCapabilities {
271/// tools: true,
272/// prompts: true,
273/// resources: true,
274/// sampling: false,
275/// max_concurrent_handlers: 100,
276/// };
277/// ```
278#[derive(Debug, Clone)]
279pub struct ClientCapabilities {
280 /// Whether the client supports tool calling
281 pub tools: bool,
282
283 /// Whether the client supports prompts
284 pub prompts: bool,
285
286 /// Whether the client supports resources
287 pub resources: bool,
288
289 /// Whether the client supports sampling
290 pub sampling: bool,
291
292 /// Maximum concurrent request/notification handlers (default: 100)
293 ///
294 /// This limits how many server-initiated requests/notifications can be processed simultaneously.
295 /// Provides automatic backpressure when the limit is reached.
296 ///
297 /// **Tuning Guide:**
298 /// - Low-resource clients: 50
299 /// - Standard clients: 100 (default)
300 /// - High-performance: 200-500
301 /// - Maximum recommended: 1000
302 pub max_concurrent_handlers: usize,
303}
304
305impl Default for ClientCapabilities {
306 fn default() -> Self {
307 Self {
308 tools: false,
309 prompts: false,
310 resources: false,
311 sampling: false,
312 max_concurrent_handlers: 100,
313 }
314 }
315}
316
317impl ClientCapabilities {
318 /// All capabilities enabled (tools, prompts, resources, sampling)
319 ///
320 /// This is the most comprehensive configuration, enabling full MCP protocol support.
321 ///
322 /// # Example
323 ///
324 /// ```rust
325 /// use turbomcp_client::ClientCapabilities;
326 ///
327 /// let capabilities = ClientCapabilities::all();
328 /// assert!(capabilities.tools);
329 /// assert!(capabilities.prompts);
330 /// assert!(capabilities.resources);
331 /// assert!(capabilities.sampling);
332 /// ```
333 #[must_use]
334 pub fn all() -> Self {
335 Self {
336 tools: true,
337 prompts: true,
338 resources: true,
339 sampling: true,
340 max_concurrent_handlers: 100,
341 }
342 }
343
344 /// Core capabilities without sampling (tools, prompts, resources)
345 ///
346 /// This is the recommended default for most applications. It enables
347 /// all standard MCP features except server-initiated sampling requests.
348 ///
349 /// # Example
350 ///
351 /// ```rust
352 /// use turbomcp_client::ClientCapabilities;
353 ///
354 /// let capabilities = ClientCapabilities::core();
355 /// assert!(capabilities.tools);
356 /// assert!(capabilities.prompts);
357 /// assert!(capabilities.resources);
358 /// assert!(!capabilities.sampling);
359 /// ```
360 #[must_use]
361 pub fn core() -> Self {
362 Self {
363 tools: true,
364 prompts: true,
365 resources: true,
366 sampling: false,
367 max_concurrent_handlers: 100,
368 }
369 }
370
371 /// Minimal capabilities (tools only)
372 ///
373 /// Use this for simple tool-calling clients that don't need prompts,
374 /// resources, or sampling support.
375 ///
376 /// # Example
377 ///
378 /// ```rust
379 /// use turbomcp_client::ClientCapabilities;
380 ///
381 /// let capabilities = ClientCapabilities::minimal();
382 /// assert!(capabilities.tools);
383 /// assert!(!capabilities.prompts);
384 /// assert!(!capabilities.resources);
385 /// assert!(!capabilities.sampling);
386 /// ```
387 #[must_use]
388 pub fn minimal() -> Self {
389 Self {
390 tools: true,
391 prompts: false,
392 resources: false,
393 sampling: false,
394 max_concurrent_handlers: 100,
395 }
396 }
397
398 /// Only tools enabled
399 ///
400 /// Same as `minimal()`, provided for clarity.
401 #[must_use]
402 pub fn only_tools() -> Self {
403 Self::minimal()
404 }
405
406 /// Only resources enabled
407 ///
408 /// Use this for resource-focused clients that don't need tools or prompts.
409 ///
410 /// # Example
411 ///
412 /// ```rust
413 /// use turbomcp_client::ClientCapabilities;
414 ///
415 /// let capabilities = ClientCapabilities::only_resources();
416 /// assert!(!capabilities.tools);
417 /// assert!(!capabilities.prompts);
418 /// assert!(capabilities.resources);
419 /// ```
420 #[must_use]
421 pub fn only_resources() -> Self {
422 Self {
423 tools: false,
424 prompts: false,
425 resources: true,
426 sampling: false,
427 max_concurrent_handlers: 100,
428 }
429 }
430
431 /// Only prompts enabled
432 ///
433 /// Use this for prompt-focused clients that don't need tools or resources.
434 ///
435 /// # Example
436 ///
437 /// ```rust
438 /// use turbomcp_client::ClientCapabilities;
439 ///
440 /// let capabilities = ClientCapabilities::only_prompts();
441 /// assert!(!capabilities.tools);
442 /// assert!(capabilities.prompts);
443 /// assert!(!capabilities.resources);
444 /// ```
445 #[must_use]
446 pub fn only_prompts() -> Self {
447 Self {
448 tools: false,
449 prompts: true,
450 resources: false,
451 sampling: false,
452 max_concurrent_handlers: 100,
453 }
454 }
455
456 /// Only sampling enabled
457 ///
458 /// Use this for clients that exclusively handle server-initiated sampling requests.
459 #[must_use]
460 pub fn only_sampling() -> Self {
461 Self {
462 tools: false,
463 prompts: false,
464 resources: false,
465 sampling: true,
466 max_concurrent_handlers: 100,
467 }
468 }
469}
470
471/// JSON-RPC protocol handler for MCP communication
472// Note: ProtocolClient implementation moved to client/protocol.rs for better modularity
473/// MCP client for communicating with servers
474///
475/// The `Client` struct provides an ergonomic interface for interacting with MCP servers.
476/// It handles protocol complexity internally, exposing clean, type-safe methods.
477///
478/// # Type Parameters
479///
480/// * `T` - The transport implementation used for communication
481///
482/// # Examples
483///
484/// ```rust,no_run
485/// use turbomcp_client::Client;
486/// use turbomcp_transport::stdio::StdioTransport;
487///
488/// # async fn example() -> turbomcp_protocol::Result<()> {
489/// let transport = StdioTransport::new();
490/// let mut client = Client::new(transport);
491///
492/// // Initialize and start using the client
493/// client.initialize().await?;
494/// # Ok(())
495/// # }
496/// ```
497// Re-export Client from the core module
498pub use client::core::Client;
499
500// Thread-safe wrapper for sharing Client across async tasks
501//
502// This wrapper encapsulates the Arc/Mutex complexity and provides a clean API
503// for concurrent access to MCP client functionality. It addresses the limitations
504// identified in PR feedback where Client requires `&mut self` for all operations
505// but needs to be shared across multiple async tasks.
506//
507// # Design Rationale
508//
509// All Client methods require `&mut self` because:
510// - MCP connections maintain state (initialized flag, connection status)
511// - Request correlation tracking for JSON-RPC requires mutation
512// - Handler and plugin registries need mutable access
513//
514// Note: SharedClient has been removed in v2 - Client is now directly cloneable via Arc
515
516// ----------------------------------------------------------------------------
517// Re-exports
518// ----------------------------------------------------------------------------
519
520#[doc = "Result of client initialization"]
521#[doc = ""]
522#[doc = "Contains information about the server and the negotiated capabilities"]
523#[doc = "after a successful initialization handshake."]
524pub use client::config::InitializeResult;
525
526// ServerCapabilities is now imported from turbomcp_protocol::types
527
528/// Connection configuration for the client
529#[derive(Debug, Clone)]
530pub struct ConnectionConfig {
531 /// Request timeout in milliseconds
532 pub timeout_ms: u64,
533
534 /// Maximum number of retry attempts
535 pub max_retries: u32,
536
537 /// Retry delay in milliseconds
538 pub retry_delay_ms: u64,
539
540 /// Keep-alive interval in milliseconds
541 pub keepalive_ms: u64,
542}
543
544fn protocol_transport_config(
545 connection_config: &ConnectionConfig,
546) -> turbomcp_transport::TransportConfig {
547 let timeout = Duration::from_millis(connection_config.timeout_ms);
548
549 turbomcp_transport::TransportConfig {
550 connect_timeout: timeout,
551 keep_alive: Some(Duration::from_millis(connection_config.keepalive_ms)),
552 timeouts: turbomcp_transport::config::TimeoutConfig {
553 connect: timeout,
554 request: Some(timeout),
555 total: Some(timeout),
556 read: Some(timeout),
557 },
558 ..Default::default()
559 }
560}
561
562fn resilience_requested(builder: &ClientBuilder) -> bool {
563 builder.enable_resilience
564 || builder.retry_config.is_some()
565 || builder.circuit_breaker_config.is_some()
566 || builder.health_check_config.is_some()
567}
568
569impl Default for ConnectionConfig {
570 fn default() -> Self {
571 Self {
572 timeout_ms: 30_000, // 30 seconds
573 max_retries: 3, // 3 attempts
574 retry_delay_ms: 1_000, // 1 second
575 keepalive_ms: 60_000, // 60 seconds
576 }
577 }
578}
579
580/// Builder for configuring and creating MCP clients
581///
582/// Provides a fluent interface for configuring client options before creation.
583/// The enhanced builder pattern supports comprehensive configuration including:
584/// - Protocol capabilities
585/// - Plugin registration
586/// - Handler registration
587/// - Connection settings
588/// - Resilience configuration
589///
590/// # Examples
591///
592/// Basic usage:
593/// ```rust,no_run
594/// use turbomcp_client::ClientBuilder;
595/// use turbomcp_transport::stdio::StdioTransport;
596///
597/// # async fn example() -> turbomcp_protocol::Result<()> {
598/// let client = ClientBuilder::new()
599/// .with_tools(true)
600/// .with_prompts(true)
601/// .with_resources(false)
602/// .build(StdioTransport::new());
603/// # Ok(())
604/// # }
605/// ```
606///
607/// Advanced configuration with Tower middleware:
608/// ```rust,no_run
609/// use turbomcp_client::{ClientBuilder, ConnectionConfig};
610/// use turbomcp_client::middleware::MetricsLayer;
611/// use turbomcp_transport::stdio::StdioTransport;
612/// use tower::ServiceBuilder;
613///
614/// # async fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
615/// let client = ClientBuilder::new()
616/// .with_tools(true)
617/// .with_prompts(true)
618/// .with_resources(true)
619/// .with_sampling(true)
620/// .with_connection_config(ConnectionConfig {
621/// timeout_ms: 60_000,
622/// max_retries: 5,
623/// retry_delay_ms: 2_000,
624/// keepalive_ms: 30_000,
625/// })
626/// .build(StdioTransport::new())
627/// .await?;
628/// # Ok(())
629/// # }
630/// ```
631#[derive(Debug, Default)]
632pub struct ClientBuilder {
633 capabilities: ClientCapabilities,
634 connection_config: ConnectionConfig,
635 elicitation_handler: Option<Arc<dyn crate::handlers::ElicitationHandler>>,
636 log_handler: Option<Arc<dyn crate::handlers::LogHandler>>,
637 resource_update_handler: Option<Arc<dyn crate::handlers::ResourceUpdateHandler>>,
638 progress_handler: Option<Arc<dyn crate::handlers::ProgressHandler>>,
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 /// Register a progress handler for processing progress notifications
983 ///
984 /// # Arguments
985 ///
986 /// * `handler` - The progress handler implementation
987 pub fn with_progress_handler(
988 mut self,
989 handler: Arc<dyn crate::handlers::ProgressHandler>,
990 ) -> Self {
991 self.progress_handler = Some(handler);
992 self
993 }
994
995 // ============================================================================
996 // BUILD METHODS
997 // ============================================================================
998
999 /// Build a client with the configured options
1000 ///
1001 /// Creates a new client instance with all the configured options. The client
1002 /// will be initialized with the registered plugins, handlers, and providers.
1003 ///
1004 /// # Arguments
1005 ///
1006 /// * `transport` - The transport to use for the client
1007 ///
1008 /// # Returns
1009 ///
1010 /// Returns a configured `Client` instance wrapped in a Result for async setup.
1011 ///
1012 /// # Examples
1013 ///
1014 /// ```rust,no_run
1015 /// use turbomcp_client::ClientBuilder;
1016 /// use turbomcp_transport::stdio::StdioTransport;
1017 ///
1018 /// # async fn example() -> turbomcp_protocol::Result<()> {
1019 /// let client = ClientBuilder::new()
1020 /// .with_tools(true)
1021 /// .with_prompts(true)
1022 /// .build(StdioTransport::new())
1023 /// .await?;
1024 /// # Ok(())
1025 /// # }
1026 /// ```
1027 pub async fn build<T: Transport + 'static>(self, transport: T) -> Result<Client<T>> {
1028 if resilience_requested(&self) {
1029 return Err(Error::configuration(
1030 "resilience settings require build_resilient(); build() would otherwise ignore them"
1031 .to_string(),
1032 ));
1033 }
1034
1035 // Create base client with capabilities
1036 let client = Client::with_capabilities_and_config(
1037 transport,
1038 self.capabilities,
1039 protocol_transport_config(&self.connection_config),
1040 );
1041
1042 // Register handlers
1043 if let Some(handler) = self.elicitation_handler {
1044 client.set_elicitation_handler(handler);
1045 }
1046 if let Some(handler) = self.log_handler {
1047 client.set_log_handler(handler);
1048 }
1049 if let Some(handler) = self.resource_update_handler {
1050 client.set_resource_update_handler(handler);
1051 }
1052 if let Some(handler) = self.progress_handler {
1053 client.set_progress_handler(handler);
1054 }
1055
1056 Ok(client)
1057 }
1058
1059 /// Build a client with resilient transport (circuit breaker, retry, health checking)
1060 ///
1061 /// When resilience features are enabled via `enable_resilience()` or any resilience
1062 /// configuration method, this wraps the transport in a `TurboTransport` that provides:
1063 /// - Automatic retry with exponential backoff
1064 /// - Circuit breaker pattern for fast failure
1065 /// - Health checking and monitoring
1066 /// - Message deduplication
1067 ///
1068 /// # Arguments
1069 ///
1070 /// * `transport` - The base transport to wrap with resilience features
1071 ///
1072 /// # Returns
1073 ///
1074 /// Returns a configured `Client<TurboTransport>` instance.
1075 ///
1076 /// # Errors
1077 ///
1078 /// Returns an error if plugin initialization fails.
1079 ///
1080 /// # Examples
1081 ///
1082 /// ```rust,no_run
1083 /// use turbomcp_client::ClientBuilder;
1084 /// use turbomcp_transport::stdio::StdioTransport;
1085 /// use turbomcp_transport::resilience::{RetryConfig, CircuitBreakerConfig, HealthCheckConfig};
1086 /// use std::time::Duration;
1087 ///
1088 /// # async fn example() -> turbomcp_protocol::Result<()> {
1089 /// let client = ClientBuilder::new()
1090 /// .with_retry_config(RetryConfig {
1091 /// max_attempts: 5,
1092 /// base_delay: Duration::from_millis(200),
1093 /// ..Default::default()
1094 /// })
1095 /// .with_circuit_breaker_config(CircuitBreakerConfig {
1096 /// failure_threshold: 3,
1097 /// timeout: Duration::from_secs(30),
1098 /// ..Default::default()
1099 /// })
1100 /// .with_health_check_config(HealthCheckConfig {
1101 /// interval: Duration::from_secs(15),
1102 /// timeout: Duration::from_secs(5),
1103 /// ..Default::default()
1104 /// })
1105 /// .build_resilient(StdioTransport::new())
1106 /// .await?;
1107 /// # Ok(())
1108 /// # }
1109 /// ```
1110 pub async fn build_resilient<T: Transport + 'static>(
1111 self,
1112 transport: T,
1113 ) -> Result<Client<turbomcp_transport::resilience::TurboTransport>> {
1114 use turbomcp_transport::resilience::TurboTransport;
1115
1116 // Get configurations or use defaults
1117 let retry_config =
1118 self.retry_config
1119 .unwrap_or_else(|| turbomcp_transport::resilience::RetryConfig {
1120 max_attempts: self.connection_config.max_retries.max(1),
1121 base_delay: Duration::from_millis(self.connection_config.retry_delay_ms),
1122 ..Default::default()
1123 });
1124 let circuit_config = self.circuit_breaker_config.unwrap_or_default();
1125 let health_config = self.health_check_config.unwrap_or_else(|| {
1126 turbomcp_transport::resilience::HealthCheckConfig {
1127 timeout: Duration::from_millis(self.connection_config.timeout_ms),
1128 ..Default::default()
1129 }
1130 });
1131
1132 // Wrap transport in TurboTransport
1133 let robust_transport = TurboTransport::new(
1134 Box::new(transport),
1135 retry_config,
1136 circuit_config,
1137 health_config,
1138 );
1139
1140 // Create client with resilient transport
1141 let client = Client::with_capabilities_and_config(
1142 robust_transport,
1143 self.capabilities,
1144 protocol_transport_config(&self.connection_config),
1145 );
1146
1147 // Register handlers
1148 if let Some(handler) = self.elicitation_handler {
1149 client.set_elicitation_handler(handler);
1150 }
1151 if let Some(handler) = self.log_handler {
1152 client.set_log_handler(handler);
1153 }
1154 if let Some(handler) = self.resource_update_handler {
1155 client.set_resource_update_handler(handler);
1156 }
1157 if let Some(handler) = self.progress_handler {
1158 client.set_progress_handler(handler);
1159 }
1160
1161 Ok(client)
1162 }
1163
1164 /// Build a client synchronously with basic configuration only
1165 ///
1166 /// This is a convenience method for simple use cases.
1167 ///
1168 /// # Arguments
1169 ///
1170 /// * `transport` - The transport to use for the client
1171 ///
1172 /// # Returns
1173 ///
1174 /// Returns a configured `Client` instance.
1175 ///
1176 /// # Examples
1177 ///
1178 /// ```rust,no_run
1179 /// use turbomcp_client::ClientBuilder;
1180 /// use turbomcp_transport::stdio::StdioTransport;
1181 ///
1182 /// let client = ClientBuilder::new()
1183 /// .with_tools(true)
1184 /// .build_sync(StdioTransport::new());
1185 /// ```
1186 pub fn build_sync<T: Transport + 'static>(self, transport: T) -> Client<T> {
1187 assert!(
1188 !resilience_requested(&self),
1189 "resilience settings require build_resilient(); build_sync() would otherwise ignore them"
1190 );
1191
1192 let client = Client::with_capabilities_and_config(
1193 transport,
1194 self.capabilities,
1195 protocol_transport_config(&self.connection_config),
1196 );
1197
1198 // Register synchronous handlers only
1199 if let Some(handler) = self.elicitation_handler {
1200 client.set_elicitation_handler(handler);
1201 }
1202 if let Some(handler) = self.log_handler {
1203 client.set_log_handler(handler);
1204 }
1205 if let Some(handler) = self.resource_update_handler {
1206 client.set_resource_update_handler(handler);
1207 }
1208 if let Some(handler) = self.progress_handler {
1209 client.set_progress_handler(handler);
1210 }
1211
1212 client
1213 }
1214
1215 // ============================================================================
1216 // CONFIGURATION ACCESS
1217 // ============================================================================
1218
1219 /// Get the current capabilities configuration
1220 #[must_use]
1221 pub fn capabilities(&self) -> &ClientCapabilities {
1222 &self.capabilities
1223 }
1224
1225 /// Get the current connection configuration
1226 #[must_use]
1227 pub fn connection_config(&self) -> &ConnectionConfig {
1228 &self.connection_config
1229 }
1230
1231 /// Check if any handlers are registered
1232 #[must_use]
1233 pub fn has_handlers(&self) -> bool {
1234 self.elicitation_handler.is_some()
1235 || self.log_handler.is_some()
1236 || self.resource_update_handler.is_some()
1237 || self.progress_handler.is_some()
1238 }
1239}
1240
1241// Re-export types for public API
1242pub use turbomcp_protocol::types::ServerCapabilities as PublicServerCapabilities;
1243
1244#[cfg(test)]
1245mod tests {
1246 use super::*;
1247 use std::future::Future;
1248 use std::pin::Pin;
1249 use turbomcp_transport::{
1250 TransportCapabilities, TransportConfig, TransportMessage, TransportMetrics,
1251 TransportResult, TransportState, TransportType,
1252 };
1253
1254 #[derive(Debug, Default)]
1255 struct NoopTransport {
1256 capabilities: TransportCapabilities,
1257 }
1258
1259 impl Transport for NoopTransport {
1260 fn transport_type(&self) -> TransportType {
1261 TransportType::Stdio
1262 }
1263
1264 fn capabilities(&self) -> &TransportCapabilities {
1265 &self.capabilities
1266 }
1267
1268 fn state(&self) -> Pin<Box<dyn Future<Output = TransportState> + Send + '_>> {
1269 Box::pin(async { TransportState::Disconnected })
1270 }
1271
1272 fn connect(&self) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1273 Box::pin(async { Ok(()) })
1274 }
1275
1276 fn disconnect(&self) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1277 Box::pin(async { Ok(()) })
1278 }
1279
1280 fn send(
1281 &self,
1282 _message: TransportMessage,
1283 ) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1284 Box::pin(async { Ok(()) })
1285 }
1286
1287 fn receive(
1288 &self,
1289 ) -> Pin<Box<dyn Future<Output = TransportResult<Option<TransportMessage>>> + Send + '_>>
1290 {
1291 Box::pin(async { Ok(None) })
1292 }
1293
1294 fn metrics(&self) -> Pin<Box<dyn Future<Output = TransportMetrics> + Send + '_>> {
1295 Box::pin(async { TransportMetrics::default() })
1296 }
1297
1298 fn configure(
1299 &self,
1300 _config: TransportConfig,
1301 ) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
1302 Box::pin(async { Ok(()) })
1303 }
1304 }
1305
1306 #[tokio::test]
1307 async fn build_rejects_resilience_flags() {
1308 let result = ClientBuilder::new()
1309 .enable_resilience()
1310 .build(NoopTransport::default())
1311 .await;
1312
1313 assert!(result.is_err());
1314 let err = match result {
1315 Ok(_) => panic!("expected build() to reject resilience settings"),
1316 Err(err) => err,
1317 };
1318 assert!(err.to_string().contains("build_resilient"));
1319 }
1320}