1use serde_json::Value;
22use std::collections::HashMap;
23use std::sync::Arc;
24use tokio::sync::{Mutex, RwLock};
25
26use crate::client::request_handler::{ClientRequestHandler, DefaultClientRequestHandler};
27use crate::core::error::{McpError, McpResult};
28use crate::protocol::tasks::{
29 has_tasks_extension, CancelTaskParams, CreateTaskResult, GetTaskParams, GetTaskResult, Task,
30 TaskAcknowledgement, TaskStatus, UpdateTaskParams, TASKS_EXTENSION_ID,
31};
32use crate::protocol::{messages::*, methods, types::*, validation::*, version::*};
33use crate::transport::traits::{ClientSubscription, Transport};
34
35#[derive(Debug, Clone)]
37pub struct ClientConfig {
38 pub request_timeout_ms: u64,
40 pub max_retries: u32,
42 pub retry_delay_ms: u64,
44 pub validate_requests: bool,
46 pub validate_responses: bool,
48 pub protocol_mode: ProtocolMode,
50 pub max_mrtr_rounds: u8,
52}
53
54impl Default for ClientConfig {
55 fn default() -> Self {
56 Self {
57 request_timeout_ms: 30000,
58 max_retries: 3,
59 retry_delay_ms: 1000,
60 validate_requests: true,
61 validate_responses: true,
62 protocol_mode: ProtocolMode::Auto,
63 max_mrtr_rounds: 10,
64 }
65 }
66}
67
68pub struct McpClient {
70 info: ClientInfo,
72 capabilities: ClientCapabilities,
74 config: ClientConfig,
76 transport: Arc<Mutex<Option<Box<dyn Transport>>>>,
78 server_capabilities: Arc<RwLock<Option<ServerCapabilities>>>,
80 server_info: Arc<RwLock<Option<ServerInfo>>>,
82 request_counter: Arc<Mutex<u64>>,
84 connected: Arc<RwLock<bool>>,
86 request_handler: Arc<dyn ClientRequestHandler>,
88 negotiated_protocol: Arc<RwLock<Option<NegotiatedProtocol>>>,
90}
91
92impl McpClient {
93 pub(crate) fn from_parts(
95 info: ClientInfo,
96 capabilities: ClientCapabilities,
97 config: ClientConfig,
98 ) -> Self {
99 Self {
100 info,
101 capabilities,
102 config,
103 transport: Arc::new(Mutex::new(None)),
104 server_capabilities: Arc::new(RwLock::new(None)),
105 server_info: Arc::new(RwLock::new(None)),
106 request_counter: Arc::new(Mutex::new(0)),
107 connected: Arc::new(RwLock::new(false)),
108 request_handler: Arc::new(DefaultClientRequestHandler),
109 negotiated_protocol: Arc::new(RwLock::new(None)),
110 }
111 }
112
113 pub fn builder() -> crate::client::enhanced_builder::McpClientBuilder {
115 crate::client::enhanced_builder::McpClientBuilder::new()
116 }
117
118 pub fn new(name: String, version: String) -> Self {
120 let info = ClientInfo::new(name, version);
121 Self::with_client_info(info)
122 }
123
124 pub fn with_client_info(info: ClientInfo) -> Self {
126 Self {
127 info,
128 capabilities: ClientCapabilities::default(),
129 config: ClientConfig::default(),
130 transport: Arc::new(Mutex::new(None)),
131 server_capabilities: Arc::new(RwLock::new(None)),
132 server_info: Arc::new(RwLock::new(None)),
133 request_counter: Arc::new(Mutex::new(0)),
134 connected: Arc::new(RwLock::new(false)),
135 request_handler: Arc::new(DefaultClientRequestHandler),
136 negotiated_protocol: Arc::new(RwLock::new(None)),
137 }
138 }
139
140 pub fn tools(&self) -> crate::client::fluent_tools::ToolsBuilder<'_> {
146 crate::client::fluent_tools::ToolsBuilder::new(self)
147 }
148
149 pub fn resources(&self) -> crate::client::fluent_interfaces::ResourcesBuilder<'_> {
151 crate::client::fluent_interfaces::ResourcesBuilder::new(self)
152 }
153
154 pub fn prompts(&self) -> crate::client::fluent_interfaces::PromptsBuilder<'_> {
156 crate::client::fluent_interfaces::PromptsBuilder::new(self)
157 }
158
159 #[deprecated(
168 since = "0.2.0",
169 note = "Use fluent interface: client.tools().call(name).args(args).execute().await"
170 )]
171 pub async fn call_tool_simple(
172 &self,
173 name: &str,
174 arguments: serde_json::Value,
175 ) -> McpResult<CallToolResult> {
176 let args_map = if let Some(obj) = arguments.as_object() {
177 obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
178 } else {
179 HashMap::new()
180 };
181
182 self.call_tool(name.to_string(), Some(args_map)).await
183 }
184
185 pub fn with_config(name: String, version: String, config: ClientConfig) -> Self {
187 let mut client = Self::new(name, version);
188 client.config = config;
189 client
190 }
191
192 pub fn set_capabilities(&mut self, capabilities: ClientCapabilities) {
194 self.capabilities = capabilities;
195 }
196
197 pub fn enable_tasks_extension(&mut self) {
199 self.capabilities
200 .extensions
201 .get_or_insert_with(HashMap::new)
202 .insert(TASKS_EXTENSION_ID.to_string(), serde_json::json!({}));
203 }
204
205 pub fn set_request_handler<H>(&mut self, handler: H)
224 where
225 H: ClientRequestHandler + 'static,
226 {
227 self.request_handler = Arc::new(handler);
228 }
229
230 pub async fn handle_server_request(
242 &self,
243 request: JsonRpcRequest,
244 ) -> McpResult<JsonRpcResponse> {
245 let result =
246 match request.method.as_str() {
247 methods::SAMPLING_CREATE_MESSAGE => {
248 let params: CreateMessageParams =
249 serde_json::from_value(request.params.ok_or_else(|| {
250 McpError::InvalidParams("Missing params".to_string())
251 })?)?;
252 let result = self.request_handler.handle_create_message(params).await?;
253 serde_json::to_value(result)?
254 }
255 methods::ROOTS_LIST => {
256 let params: ListRootsParams = request
257 .params
258 .map(serde_json::from_value)
259 .transpose()?
260 .unwrap_or(ListRootsParams { meta: None });
261 let result = self.request_handler.handle_list_roots(params).await?;
262 serde_json::to_value(result)?
263 }
264 methods::ELICITATION_CREATE => {
265 let params: ElicitParams =
266 serde_json::from_value(request.params.ok_or_else(|| {
267 McpError::InvalidParams("Missing params".to_string())
268 })?)?;
269 let result = self.request_handler.handle_elicit(params).await?;
270 serde_json::to_value(result)?
271 }
272 methods::PING => {
273 let params: PingParams = request
274 .params
275 .map(serde_json::from_value)
276 .transpose()?
277 .unwrap_or(PingParams { meta: None });
278 let result = self.request_handler.handle_ping(params).await?;
279 serde_json::to_value(result)?
280 }
281 _ => {
282 return Err(McpError::MethodNotFound(format!(
283 "Unknown method: {}",
284 request.method
285 )));
286 }
287 };
288
289 Ok(JsonRpcResponse::success(request.id, result)?)
290 }
291
292 pub fn info(&self) -> &ClientInfo {
294 &self.info
295 }
296
297 pub fn capabilities(&self) -> &ClientCapabilities {
299 &self.capabilities
300 }
301
302 pub fn config(&self) -> &ClientConfig {
304 &self.config
305 }
306
307 pub fn set_protocol_mode(&mut self, mode: ProtocolMode) {
309 self.config.protocol_mode = mode;
310 }
311
312 pub async fn negotiated_protocol(&self) -> Option<NegotiatedProtocol> {
314 self.negotiated_protocol.read().await.clone()
315 }
316
317 pub async fn server_capabilities(&self) -> Option<ServerCapabilities> {
319 let capabilities = self.server_capabilities.read().await;
320 capabilities.clone()
321 }
322
323 pub async fn server_info(&self) -> Option<ServerInfo> {
325 let info = self.server_info.read().await;
326 info.clone()
327 }
328
329 pub async fn is_connected(&self) -> bool {
331 let connected = self.connected.read().await;
332 *connected
333 }
334
335 pub async fn connect<T>(&mut self, transport: T) -> McpResult<ConnectResult>
341 where
342 T: Transport + 'static,
343 {
344 {
346 let mut transport_guard = self.transport.lock().await;
347 *transport_guard = Some(Box::new(transport));
348 }
349
350 let connection = match self.config.protocol_mode {
351 ProtocolMode::ModernOnly => self.discover_modern().await?,
352 ProtocolMode::LegacyOnly => self.initialize_legacy().await?,
353 ProtocolMode::Auto => match self.discover_modern().await {
354 Ok(result) => result,
355 Err(error) if is_method_not_found(&error) => {
356 tracing::info!("server/discover unavailable; using MCP 2025-11-25");
357 self.initialize_legacy().await?
358 }
359 Err(error) => return Err(error),
360 },
361 };
362
363 {
365 let mut connected = self.connected.write().await;
366 *connected = true;
367 }
368
369 Ok(connection)
370 }
371
372 pub async fn disconnect(&self) -> McpResult<()> {
374 {
376 let mut transport_guard = self.transport.lock().await;
377 if let Some(transport) = transport_guard.as_mut() {
378 transport.close().await?;
379 }
380 *transport_guard = None;
381 }
382
383 {
385 let mut server_capabilities = self.server_capabilities.write().await;
386 *server_capabilities = None;
387 }
388 {
389 let mut server_info = self.server_info.write().await;
390 *server_info = None;
391 }
392
393 {
395 let mut connected = self.connected.write().await;
396 *connected = false;
397 }
398 *self.negotiated_protocol.write().await = None;
399
400 Ok(())
401 }
402
403 async fn discover_modern(&self) -> McpResult<ConnectResult> {
405 let params = DiscoverParams {
406 meta: RequestMetaObject::modern(self.info.clone(), self.capabilities.clone()),
407 };
408 let request = JsonRpcRequest::new(
409 Value::from(self.next_request_id().await),
410 methods::SERVER_DISCOVER.to_string(),
411 Some(params.clone()),
412 )?;
413 let response = match self.send_request(request).await {
414 Err(McpError::UnsupportedProtocolVersion { supported, .. })
415 if supported
416 .iter()
417 .any(|version| version == MODERN_PROTOCOL_VERSION) =>
418 {
419 tracing::info!(
420 protocol.version = MODERN_PROTOCOL_VERSION,
421 "retrying server/discover with a mutually supported version"
422 );
423 let retry = JsonRpcRequest::new(
424 Value::from(self.next_request_id().await),
425 methods::SERVER_DISCOVER.to_string(),
426 Some(params),
427 )?;
428 self.send_request(retry).await?
429 }
430 result => result?,
431 };
432 let result: DiscoverResult = serde_json::from_value(
433 response
434 .result
435 .ok_or_else(|| McpError::Protocol("Missing discover result".to_string()))?,
436 )?;
437 if !result
438 .supported_versions
439 .iter()
440 .any(|version| version == MODERN_PROTOCOL_VERSION)
441 {
442 return Err(McpError::UnsupportedProtocolVersion {
443 requested: MODERN_PROTOCOL_VERSION.to_string(),
444 supported: result.supported_versions,
445 });
446 }
447
448 let protocol = NegotiatedProtocol::modern();
449 let server_info = result.server_info();
450 *self.server_capabilities.write().await = Some(result.capabilities.clone());
451 *self.server_info.write().await = server_info.clone();
452 *self.negotiated_protocol.write().await = Some(protocol.clone());
453
454 Ok(ConnectResult {
455 protocol,
456 capabilities: result.capabilities,
457 server_info,
458 instructions: result.instructions,
459 })
460 }
461
462 async fn initialize_legacy(&self) -> McpResult<ConnectResult> {
464 let params = InitializeParams::new(
465 LEGACY_PROTOCOL_VERSION.to_string(),
466 self.capabilities.clone(),
467 self.info.clone(),
468 );
469
470 let request = JsonRpcRequest::new(
471 Value::from(self.next_request_id().await),
472 methods::INITIALIZE.to_string(),
473 Some(params),
474 )?;
475
476 let response = self.send_request(request).await?;
477
478 let result: InitializeResult = serde_json::from_value(
482 response
483 .result
484 .ok_or_else(|| McpError::Protocol("Missing initialize result".to_string()))?,
485 )?;
486
487 {
489 let mut server_capabilities = self.server_capabilities.write().await;
490 *server_capabilities = Some(result.capabilities.clone());
491 }
492 {
493 let mut server_info = self.server_info.write().await;
494 *server_info = Some(result.server_info.clone());
495 }
496
497 let protocol = NegotiatedProtocol::legacy();
498 *self.negotiated_protocol.write().await = Some(protocol.clone());
499 Ok(ConnectResult {
500 protocol,
501 capabilities: result.capabilities,
502 server_info: Some(result.server_info),
503 instructions: result.instructions,
504 })
505 }
506
507 #[cfg(feature = "stdio")]
531 pub async fn connect_with_stdio(
532 &mut self,
533 command: &str,
534 args: Vec<&str>,
535 ) -> McpResult<ConnectResult> {
536 use crate::transport::stdio::StdioClientTransport;
537
538 if self.config.protocol_mode != ProtocolMode::Auto {
539 let transport = StdioClientTransport::new(command, args).await?;
540 return self.connect(transport).await;
541 }
542
543 let probe = StdioClientTransport::new(command, args.clone()).await?;
546 *self.transport.lock().await = Some(Box::new(probe));
547 match self.discover_modern().await {
548 Ok(connection) => {
549 *self.connected.write().await = true;
550 Ok(connection)
551 }
552 Err(error) if is_method_not_found(&error) => {
553 if let Some(transport) = self.transport.lock().await.as_mut() {
554 let _ = transport.close().await;
555 }
556 let legacy = StdioClientTransport::new(command, args).await?;
557 *self.transport.lock().await = Some(Box::new(legacy));
558 let connection = self.initialize_legacy().await?;
559 *self.connected.write().await = true;
560 Ok(connection)
561 }
562 Err(error) => {
563 if let Some(transport) = self.transport.lock().await.as_mut() {
564 let _ = transport.close().await;
565 }
566 *self.transport.lock().await = None;
567 Err(error)
568 }
569 }
570 }
571
572 #[cfg(feature = "http")]
596 pub async fn connect_with_http(
597 &mut self,
598 server_url: &str,
599 sse_url: Option<&str>,
600 ) -> McpResult<ConnectResult> {
601 use crate::transport::http::HttpClientTransport;
602
603 let transport = HttpClientTransport::new(server_url, sse_url).await?;
604 self.connect(transport).await
605 }
606
607 #[cfg(feature = "stdio")]
628 pub async fn connect_with_stdio_simple(&mut self, command: &str) -> McpResult<ConnectResult> {
629 self.connect_with_stdio(command, vec![]).await
630 }
631
632 #[cfg(feature = "websocket")]
655 pub async fn connect_with_websocket(&mut self, server_url: &str) -> McpResult<ConnectResult> {
656 use crate::transport::websocket::WebSocketClientTransport;
657
658 let transport = WebSocketClientTransport::new(server_url).await?;
659 self.connect(transport).await
660 }
661
662 #[cfg(feature = "stdio")]
690 pub async fn run_with_stdio<F, Fut>(
691 &mut self,
692 command: &str,
693 args: Vec<&str>,
694 session_handler: F,
695 ) -> McpResult<()>
696 where
697 F: FnOnce(&Self) -> Fut,
698 Fut: std::future::Future<Output = McpResult<()>>,
699 {
700 let init_result = self.connect_with_stdio(command, args).await?;
702 if let Some(server_info) = &init_result.server_info {
703 tracing::info!(
704 "Connected to server: {} v{} using {}",
705 server_info.name,
706 server_info.version,
707 init_result.protocol.version
708 );
709 } else {
710 tracing::info!("Connected using {}", init_result.protocol.version);
711 }
712
713 let (tx, mut rx) = tokio::sync::mpsc::channel::<()>(1);
715 let tx_clone = tx.clone();
716
717 tokio::spawn(async move {
718 tokio::signal::ctrl_c().await.ok();
719 let _ = tx_clone.send(()).await;
720 });
721
722 tokio::select! {
724 result = session_handler(self) => {
725 match result {
726 Ok(_) => tracing::info!("Session completed successfully"),
727 Err(e) => tracing::error!("Session error: {}", e),
728 }
729 }
730 _ = rx.recv() => {
731 tracing::info!("Shutdown signal received, disconnecting...");
732 }
733 }
734
735 self.disconnect().await?;
737 tracing::info!("Client disconnected");
738
739 Ok(())
740 }
741
742 #[cfg(feature = "chunked-encoding")]
747 fn ensure_legacy_prism_streaming(&self) -> McpResult<()> {
748 if self.config.protocol_mode != ProtocolMode::LegacyOnly {
749 return Err(McpError::Transport(
750 "Prism chunked/compressed endpoint helpers are legacy-only; use connect_with_http for standards-track MCP or set ProtocolMode::LegacyOnly explicitly"
751 .to_string(),
752 ));
753 }
754 Ok(())
755 }
756
757 #[cfg(feature = "chunked-encoding")]
796 pub async fn connect_with_chunked_encoding(
797 &mut self,
798 server_url: &str,
799 config: crate::transport::StreamingConfig,
800 ) -> McpResult<ConnectResult> {
801 use crate::transport::streaming_http::StreamingHttpClientTransport;
802
803 self.ensure_legacy_prism_streaming()?;
804 let transport = StreamingHttpClientTransport::with_config(server_url, config).await?;
805 self.connect(transport).await
806 }
807
808 #[cfg(feature = "chunked-encoding")]
828 pub async fn connect_with_chunked_encoding_default(
829 &mut self,
830 server_url: &str,
831 ) -> McpResult<ConnectResult> {
832 use crate::transport::streaming_http::StreamingHttpClientTransport;
833
834 self.ensure_legacy_prism_streaming()?;
835 let transport = StreamingHttpClientTransport::new(server_url).await?;
836 self.connect(transport).await
837 }
838
839 #[cfg(feature = "chunked-encoding")]
859 pub async fn connect_with_chunked_encoding_memory_improved(
860 &mut self,
861 server_url: &str,
862 ) -> McpResult<ConnectResult> {
863 use crate::transport::streaming_http::StreamingHttpClientTransport;
864 use crate::transport::StreamingConfig;
865
866 self.ensure_legacy_prism_streaming()?;
867 let config = StreamingConfig::memory_improved();
868 let transport = StreamingHttpClientTransport::with_config(server_url, config).await?;
869 self.connect(transport).await
870 }
871
872 #[cfg(feature = "chunked-encoding")]
892 pub async fn connect_with_chunked_encoding_performance_improved(
893 &mut self,
894 server_url: &str,
895 ) -> McpResult<ConnectResult> {
896 use crate::transport::streaming_http::StreamingHttpClientTransport;
897 use crate::transport::StreamingConfig;
898
899 self.ensure_legacy_prism_streaming()?;
900 let config = StreamingConfig::performance_improved();
901 let transport = StreamingHttpClientTransport::with_config(server_url, config).await?;
902 self.connect(transport).await
903 }
904
905 #[allow(unused_variables)] pub async fn connect_with_recommended_transport(
935 &mut self,
936 use_case: TransportUseCase,
937 server_url: &str,
938 ) -> McpResult<ConnectResult> {
939 match use_case {
940 TransportUseCase::CommandLine
941 | TransportUseCase::DesktopApp
942 | TransportUseCase::Development => {
943 #[cfg(feature = "stdio")]
945 {
946 self.connect_with_stdio_simple(server_url).await
947 }
948 #[cfg(not(feature = "stdio"))]
949 {
950 Err(McpError::Transport(
951 "STDIO transport requested but feature not enabled".to_string(),
952 ))
953 }
954 }
955 TransportUseCase::WebApplication
956 | TransportUseCase::Mobile
957 | TransportUseCase::Enterprise => {
958 #[cfg(feature = "http")]
960 {
961 self.connect_with_http(server_url, None).await
962 }
963 #[cfg(not(feature = "http"))]
964 {
965 Err(McpError::Connection(
966 "HTTP transport not available".to_string(),
967 ))
968 }
969 }
970 TransportUseCase::LargeDataProcessing
971 | TransportUseCase::MemoryConstrained
972 | TransportUseCase::HighPerformance => {
973 #[cfg(feature = "http")]
976 {
977 self.connect_with_http(server_url, None).await
978 }
979 #[cfg(not(feature = "http"))]
980 {
981 Err(McpError::Connection(
982 "HTTP transport not available".to_string(),
983 ))
984 }
985 }
986 TransportUseCase::RealTime
987 | TransportUseCase::HighFrequency
988 | TransportUseCase::Interactive => {
989 #[cfg(feature = "http")]
991 {
992 self.connect_with_http(server_url, None).await
993 }
994 #[cfg(not(feature = "http"))]
995 {
996 Err(McpError::Connection(
997 "HTTP transport not available".to_string(),
998 ))
999 }
1000 }
1001 }
1002 }
1003
1004 pub fn get_transport_recommendation(&self, use_case: TransportUseCase) -> &'static str {
1018 match use_case {
1019 TransportUseCase::CommandLine
1020 | TransportUseCase::DesktopApp
1021 | TransportUseCase::Development => {
1022 "STDIO Transport - complete for command-line tools, desktop apps, and local development. Direct process communication with zero network configuration."
1023 }
1024 TransportUseCase::WebApplication
1025 | TransportUseCase::Mobile
1026 | TransportUseCase::Enterprise => {
1027 "HTTP Transport - Ideal for web applications, mobile clients, and enterprise environments. Universal compatibility with firewalls and proxies."
1028 }
1029 TransportUseCase::LargeDataProcessing
1030 | TransportUseCase::MemoryConstrained
1031 | TransportUseCase::HighPerformance => {
1032 "Standard Streamable HTTP - interoperable MCP streaming with HTTP/2-capable clients, proxy compatibility, and subscription SSE."
1033 }
1034 TransportUseCase::RealTime
1035 | TransportUseCase::HighFrequency
1036 | TransportUseCase::Interactive => {
1037 "Standard Streamable HTTP subscriptions - interoperable real-time notifications over request-scoped SSE."
1038 }
1039 }
1040 }
1041
1042 pub fn get_transport_comparison(&self) -> Vec<TransportInfo> {
1058 vec![
1059 TransportInfo {
1060 name: "STDIO".to_string(),
1061 description: "Direct process communication - spawn and communicate with MCP servers as child processes".to_string(),
1062 use_cases: vec!["Command-line tools".to_string(), "Desktop applications".to_string(), "Local development".to_string()],
1063 pros: vec!["Zero network configuration".to_string(), "Direct process lifecycle management".to_string(), "Highest security (local only)".to_string()],
1064 cons: vec!["Local only".to_string(), "Requires process spawning".to_string()],
1065 latency: "<1ms".to_string(),
1066 throughput: "High".to_string(),
1067 available: cfg!(feature = "stdio"),
1068 },
1069 TransportInfo {
1070 name: "HTTP".to_string(),
1071 description: "Traditional HTTP/1.1 with Server-Sent Events for notifications".to_string(),
1072 use_cases: vec!["Web applications".to_string(), "Mobile clients".to_string(), "Enterprise environments".to_string()],
1073 pros: vec!["Universal compatibility".to_string(), "Firewall friendly".to_string(), "Simple debugging".to_string()],
1074 cons: vec!["Higher latency".to_string(), "Request/response only".to_string()],
1075 latency: "10-50ms".to_string(),
1076 throughput: "Medium".to_string(),
1077 available: cfg!(feature = "http"),
1078 },
1079 TransportInfo {
1080 name: "WebSocket".to_string(),
1081 description: "Full-duplex real-time communication with automatic reconnection".to_string(),
1082 use_cases: vec!["Real-time applications".to_string(), "Live collaboration".to_string(), "High-frequency messaging".to_string()],
1083 pros: vec!["Lowest latency".to_string(), "Full-duplex".to_string(), "Real-time notifications".to_string()],
1084 cons: vec!["More complex".to_string(), "Firewall issues possible".to_string()],
1085 latency: "<5ms".to_string(),
1086 throughput: "High".to_string(),
1087 available: cfg!(feature = "websocket"),
1088 },
1089 TransportInfo {
1090 name: "Streaming HTTP".to_string(),
1091 description: "complete HTTP with chunked streaming, smart content analysis, and compression".to_string(),
1092 use_cases: vec!["Large data processing".to_string(), "Memory-constrained environments".to_string(), "High-performance applications".to_string()],
1093 pros: vec!["Memory efficient".to_string(), "complete compression (Gzip/Brotli/Zstd)".to_string(), "smart content analysis".to_string(), "Adaptive buffering".to_string()],
1094 cons: vec!["More complexity".to_string(), "Requires chunked-encoding feature".to_string()],
1095 latency: "10-30ms".to_string(),
1096 throughput: "Very High".to_string(),
1097 available: cfg!(feature = "chunked-encoding"),
1098 },
1099 ]
1100 }
1101
1102 pub async fn list_tools(&self, cursor: Option<String>) -> McpResult<ListToolsResult> {
1108 self.ensure_connected().await?;
1109
1110 let params = ListToolsParams { cursor, meta: None };
1111 let request = JsonRpcRequest::new(
1112 Value::from(self.next_request_id().await),
1113 methods::TOOLS_LIST.to_string(),
1114 Some(params),
1115 )?;
1116
1117 let response = self.send_request(request).await?;
1118 self.handle_response(response)
1119 }
1120
1121 pub async fn call_tool(
1123 &self,
1124 name: String,
1125 arguments: Option<HashMap<String, Value>>,
1126 ) -> McpResult<CallToolResult> {
1127 self.ensure_connected().await?;
1128
1129 let params = if let Some(args) = arguments {
1130 CallToolParams::new_with_arguments(name, args)
1131 } else {
1132 CallToolParams::new(name)
1133 };
1134
1135 if self.config.validate_requests {
1136 validate_call_tool_params(¶ms)?;
1137 }
1138
1139 let request = JsonRpcRequest::new(
1140 Value::from(self.next_request_id().await),
1141 methods::TOOLS_CALL.to_string(),
1142 Some(params),
1143 )?;
1144
1145 let response = self.send_request(request).await?;
1146 self.handle_response(response)
1147 }
1148
1149 pub async fn list_resources(&self, cursor: Option<String>) -> McpResult<ListResourcesResult> {
1155 self.ensure_connected().await?;
1156
1157 let params = ListResourcesParams { cursor, meta: None };
1158 let request = JsonRpcRequest::new(
1159 Value::from(self.next_request_id().await),
1160 methods::RESOURCES_LIST.to_string(),
1161 Some(params),
1162 )?;
1163
1164 let response = self.send_request(request).await?;
1165 self.handle_response(response)
1166 }
1167
1168 pub async fn read_resource(&self, uri: String) -> McpResult<ReadResourceResult> {
1170 self.ensure_connected().await?;
1171
1172 let params = ReadResourceParams::new(uri);
1173
1174 if self.config.validate_requests {
1175 validate_read_resource_params(¶ms)?;
1176 }
1177
1178 let request = JsonRpcRequest::new(
1179 Value::from(self.next_request_id().await),
1180 methods::RESOURCES_READ.to_string(),
1181 Some(params),
1182 )?;
1183
1184 let response = self.send_request(request).await?;
1185 self.handle_response(response)
1186 }
1187
1188 pub async fn subscribe_resource(&self, uri: String) -> McpResult<SubscribeResourceResult> {
1190 self.ensure_connected().await?;
1191
1192 let params = SubscribeResourceParams { uri, meta: None };
1193 let request = JsonRpcRequest::new(
1194 Value::from(self.next_request_id().await),
1195 methods::RESOURCES_SUBSCRIBE.to_string(),
1196 Some(params),
1197 )?;
1198
1199 let response = self.send_request(request).await?;
1200 self.handle_response(response)
1201 }
1202
1203 pub async fn unsubscribe_resource(&self, uri: String) -> McpResult<UnsubscribeResourceResult> {
1205 self.ensure_connected().await?;
1206
1207 let params = UnsubscribeResourceParams { uri, meta: None };
1208 let request = JsonRpcRequest::new(
1209 Value::from(self.next_request_id().await),
1210 methods::RESOURCES_UNSUBSCRIBE.to_string(),
1211 Some(params),
1212 )?;
1213
1214 let response = self.send_request(request).await?;
1215 self.handle_response(response)
1216 }
1217
1218 pub async fn list_resource_templates(
1220 &self,
1221 cursor: Option<String>,
1222 ) -> McpResult<ListResourceTemplatesResult> {
1223 self.ensure_connected().await?;
1224
1225 let params = ListResourceTemplatesParams { cursor, meta: None };
1226 let request = JsonRpcRequest::new(
1227 Value::from(self.next_request_id().await),
1228 methods::RESOURCES_TEMPLATES_LIST.to_string(),
1229 Some(params),
1230 )?;
1231
1232 let response = self.send_request(request).await?;
1233 self.handle_response(response)
1234 }
1235
1236 pub async fn complete_argument(
1242 &self,
1243 reference: CompletionReference,
1244 argument: CompletionArgument,
1245 ) -> McpResult<CompleteResult> {
1246 self.ensure_connected().await?;
1247
1248 {
1250 let server_capabilities = self.server_capabilities.read().await;
1251 if let Some(capabilities) = server_capabilities.as_ref() {
1252 if capabilities.completions.is_none() {
1253 return Err(McpError::MethodNotFound(
1254 "Server does not support completion".to_string(),
1255 ));
1256 }
1257 }
1258 }
1259
1260 let params = CompleteParams {
1261 reference,
1262 argument,
1263 meta: None,
1264 };
1265
1266 let request = JsonRpcRequest::new(
1267 Value::from(self.next_request_id().await),
1268 methods::COMPLETION_COMPLETE.to_string(),
1269 Some(params),
1270 )?;
1271
1272 let response = self.send_request(request).await?;
1273 self.handle_response(response)
1274 }
1275
1276 pub async fn complete_prompt_argument(
1278 &self,
1279 prompt_name: &str,
1280 argument_name: &str,
1281 current_value: &str,
1282 ) -> McpResult<Vec<String>> {
1283 let reference = CompletionReference::Prompt {
1284 name: prompt_name.to_string(),
1285 };
1286
1287 let argument = CompletionArgument {
1288 name: argument_name.to_string(),
1289 value: current_value.to_string(),
1290 };
1291
1292 let result = self.complete_argument(reference, argument).await?;
1293 Ok(result.completion.values)
1294 }
1295
1296 pub async fn complete_resource_uri(
1298 &self,
1299 uri_template: &str,
1300 argument_name: &str,
1301 current_value: &str,
1302 ) -> McpResult<Vec<String>> {
1303 let reference = CompletionReference::Resource {
1304 uri: uri_template.to_string(),
1305 };
1306
1307 let argument = CompletionArgument {
1308 name: argument_name.to_string(),
1309 value: current_value.to_string(),
1310 };
1311
1312 let result = self.complete_argument(reference, argument).await?;
1313 Ok(result.completion.values)
1314 }
1315
1316 pub async fn complete_tool_argument(
1318 &self,
1319 tool_name: &str,
1320 argument_name: &str,
1321 current_value: &str,
1322 ) -> McpResult<Vec<String>> {
1323 let reference = CompletionReference::Tool {
1324 name: tool_name.to_string(),
1325 };
1326
1327 let argument = CompletionArgument {
1328 name: argument_name.to_string(),
1329 value: current_value.to_string(),
1330 };
1331
1332 let result = self.complete_argument(reference, argument).await?;
1333 Ok(result.completion.values)
1334 }
1335
1336 pub async fn list_prompts(&self, cursor: Option<String>) -> McpResult<ListPromptsResult> {
1342 self.ensure_connected().await?;
1343
1344 let params = ListPromptsParams { cursor, meta: None };
1345 let request = JsonRpcRequest::new(
1346 Value::from(self.next_request_id().await),
1347 methods::PROMPTS_LIST.to_string(),
1348 Some(params),
1349 )?;
1350
1351 let response = self.send_request(request).await?;
1352 self.handle_response(response)
1353 }
1354
1355 pub async fn get_prompt(
1357 &self,
1358 name: String,
1359 arguments: Option<HashMap<String, String>>,
1360 ) -> McpResult<GetPromptResult> {
1361 self.ensure_connected().await?;
1362
1363 let params = if let Some(args) = arguments {
1364 GetPromptParams::new_with_arguments(name, args)
1365 } else {
1366 GetPromptParams::new(name)
1367 };
1368
1369 if self.config.validate_requests {
1370 validate_get_prompt_params(¶ms)?;
1371 }
1372
1373 let request = JsonRpcRequest::new(
1374 Value::from(self.next_request_id().await),
1375 methods::PROMPTS_GET.to_string(),
1376 Some(params),
1377 )?;
1378
1379 let response = self.send_request(request).await?;
1380 self.handle_response(response)
1381 }
1382
1383 pub async fn create_message(
1389 &self,
1390 params: CreateMessageParams,
1391 ) -> McpResult<CreateMessageResult> {
1392 self.ensure_connected().await?;
1393
1394 {
1396 let server_capabilities = self.server_capabilities.read().await;
1397 if let Some(capabilities) = server_capabilities.as_ref() {
1398 if capabilities.sampling.is_none() {
1399 return Err(McpError::Protocol(
1400 "Server does not support sampling".to_string(),
1401 ));
1402 }
1403 } else {
1404 return Err(McpError::Protocol("Not connected to server".to_string()));
1405 }
1406 }
1407
1408 if self.config.validate_requests {
1409 validate_create_message_params(¶ms)?;
1410 }
1411
1412 let request = JsonRpcRequest::new(
1413 Value::from(self.next_request_id().await),
1414 methods::SAMPLING_CREATE_MESSAGE.to_string(),
1415 Some(params),
1416 )?;
1417
1418 let response = self.send_request(request).await?;
1419 self.handle_response(response)
1420 }
1421
1422 pub async fn ping(&self) -> McpResult<PingResult> {
1428 self.ensure_connected().await?;
1429
1430 let request = JsonRpcRequest::new(
1431 Value::from(self.next_request_id().await),
1432 methods::PING.to_string(),
1433 Some(PingParams { meta: None }),
1434 )?;
1435
1436 let response = self.send_request(request).await?;
1437 self.handle_response(response)
1438 }
1439
1440 pub async fn set_logging_level(&self, level: LoggingLevel) -> McpResult<SetLoggingLevelResult> {
1442 self.ensure_connected().await?;
1443
1444 let params = SetLoggingLevelParams { level, meta: None };
1445 let request = JsonRpcRequest::new(
1446 Value::from(self.next_request_id().await),
1447 methods::LOGGING_SET_LEVEL.to_string(),
1448 Some(params),
1449 )?;
1450
1451 let response = self.send_request(request).await?;
1452 self.handle_response(response)
1453 }
1454
1455 async fn ensure_tasks_extension(&self) -> McpResult<()> {
1460 if !has_tasks_extension(&self.capabilities) {
1461 return Err(McpError::MissingRequiredClientCapability(
1462 serde_json::json!({"extensions": {(TASKS_EXTENSION_ID): {}}}),
1463 ));
1464 }
1465 if self
1466 .negotiated_protocol
1467 .read()
1468 .await
1469 .as_ref()
1470 .is_none_or(|protocol| protocol.era != ProtocolEra::Modern)
1471 {
1472 return Err(McpError::MethodNotFound(format!(
1473 "the Tasks extension requires MCP {MODERN_PROTOCOL_VERSION}"
1474 )));
1475 }
1476 let server_supports_tasks = self
1477 .server_capabilities
1478 .read()
1479 .await
1480 .as_ref()
1481 .and_then(|capabilities| capabilities.extensions.as_ref())
1482 .is_some_and(|extensions| extensions.contains_key(TASKS_EXTENSION_ID));
1483 if !server_supports_tasks {
1484 return Err(McpError::MethodNotFound(
1485 "the server did not advertise io.modelcontextprotocol/tasks".to_string(),
1486 ));
1487 }
1488 Ok(())
1489 }
1490
1491 pub async fn get_task(&self, task_id: impl Into<String>) -> McpResult<Task> {
1493 self.ensure_connected().await?;
1494 self.ensure_tasks_extension().await?;
1495 let result: GetTaskResult = self
1496 .send_task_request(
1497 methods::TASKS_GET,
1498 GetTaskParams {
1499 task_id: task_id.into(),
1500 meta: HashMap::new(),
1501 },
1502 )
1503 .await?;
1504 result.task.validate().map_err(McpError::Protocol)?;
1505 Ok(result.task)
1506 }
1507
1508 pub async fn update_task(
1510 &self,
1511 task_id: impl Into<String>,
1512 input_responses: HashMap<String, Value>,
1513 ) -> McpResult<()> {
1514 self.ensure_connected().await?;
1515 self.ensure_tasks_extension().await?;
1516 let _: TaskAcknowledgement = self
1517 .send_task_request(
1518 methods::TASKS_UPDATE,
1519 UpdateTaskParams {
1520 task_id: task_id.into(),
1521 input_responses,
1522 meta: HashMap::new(),
1523 },
1524 )
1525 .await?;
1526 Ok(())
1527 }
1528
1529 pub async fn cancel_task(&self, task_id: impl Into<String>) -> McpResult<()> {
1531 self.ensure_connected().await?;
1532 self.ensure_tasks_extension().await?;
1533 let _: TaskAcknowledgement = self
1534 .send_task_request(
1535 methods::TASKS_CANCEL,
1536 CancelTaskParams {
1537 task_id: task_id.into(),
1538 meta: HashMap::new(),
1539 },
1540 )
1541 .await?;
1542 Ok(())
1543 }
1544
1545 pub async fn receive_notification(&self) -> McpResult<Option<JsonRpcNotification>> {
1551 let mut transport_guard = self.transport.lock().await;
1552 if let Some(transport) = transport_guard.as_mut() {
1553 transport.receive_notification().await
1554 } else {
1555 Err(McpError::Transport("Not connected".to_string()))
1556 }
1557 }
1558
1559 pub async fn listen(
1561 &self,
1562 notifications: crate::protocol::SubscriptionFilter,
1563 ) -> McpResult<ClientSubscription> {
1564 self.ensure_connected().await?;
1565 if self
1566 .negotiated_protocol
1567 .read()
1568 .await
1569 .as_ref()
1570 .is_none_or(|protocol| protocol.era != ProtocolEra::Modern)
1571 {
1572 return Err(McpError::MethodNotFound(
1573 "subscriptions/listen requires MCP 2026-07-28".to_string(),
1574 ));
1575 }
1576 if notifications.requests_tasks() {
1577 self.ensure_tasks_extension().await?;
1578 }
1579 let mut request = JsonRpcRequest::new(
1580 Value::from(self.next_request_id().await),
1581 methods::SUBSCRIPTIONS_LISTEN.to_string(),
1582 Some(crate::protocol::SubscriptionsListenParams {
1583 notifications,
1584 meta: HashMap::new(),
1585 }),
1586 )?;
1587 decorate_modern_request(&mut request, &self.info, &self.capabilities)?;
1588 let mut transport = self.transport.lock().await;
1589 transport
1590 .as_mut()
1591 .ok_or_else(|| McpError::Transport("Not connected".to_string()))?
1592 .open_subscription(request)
1593 .await
1594 }
1595
1596 pub async fn cancel_subscription(&self, subscription: &ClientSubscription) -> McpResult<()> {
1598 let mut transport = self.transport.lock().await;
1599 transport
1600 .as_mut()
1601 .ok_or_else(|| McpError::Transport("Not connected".to_string()))?
1602 .cancel_subscription(subscription.id())
1603 .await
1604 }
1605
1606 async fn send_request(&self, mut request: JsonRpcRequest) -> McpResult<JsonRpcResponse> {
1612 let modern = request.method == methods::SERVER_DISCOVER
1613 || self
1614 .negotiated_protocol
1615 .read()
1616 .await
1617 .as_ref()
1618 .is_some_and(|protocol| protocol.era == ProtocolEra::Modern);
1619 if modern && is_legacy_only_method(&request.method) {
1620 return Err(McpError::MethodNotFound(format!(
1621 "{} is not part of MCP {MODERN_PROTOCOL_VERSION}",
1622 request.method
1623 )));
1624 }
1625
1626 let mut round = 0_u8;
1627 loop {
1628 if modern {
1629 decorate_modern_request(&mut request, &self.info, &self.capabilities)?;
1630 }
1631 if self.config.validate_requests {
1632 validate_jsonrpc_request(&request)?;
1633 validate_mcp_request(&request.method, request.params.as_ref())?;
1634 }
1635
1636 let mut response = {
1637 let mut transport_guard = self.transport.lock().await;
1638 let transport = transport_guard
1639 .as_mut()
1640 .ok_or_else(|| McpError::Transport("Not connected".to_string()))?;
1641 transport.send_request(request.clone()).await?
1642 };
1643
1644 if self.config.validate_responses {
1645 validate_jsonrpc_response(&response)?;
1646 }
1647 if !modern {
1648 return Ok(response);
1649 }
1650
1651 let result = response.result.as_ref().ok_or_else(|| {
1652 McpError::Protocol("modern response is missing a result".to_string())
1653 })?;
1654 match result.get("resultType").and_then(Value::as_str) {
1655 Some("complete") => return Ok(response),
1656 Some("task") if request.method == methods::TOOLS_CALL => {
1657 self.ensure_tasks_extension().await?;
1658 let created: CreateTaskResult = serde_json::from_value(result.clone())?;
1659 created.task.validate().map_err(McpError::Protocol)?;
1660 let completed = self.drive_task(created.task).await?;
1661 response.result = Some(completed);
1662 return Ok(response);
1663 }
1664 Some("task") => {
1665 return Err(McpError::Protocol(format!(
1666 "resultType task is invalid for {}",
1667 request.method
1668 )))
1669 }
1670 Some("input_required") => {
1671 round = round.saturating_add(1);
1672 if round > self.config.max_mrtr_rounds {
1673 return Err(McpError::Protocol(format!(
1674 "MCP input_required exceeded {} rounds",
1675 self.config.max_mrtr_rounds
1676 )));
1677 }
1678 let input: InputRequiredResult = serde_json::from_value(result.clone())?;
1679 let mut input_responses = serde_json::Map::new();
1680 for (key, input_request) in input.input_requests {
1681 let response = self.fulfill_input_request(input_request).await?;
1682 input_responses.insert(key, response);
1683 }
1684
1685 let params = request
1686 .params
1687 .get_or_insert_with(|| Value::Object(serde_json::Map::new()))
1688 .as_object_mut()
1689 .ok_or_else(|| {
1690 McpError::Protocol("MRTR request params must be an object".to_string())
1691 })?;
1692 if !input_responses.is_empty() {
1693 params.insert("inputResponses".to_string(), Value::Object(input_responses));
1694 }
1695 if let Some(request_state) = input.request_state {
1696 params.insert("requestState".to_string(), Value::String(request_state));
1697 }
1698 request.id = Value::from(self.next_request_id().await);
1699 }
1700 Some(other) => {
1701 return Err(McpError::Protocol(format!(
1702 "unsupported MCP resultType: {other}"
1703 )))
1704 }
1705 None => {
1706 return Err(McpError::Protocol(
1707 "MCP 2026 response is missing resultType".to_string(),
1708 ))
1709 }
1710 }
1711 }
1712 }
1713
1714 async fn send_task_request<P, T>(&self, method: &str, params: P) -> McpResult<T>
1715 where
1716 P: serde::Serialize,
1717 T: serde::de::DeserializeOwned,
1718 {
1719 let mut request = JsonRpcRequest::new(
1720 Value::from(self.next_request_id().await),
1721 method.to_string(),
1722 Some(params),
1723 )?;
1724 decorate_modern_request(&mut request, &self.info, &self.capabilities)?;
1725 if self.config.validate_requests {
1726 validate_jsonrpc_request(&request)?;
1727 validate_mcp_request(&request.method, request.params.as_ref())?;
1728 }
1729 let response = {
1730 let mut transport_guard = self.transport.lock().await;
1731 transport_guard
1732 .as_mut()
1733 .ok_or_else(|| McpError::Transport("Not connected".to_string()))?
1734 .send_request(request)
1735 .await?
1736 };
1737 if self.config.validate_responses {
1738 validate_jsonrpc_response(&response)?;
1739 }
1740 self.handle_response(response)
1741 }
1742
1743 async fn drive_task(&self, mut task: Task) -> McpResult<Value> {
1744 loop {
1745 match task.status {
1746 TaskStatus::Completed => {
1747 let mut result = task.result.ok_or_else(|| {
1748 McpError::Protocol("completed task is missing result".to_string())
1749 })?;
1750 if let Some(object) = result.as_object_mut() {
1751 object
1752 .entry("resultType")
1753 .or_insert_with(|| Value::String("complete".to_string()));
1754 }
1755 return Ok(result);
1756 }
1757 TaskStatus::Failed => {
1758 return Err(McpError::Protocol(format!(
1759 "task {} failed: {}",
1760 task.task_id,
1761 task.error
1762 .as_ref()
1763 .map(Value::to_string)
1764 .unwrap_or_else(|| "unknown error".to_string())
1765 )))
1766 }
1767 TaskStatus::Cancelled => {
1768 return Err(McpError::Cancelled(format!(
1769 "task {} was cancelled",
1770 task.task_id
1771 )))
1772 }
1773 TaskStatus::InputRequired => {
1774 let mut responses = HashMap::new();
1775 for (key, input_request) in &task.input_requests {
1776 responses.insert(
1777 key.clone(),
1778 self.fulfill_input_request(input_request.clone()).await?,
1779 );
1780 }
1781 self.update_task(task.task_id.clone(), responses).await?;
1782 }
1783 TaskStatus::Working => {}
1784 }
1785 let delay = task.poll_interval_ms.unwrap_or(1_000).max(1);
1786 tokio::time::sleep(std::time::Duration::from_millis(delay)).await;
1787 task = self.get_task(task.task_id.clone()).await?;
1788 }
1789 }
1790
1791 async fn fulfill_input_request(&self, input_request: Value) -> McpResult<Value> {
1792 let object = input_request.as_object().ok_or_else(|| {
1793 McpError::Protocol("MRTR input request must be an object".to_string())
1794 })?;
1795 let method = object
1796 .get("method")
1797 .and_then(Value::as_str)
1798 .ok_or_else(|| {
1799 McpError::Protocol("MRTR input request is missing method".to_string())
1800 })?;
1801 let params = object
1802 .get("params")
1803 .cloned()
1804 .unwrap_or_else(|| serde_json::json!({}));
1805
1806 match method {
1807 methods::SAMPLING_CREATE_MESSAGE => {
1808 let params: CreateMessageParams = serde_json::from_value(params)?;
1809 Ok(serde_json::to_value(
1810 self.request_handler.handle_create_message(params).await?,
1811 )?)
1812 }
1813 methods::ROOTS_LIST => {
1814 let params: ListRootsParams = serde_json::from_value(params)?;
1815 Ok(serde_json::to_value(
1816 self.request_handler.handle_list_roots(params).await?,
1817 )?)
1818 }
1819 methods::ELICITATION_CREATE => {
1820 let params: ElicitParams = serde_json::from_value(params)?;
1821 Ok(serde_json::to_value(
1822 self.request_handler.handle_elicit(params).await?,
1823 )?)
1824 }
1825 _ => Err(McpError::MethodNotFound(format!(
1826 "unsupported MRTR input method: {method}"
1827 ))),
1828 }
1829 }
1830
1831 fn handle_response<T>(&self, response: JsonRpcResponse) -> McpResult<T>
1833 where
1834 T: serde::de::DeserializeOwned,
1835 {
1836 let result = response
1839 .result
1840 .ok_or_else(|| McpError::Protocol("Missing result in response".to_string()))?;
1841
1842 serde_json::from_value(result).map_err(|e| McpError::Serialization(e.to_string()))
1843 }
1844
1845 async fn ensure_connected(&self) -> McpResult<()> {
1847 if !self.is_connected().await {
1848 return Err(McpError::Connection("Not connected to server".to_string()));
1849 }
1850 Ok(())
1851 }
1852
1853 async fn next_request_id(&self) -> u64 {
1855 let mut counter = self.request_counter.lock().await;
1856 *counter += 1;
1857 *counter
1858 }
1859}
1860
1861pub struct McpClientBuilder {
1863 name: String,
1864 version: String,
1865 capabilities: ClientCapabilities,
1866 config: ClientConfig,
1867}
1868
1869impl McpClientBuilder {
1870 pub fn new(name: String, version: String) -> Self {
1872 Self {
1873 name,
1874 version,
1875 capabilities: ClientCapabilities::default(),
1876 config: ClientConfig::default(),
1877 }
1878 }
1879
1880 pub fn capabilities(mut self, capabilities: ClientCapabilities) -> Self {
1882 self.capabilities = capabilities;
1883 self
1884 }
1885
1886 pub fn config(mut self, config: ClientConfig) -> Self {
1888 self.config = config;
1889 self
1890 }
1891
1892 pub fn request_timeout(mut self, timeout_ms: u64) -> Self {
1894 self.config.request_timeout_ms = timeout_ms;
1895 self
1896 }
1897
1898 pub fn max_retries(mut self, retries: u32) -> Self {
1900 self.config.max_retries = retries;
1901 self
1902 }
1903
1904 pub fn validate_requests(mut self, validate: bool) -> Self {
1906 self.config.validate_requests = validate;
1907 self
1908 }
1909
1910 pub fn validate_responses(mut self, validate: bool) -> Self {
1912 self.config.validate_responses = validate;
1913 self
1914 }
1915
1916 pub fn build(self) -> McpClient {
1918 let mut client = McpClient::new(self.name, self.version);
1919 client.set_capabilities(self.capabilities);
1920 client.config = self.config;
1921 client
1922 }
1923}
1924
1925#[derive(Debug, Clone, PartialEq, Eq)]
1940pub enum TransportUseCase {
1941 CommandLine,
1943 DesktopApp,
1945 Development,
1947 WebApplication,
1949 Mobile,
1951 Enterprise,
1953 LargeDataProcessing,
1955 MemoryConstrained,
1957 HighPerformance,
1959 RealTime,
1961 HighFrequency,
1963 Interactive,
1965}
1966
1967#[derive(Debug, Clone)]
1972pub struct TransportInfo {
1973 pub name: String,
1975 pub description: String,
1977 pub use_cases: Vec<String>,
1979 pub pros: Vec<String>,
1981 pub cons: Vec<String>,
1983 pub latency: String,
1985 pub throughput: String,
1987 pub available: bool,
1989}
1990
1991impl Default for TransportUseCase {
1993 fn default() -> Self {
1994 TransportUseCase::Development
1995 }
1996}
1997
1998impl std::fmt::Display for TransportUseCase {
1999 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2000 match self {
2001 TransportUseCase::CommandLine => write!(f, "Command-line tool"),
2002 TransportUseCase::DesktopApp => write!(f, "Desktop application"),
2003 TransportUseCase::Development => write!(f, "Development and testing"),
2004 TransportUseCase::WebApplication => write!(f, "Web application"),
2005 TransportUseCase::Mobile => write!(f, "Mobile application"),
2006 TransportUseCase::Enterprise => write!(f, "Enterprise environment"),
2007 TransportUseCase::LargeDataProcessing => write!(f, "Large data processing"),
2008 TransportUseCase::MemoryConstrained => write!(f, "Memory-constrained environment"),
2009 TransportUseCase::HighPerformance => write!(f, "High-performance application"),
2010 TransportUseCase::RealTime => write!(f, "Real-time application"),
2011 TransportUseCase::HighFrequency => write!(f, "High-frequency messaging"),
2012 TransportUseCase::Interactive => write!(f, "Interactive application"),
2013 }
2014 }
2015}
2016
2017#[cfg(test)]
2018mod tests {
2019 use super::*;
2020 use async_trait::async_trait;
2021
2022 struct MockTransport {
2024 responses: Vec<JsonRpcResponse>,
2025 current: usize,
2026 }
2027
2028 impl MockTransport {
2029 fn new(responses: Vec<JsonRpcResponse>) -> Self {
2030 Self {
2031 responses,
2032 current: 0,
2033 }
2034 }
2035 }
2036
2037 #[async_trait]
2038 impl Transport for MockTransport {
2039 async fn send_request(&mut self, _request: JsonRpcRequest) -> McpResult<JsonRpcResponse> {
2040 if self.current < self.responses.len() {
2041 let response = self.responses[self.current].clone();
2042 self.current += 1;
2043 Ok(response)
2044 } else {
2045 Err(McpError::Transport("No more responses".to_string()))
2046 }
2047 }
2048
2049 async fn send_notification(&mut self, _notification: JsonRpcNotification) -> McpResult<()> {
2050 Ok(())
2051 }
2052
2053 async fn receive_notification(&mut self) -> McpResult<Option<JsonRpcNotification>> {
2054 Ok(None)
2055 }
2056
2057 async fn close(&mut self) -> McpResult<()> {
2058 Ok(())
2059 }
2060 }
2061
2062 #[tokio::test]
2063 async fn test_client_builder() {
2064 let client = McpClientBuilder::new("test-client".to_string(), "1.0.0".to_string())
2065 .request_timeout(5000)
2066 .max_retries(5)
2067 .validate_requests(false)
2068 .build();
2069
2070 assert_eq!(client.config().request_timeout_ms, 5000);
2071 assert_eq!(client.config().max_retries, 5);
2072 assert!(!client.config().validate_requests);
2073 }
2074
2075 #[tokio::test]
2076 async fn test_mock_connection() {
2077 let init_result = InitializeResult::new(
2078 crate::protocol::LATEST_PROTOCOL_VERSION.to_string(),
2079 ServerCapabilities::default(),
2080 ServerInfo {
2081 name: "test-server".to_string(),
2082 version: "1.0.0".to_string(),
2083 description: None,
2084 title: Some("Test Server".to_string()),
2085 website_url: None,
2086 icons: None,
2087 },
2088 );
2089
2090 let init_response = JsonRpcResponse::success(Value::from(1), init_result.clone()).unwrap();
2091
2092 let transport = MockTransport::new(vec![init_response]);
2093
2094 let mut client = McpClient::new("test-client".to_string(), "1.0.0".to_string());
2095 client.set_protocol_mode(ProtocolMode::LegacyOnly);
2096 let result = client.connect(transport).await.unwrap();
2097
2098 assert_eq!(result.server_info.unwrap().name, "test-server");
2099 assert!(client.is_connected().await);
2100 }
2101
2102 #[tokio::test]
2103 async fn test_disconnect() {
2104 let init_result = InitializeResult::new(
2105 crate::protocol::LATEST_PROTOCOL_VERSION.to_string(),
2106 ServerCapabilities::default(),
2107 ServerInfo {
2108 name: "test-server".to_string(),
2109 version: "1.0.0".to_string(),
2110 description: None,
2111 title: Some("Test Server".to_string()),
2112 website_url: None,
2113 icons: None,
2114 },
2115 );
2116
2117 let init_response = JsonRpcResponse::success(Value::from(1), init_result).unwrap();
2118
2119 let transport = MockTransport::new(vec![init_response]);
2120
2121 let mut client = McpClient::new("test-client".to_string(), "1.0.0".to_string());
2122 client.set_protocol_mode(ProtocolMode::LegacyOnly);
2123 client.connect(transport).await.unwrap();
2124
2125 assert!(client.is_connected().await);
2126
2127 client.disconnect().await.unwrap();
2128 assert!(!client.is_connected().await);
2129 assert!(client.server_info().await.is_none());
2130 assert!(client.server_capabilities().await.is_none());
2131 }
2132}