Skip to main content

prism_mcp_rs/client/
mcp_client.rs

1//! MCP client implementation
2//!
3//! Module provides the main MCP client that can connect to MCP servers,
4//! initialize connections, and perform operations like calling tools, reading resources,
5//! and executing prompts according to the Model Context Protocol specification.
6//!
7//! # Standards-track and proprietary HTTP
8//!
9//! Use [`McpClient::connect_with_http`] for standards-track MCP, including
10//! MCP 2026 request-scoped subscription SSE. The optional `chunked-encoding`
11//! feature exposes historical Prism-specific endpoints:
12//! ```toml
13//! [dependencies]
14//! prism-mcp-rs = { version = "3", features = ["chunked-encoding"] }
15//! ```
16//!
17//! Those helpers require explicit [`ProtocolMode::LegacyOnly`] and a peer that
18//! implements the same proprietary routes; automatic transport selection never
19//! chooses them.
20
21use 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/// Configuration for the MCP client
36#[derive(Debug, Clone)]
37pub struct ClientConfig {
38    /// Request timeout in milliseconds
39    pub request_timeout_ms: u64,
40    /// Maximum number of retry attempts
41    pub max_retries: u32,
42    /// Retry delay in milliseconds
43    pub retry_delay_ms: u64,
44    /// Whether to validate outgoing requests
45    pub validate_requests: bool,
46    /// Whether to validate incoming responses
47    pub validate_responses: bool,
48    /// Runtime MCP revision policy.
49    pub protocol_mode: ProtocolMode,
50    /// Maximum automatic multi-round-trip input cycles.
51    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
68/// Main MCP client implementation
69pub struct McpClient {
70    /// Client information
71    info: ClientInfo,
72    /// Client capabilities
73    capabilities: ClientCapabilities,
74    /// Client configuration
75    config: ClientConfig,
76    /// Active transport
77    transport: Arc<Mutex<Option<Box<dyn Transport>>>>,
78    /// Server capabilities (available after initialization)
79    server_capabilities: Arc<RwLock<Option<ServerCapabilities>>>,
80    /// Server information (available after initialization)
81    server_info: Arc<RwLock<Option<ServerInfo>>>,
82    /// Request ID counter
83    request_counter: Arc<Mutex<u64>>,
84    /// Connection state
85    connected: Arc<RwLock<bool>>,
86    /// Request handler for server-initiated requests
87    request_handler: Arc<dyn ClientRequestHandler>,
88    /// Protocol selected for the active connection.
89    negotiated_protocol: Arc<RwLock<Option<NegotiatedProtocol>>>,
90}
91
92impl McpClient {
93    /// Internal constructor - use builder() instead
94    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    /// Create a new client builder (primary constructor)
114    pub fn builder() -> crate::client::enhanced_builder::McpClientBuilder {
115        crate::client::enhanced_builder::McpClientBuilder::new()
116    }
117
118    /// Create a new MCP client with name and version
119    pub fn new(name: String, version: String) -> Self {
120        let info = ClientInfo::new(name, version);
121        Self::with_client_info(info)
122    }
123
124    /// Create a new MCP client with a specific ClientInfo
125    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    // ========================================================================
141    // Modern Fluent Interface (Primary API)
142    // ========================================================================
143
144    /// Access tools with fluent interface
145    pub fn tools(&self) -> crate::client::fluent_tools::ToolsBuilder<'_> {
146        crate::client::fluent_tools::ToolsBuilder::new(self)
147    }
148
149    /// Access resources with fluent interface
150    pub fn resources(&self) -> crate::client::fluent_interfaces::ResourcesBuilder<'_> {
151        crate::client::fluent_interfaces::ResourcesBuilder::new(self)
152    }
153
154    /// Access prompts with fluent interface
155    pub fn prompts(&self) -> crate::client::fluent_interfaces::PromptsBuilder<'_> {
156        crate::client::fluent_interfaces::PromptsBuilder::new(self)
157    }
158
159    // ========================================================================
160    // Deprecated Convenience Methods (Backward Compatibility)
161    // ========================================================================
162
163    /// Convenience method: call_tool with &str name and serde_json::Value arguments
164    ///
165    /// # Deprecated
166    /// Use `client.tools().call(name).args(arguments).execute().await` instead
167    #[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    /// Create a new MCP client with custom configuration
186    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    /// Set client capabilities
193    pub fn set_capabilities(&mut self, capabilities: ClientCapabilities) {
194        self.capabilities = capabilities;
195    }
196
197    /// Declare support for the official MCP Tasks extension.
198    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    /// Set custom request handler for server-initiated requests
206    ///
207    /// This enables bidirectional MCP communication by allowing the server
208    /// to initiate requests to the client for sampling, elicitation, etc.
209    ///
210    /// # Arguments
211    /// * `handler` - Custom implementation of ClientRequestHandler
212    ///
213    /// # Example
214    /// ```rust
215    /// use prism_mcp_rs::client::{McpClient, InteractiveClientRequestHandler};
216    ///
217    /// let mut client = McpClient::new("my-app".to_string(), "1.0.0".to_string());
218    /// let handler = InteractiveClientRequestHandler::new("my-app")
219    /// .add_root("file:///home/user", Some("Home"))
220    /// .auto_accept_elicitation(true);
221    /// client.set_request_handler(handler);
222    /// ```
223    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    /// Handle incoming server request
231    ///
232    /// Method processes server-initiated requests and returns appropriate
233    /// responses. It's called automatically by the transport layer when the
234    /// server sends a request to the client.
235    ///
236    /// # Arguments
237    /// * `request` - The JSON-RPC request from the server
238    ///
239    /// # Returns
240    /// JSON-RPC response to send back to the server
241    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    /// Get client information
293    pub fn info(&self) -> &ClientInfo {
294        &self.info
295    }
296
297    /// Get client capabilities
298    pub fn capabilities(&self) -> &ClientCapabilities {
299        &self.capabilities
300    }
301
302    /// Get client configuration
303    pub fn config(&self) -> &ClientConfig {
304        &self.config
305    }
306
307    /// Set the protocol selection policy before connecting.
308    pub fn set_protocol_mode(&mut self, mode: ProtocolMode) {
309        self.config.protocol_mode = mode;
310    }
311
312    /// Return the protocol selected for the active connection.
313    pub async fn negotiated_protocol(&self) -> Option<NegotiatedProtocol> {
314        self.negotiated_protocol.read().await.clone()
315    }
316
317    /// Get server capabilities (if connected)
318    pub async fn server_capabilities(&self) -> Option<ServerCapabilities> {
319        let capabilities = self.server_capabilities.read().await;
320        capabilities.clone()
321    }
322
323    /// Get server information (if connected)
324    pub async fn server_info(&self) -> Option<ServerInfo> {
325        let info = self.server_info.read().await;
326        info.clone()
327    }
328
329    /// Check if the client is connected
330    pub async fn is_connected(&self) -> bool {
331        let connected = self.connected.read().await;
332        *connected
333    }
334
335    // ========================================================================
336    // Connection Management
337    // ========================================================================
338
339    /// Connect to an MCP server using the provided transport
340    pub async fn connect<T>(&mut self, transport: T) -> McpResult<ConnectResult>
341    where
342        T: Transport + 'static,
343    {
344        // Set the transport
345        {
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        // Mark as connected
364        {
365            let mut connected = self.connected.write().await;
366            *connected = true;
367        }
368
369        Ok(connection)
370    }
371
372    /// Disconnect from the server
373    pub async fn disconnect(&self) -> McpResult<()> {
374        // Close the transport
375        {
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        // Clear server information
384        {
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        // Mark as disconnected
394        {
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    /// Negotiate the stateless MCP 2026-07-28 lifecycle.
404    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    /// Initialize the legacy MCP 2025-11-25 lifecycle.
463    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        // The send_request method will return an error if there was a JSON-RPC error
479        // so we can safely extract the result here
480
481        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        // Store server information
488        {
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    // ========================================================================
508    // Connection Convenience Methods
509    // ========================================================================
510
511    /// Connect to an MCP server over STDIO (convenience method)
512    ///
513    /// This is a convenience method that:
514    /// 1. Creates a STDIO transport
515    /// 2. Connects to the server
516    /// 3. Returns the initialization result
517    ///
518    /// # Example
519    /// ```rust,no_run
520    /// use prism_mcp_rs::prelude::*;
521    ///
522    /// #[tokio::main]
523    /// async fn main() -> McpResult<()> {
524    /// let mut client = McpClient::new("my-client".to_string(), "1.0.0".to_string());
525    /// let init_result = client.connect_with_stdio("my-mcp-server", vec!["--verbose"]).await?;
526    /// println!("Protocol: {}", init_result.protocol.version);
527    /// Ok(())
528    /// }
529    /// ```
530    #[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        // Some legacy stdio servers terminate after an unknown-method probe.
544        // Probe a disposable child and start a clean sibling for initialization.
545        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    /// Connect to an MCP server over HTTP (convenience method)
573    ///
574    /// This is a convenience method that:
575    /// 1. Creates an HTTP transport to the specified URL
576    /// 2. Connects to the server
577    /// 3. Returns the initialization result
578    ///
579    /// # Arguments
580    /// * `server_url` - The HTTP URL of the MCP server (e.g., "http://localhost:3000")
581    /// * `sse_url` - Optional Server-Sent Events URL for notifications
582    ///
583    /// # Example
584    /// ```rust,no_run
585    /// use prism_mcp_rs::prelude::*;
586    ///
587    /// #[tokio::main]
588    /// async fn main() -> McpResult<()> {
589    /// let mut client = McpClient::new("my-client".to_string(), "1.0.0".to_string());
590    /// let init_result = client.connect_with_http("http://localhost:3000", None).await?;
591    /// println!("Protocol: {}", init_result.protocol.version);
592    /// Ok(())
593    /// }
594    /// ```
595    #[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    /// Connect to an MCP server over STDIO with simple command (convenience method)
608    ///
609    /// This is a convenience method for the most common STDIO use case:
610    /// connecting to a server with just a command and no arguments.
611    ///
612    /// # Arguments
613    /// * `command` - Command to execute for the MCP server
614    ///
615    /// # Example
616    /// ```rust,no_run
617    /// use prism_mcp_rs::prelude::*;
618    ///
619    /// #[tokio::main]
620    /// async fn main() -> McpResult<()> {
621    /// let mut client = McpClient::new("my-client".to_string(), "1.0.0".to_string());
622    /// let init_result = client.connect_with_stdio_simple("my-mcp-server").await?;
623    /// println!("Protocol: {}", init_result.protocol.version);
624    /// Ok(())
625    /// }
626    /// ```
627    #[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    /// Connect to an MCP server over WebSocket (convenience method)
633    ///
634    /// This is a convenience method that:
635    /// 1. Creates a WebSocket transport to the specified URL
636    /// 2. Connects to the server
637    /// 3. Returns the initialization result
638    ///
639    /// # Arguments
640    /// * `server_url` - The WebSocket URL of the MCP server (e.g., "ws://localhost:8080")
641    ///
642    /// # Example
643    /// ```rust,no_run
644    /// use prism_mcp_rs::prelude::*;
645    ///
646    /// #[tokio::main]
647    /// async fn main() -> McpResult<()> {
648    /// let mut client = McpClient::new("my-client".to_string(), "1.0.0".to_string());
649    /// let init_result = client.connect_with_websocket("ws://localhost:8080").await?;
650    /// println!("Protocol: {}", init_result.protocol.version);
651    /// Ok(())
652    /// }
653    /// ```
654    #[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    /// Connect to an MCP server and run interactive session over STDIO (convenience method)
663    ///
664    /// This is a convenience method that:
665    /// 1. Creates a STDIO transport
666    /// 2. Connects to the server
667    /// 3. Runs an interactive session until Ctrl+C
668    /// 4. smoothly disconnects
669    ///
670    /// # Arguments
671    /// * `session_handler` - A closure that receives the connected client for operations
672    ///
673    /// # Example
674    /// ```rust,no_run
675    /// use prism_mcp_rs::prelude::*;
676    ///
677    /// #[tokio::main]
678    /// async fn main() -> McpResult<()> {
679    ///     let mut client = McpClient::new("my-client".to_string(), "1.0.0".to_string());
680    ///
681    ///     client.run_with_stdio("my-mcp-server", vec!["--verbose"], |_client| async move {
682    ///         // Your client operations here
683    ///         // Note: client operations would typically use _client parameter
684    ///         println!("Client connected successfully");
685    ///         Ok(())
686    ///     }).await
687    /// }
688    /// ```
689    #[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        // Connect with STDIO
701        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        // Set up Ctrl+C handler
714        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        // Run the session handler
723        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        // Disconnect smoothly
736        self.disconnect().await?;
737        tracing::info!("Client disconnected");
738
739        Ok(())
740    }
741
742    // ========================================================================
743    // Streaming HTTP Transport Methods
744    // ========================================================================
745
746    #[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    /// Connect to an MCP server with streaming HTTP transport for optimal efficiency
758    ///
759    /// This is a convenience method that:
760    /// 1. Creates a streaming HTTP transport with smart content analysis
761    /// 2. Connects to the server
762    /// 3. Returns the initialization result
763    ///
764    /// Optimized for:
765    /// - Large payload applications (>100KB)
766    /// - Memory-constrained environments
767    /// - High-performance requirements
768    /// - Applications with mixed payload sizes
769    ///
770    /// Features:
771    /// - Chunked transfer encoding for large payloads
772    /// - Multiple compression algorithms (Gzip, Brotli, Zstd)
773    /// - HTTP/2 Server Push support
774    /// - smart content analysis
775    /// - Automatic fallback to traditional HTTP
776    ///
777    /// # Arguments
778    /// * `server_url` - The HTTP URL of the MCP server (e.g., "http://localhost:3000")
779    /// * `config` - Streaming configuration options
780    ///
781    /// # Example
782    /// ```rust,no_run
783    /// use prism_mcp_rs::prelude::*;
784    /// use prism_mcp_rs::transport::StreamingConfig;
785    ///
786    /// #[tokio::main]
787    /// async fn main() -> McpResult<()> {
788    /// let mut client = McpClient::new("data-processor".to_string(), "1.0.0".to_string());
789    /// let config = StreamingConfig::performance_improved();
790    /// let init = client.connect_with_chunked_encoding("http://localhost:3000", config).await?;
791    /// println!("Connected with {}", init.protocol.version);
792    /// Ok(())
793    /// }
794    /// ```
795    #[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    /// Connect with streaming HTTP using default configuration
809    ///
810    /// This is a convenience method that uses default streaming HTTP configuration
811    /// improved for general use cases.
812    ///
813    /// # Arguments
814    /// * `server_url` - The HTTP URL of the MCP server
815    ///
816    /// # Example
817    /// ```rust,no_run
818    /// use prism_mcp_rs::prelude::*;
819    ///
820    /// #[tokio::main]
821    /// async fn main() -> McpResult<()> {
822    /// let mut client = McpClient::new("my-app".to_string(), "1.0.0".to_string());
823    /// let init = client.connect_with_chunked_encoding_default("http://localhost:3000").await?;
824    /// Ok(())
825    /// }
826    /// ```
827    #[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    /// Connect with memory-improved streaming HTTP configuration
840    ///
841    /// This configuration is improved for memory-constrained environments
842    /// with smaller chunk sizes and conservative buffering.
843    ///
844    /// # Arguments
845    /// * `server_url` - The HTTP URL of the MCP server
846    ///
847    /// # Example
848    /// ```rust,no_run
849    /// use prism_mcp_rs::prelude::*;
850    ///
851    /// #[tokio::main]
852    /// async fn main() -> McpResult<()> {
853    /// let mut client = McpClient::new("embedded-app".to_string(), "1.0.0".to_string());
854    /// let init = client.connect_with_chunked_encoding_memory_improved("http://localhost:3000").await?;
855    /// Ok(())
856    /// }
857    /// ```
858    #[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    /// Connect with performance-improved streaming HTTP configuration
873    ///
874    /// This configuration is improved for high-performance scenarios
875    /// with larger chunk sizes, multi-algorithm compression, and HTTP/2 features.
876    ///
877    /// # Arguments
878    /// * `server_url` - The HTTP URL of the MCP server
879    ///
880    /// # Example
881    /// ```rust,no_run
882    /// use prism_mcp_rs::prelude::*;
883    ///
884    /// #[tokio::main]
885    /// async fn main() -> McpResult<()> {
886    /// let mut client = McpClient::new("high-perf-app".to_string(), "1.0.0".to_string());
887    /// let init = client.connect_with_chunked_encoding_performance_improved("http://localhost:3000").await?;
888    /// Ok(())
889    /// }
890    /// ```
891    #[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    // ========================================================================
906    // complete Transport Selection Guide
907    // ========================================================================
908
909    /// Choose the right transport automatically based on your use case
910    ///
911    /// This is a convenience method that selects the optimal transport based on
912    /// your application characteristics. Use this if you want automatic selection.
913    ///
914    /// # Arguments
915    /// * `use_case` - Your primary use case
916    /// * `server_url` - Server URL (protocol will be adjusted automatically)
917    ///
918    /// # Example
919    /// ```rust,no_run
920    /// use prism_mcp_rs::prelude::*;
921    /// use prism_mcp_rs::client::TransportUseCase;
922    ///
923    /// #[tokio::main]
924    /// async fn main() -> McpResult<()> {
925    /// let mut client = McpClient::new("my-app".to_string(), "1.0.0".to_string());
926    /// let init = client.connect_with_recommended_transport(
927    /// TransportUseCase::LargeDataProcessing,
928    /// "http://localhost:3000"
929    /// ).await?;
930    /// Ok(())
931    /// }
932    /// ```
933    #[allow(unused_variables)] // server_url may not be used depending on features
934    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                // STDIO for command-line and desktop applications
944                #[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                // HTTP for web applications, mobile, and enterprise environments
959                #[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                // Standard Streamable HTTP supports streaming responses while
974                // remaining interoperable with conforming MCP peers.
975                #[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                // MCP 2026 subscriptions are carried by standard HTTP SSE.
990                #[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    /// Get transport recommendation for a use case (informational)
1005    ///
1006    /// Returns a human-readable recommendation for the best transport
1007    /// to use for a given use case.
1008    ///
1009    /// # Example
1010    /// ```rust
1011    /// use prism_mcp_rs::client::{McpClient, TransportUseCase};
1012    ///
1013    /// let client = McpClient::new("app".to_string(), "1.0.0".to_string());
1014    /// let recommendation = client.get_transport_recommendation(TransportUseCase::RealTime);
1015    /// println!("Recommendation: {}", recommendation);
1016    /// ```
1017    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    /// Get detailed transport comparison for decision making
1043    ///
1044    /// Returns detailed information about all available transports
1045    /// to help with transport selection decisions.
1046    ///
1047    /// # Example
1048    /// ```rust
1049    /// use prism_mcp_rs::client::McpClient;
1050    ///
1051    /// let client = McpClient::new("app".to_string(), "1.0.0".to_string());
1052    /// let comparison = client.get_transport_comparison();
1053    /// for transport in comparison {
1054    /// println!("{}: {}", transport.name, transport.description);
1055    /// }
1056    /// ```
1057    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    // ========================================================================
1103    // Tool Operations
1104    // ========================================================================
1105
1106    /// List available tools from the server
1107    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    /// Call a tool on the server
1122    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(&params)?;
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    // ========================================================================
1150    // Resource Operations
1151    // ========================================================================
1152
1153    /// List available resources from the server
1154    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    /// Read a resource from the server
1169    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(&params)?;
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    /// Subscribe to resource updates
1189    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    /// Unsubscribe from resource updates
1204    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    /// List resource templates from the server (New in 2025-11-25)
1219    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    // ========================================================================
1237    // Completion Operations (New in 2025-11-25)
1238    // ========================================================================
1239
1240    /// Request completion suggestions for an argument
1241    pub async fn complete_argument(
1242        &self,
1243        reference: CompletionReference,
1244        argument: CompletionArgument,
1245    ) -> McpResult<CompleteResult> {
1246        self.ensure_connected().await?;
1247
1248        // Check if server supports completion
1249        {
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    /// Convenience method for prompt argument completion
1277    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    /// Convenience method for resource URI completion
1297    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    /// Convenience method for tool argument completion
1317    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    // ========================================================================
1337    // Prompt Operations
1338    // ========================================================================
1339
1340    /// List available prompts from the server
1341    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    /// Get a prompt from the server
1356    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(&params)?;
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    // ========================================================================
1384    // Sampling Operations (if supported by server)
1385    // ========================================================================
1386
1387    /// Create a message using server-side sampling
1388    pub async fn create_message(
1389        &self,
1390        params: CreateMessageParams,
1391    ) -> McpResult<CreateMessageResult> {
1392        self.ensure_connected().await?;
1393
1394        // Check if server supports sampling
1395        {
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(&params)?;
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    // ========================================================================
1423    // Utility Operations
1424    // ========================================================================
1425
1426    /// Send a ping to the server
1427    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    /// Set the logging level on the server
1441    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    // ========================================================================
1456    // Tasks extension
1457    // ========================================================================
1458
1459    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    /// Retrieve the current state of a durable task.
1492    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    /// Submit responses for currently outstanding task input requests.
1509    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    /// Signal cooperative cancellation of a task.
1530    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    // ========================================================================
1546    // Notification Handling
1547    // ========================================================================
1548
1549    /// Receive notifications from the server
1550    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    /// Open a standards-track MCP 2026 notification stream.
1560    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    /// Close an open subscription using transport-appropriate semantics.
1597    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    // ========================================================================
1607    // Helper Methods
1608    // ========================================================================
1609
1610    /// Send a request and get a response
1611    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    /// Handle a JSON-RPC response and extract the result
1832    fn handle_response<T>(&self, response: JsonRpcResponse) -> McpResult<T>
1833    where
1834        T: serde::de::DeserializeOwned,
1835    {
1836        // JsonRpcResponse only contains successful responses
1837        // Errors are handled separately by the transport layer
1838        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    /// Check client is connected
1846    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    /// Get the next request ID
1854    async fn next_request_id(&self) -> u64 {
1855        let mut counter = self.request_counter.lock().await;
1856        *counter += 1;
1857        *counter
1858    }
1859}
1860
1861/// Client builder for easier construction
1862pub struct McpClientBuilder {
1863    name: String,
1864    version: String,
1865    capabilities: ClientCapabilities,
1866    config: ClientConfig,
1867}
1868
1869impl McpClientBuilder {
1870    /// Create a new client builder
1871    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    /// Set client capabilities
1881    pub fn capabilities(mut self, capabilities: ClientCapabilities) -> Self {
1882        self.capabilities = capabilities;
1883        self
1884    }
1885
1886    /// Set client configuration
1887    pub fn config(mut self, config: ClientConfig) -> Self {
1888        self.config = config;
1889        self
1890    }
1891
1892    /// Set request timeout
1893    pub fn request_timeout(mut self, timeout_ms: u64) -> Self {
1894        self.config.request_timeout_ms = timeout_ms;
1895        self
1896    }
1897
1898    /// Set maximum retries
1899    pub fn max_retries(mut self, retries: u32) -> Self {
1900        self.config.max_retries = retries;
1901        self
1902    }
1903
1904    /// Enable or disable request validation
1905    pub fn validate_requests(mut self, validate: bool) -> Self {
1906        self.config.validate_requests = validate;
1907        self
1908    }
1909
1910    /// Enable or disable response validation
1911    pub fn validate_responses(mut self, validate: bool) -> Self {
1912        self.config.validate_responses = validate;
1913        self
1914    }
1915
1916    /// Build the client
1917    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/// Transport use case guide for automatic selection
1926///
1927/// This enum helps you choose the right transport by describing your
1928/// primary use case. The client can then automatically select the
1929/// most appropriate transport configuration.
1930///
1931/// # Examples
1932/// ```rust
1933/// use prism_mcp_rs::client::TransportUseCase;
1934///
1935/// let use_case = TransportUseCase::RealTime; // Will choose WebSocket
1936/// let use_case = TransportUseCase::CommandLine; // Will choose STDIO
1937/// let use_case = TransportUseCase::Enterprise; // Will choose HTTP
1938/// ```
1939#[derive(Debug, Clone, PartialEq, Eq)]
1940pub enum TransportUseCase {
1941    /// Command-line tools and scripts - uses STDIO transport
1942    CommandLine,
1943    /// Desktop applications - uses STDIO transport
1944    DesktopApp,
1945    /// Local development and testing - uses STDIO transport
1946    Development,
1947    /// Web applications and browser integration - uses HTTP transport
1948    WebApplication,
1949    /// Mobile applications with battery constraints - uses HTTP transport
1950    Mobile,
1951    /// Enterprise environments with firewall restrictions - uses HTTP transport
1952    Enterprise,
1953    /// Applications processing large datasets - uses improved transport
1954    LargeDataProcessing,
1955    /// Memory-constrained environments - uses streaming transport
1956    MemoryConstrained,
1957    /// High-performance applications - uses improved transport
1958    HighPerformance,
1959    /// Real-time applications requiring low latency - uses WebSocket transport
1960    RealTime,
1961    /// High-frequency message exchange - uses WebSocket transport
1962    HighFrequency,
1963    /// Interactive applications and live collaboration - uses WebSocket transport
1964    Interactive,
1965}
1966
1967/// Information about a transport for comparison
1968///
1969/// detailed information about transport characteristics
1970/// to help with selection decisions.
1971#[derive(Debug, Clone)]
1972pub struct TransportInfo {
1973    /// Transport name
1974    pub name: String,
1975    /// Detailed description
1976    pub description: String,
1977    /// Primary use cases
1978    pub use_cases: Vec<String>,
1979    /// Advantages
1980    pub pros: Vec<String>,
1981    /// Disadvantages
1982    pub cons: Vec<String>,
1983    /// Typical latency range
1984    pub latency: String,
1985    /// Throughput characteristics
1986    pub throughput: String,
1987    /// Whether this transport is available (compiled in)
1988    pub available: bool,
1989}
1990
1991/// Default transport recommendations for different use cases
1992impl 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    // Mock transport for testing
2023    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}