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}