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}