mcp_probe_core/messages/
initialization.rs

1//! MCP initialization and protocol negotiation message types.
2//!
3//! This module contains message structures for the MCP initialization sequence:
4//! 1. Client sends `initialize` request with capabilities and client info
5//! 2. Server responds with its capabilities and server info
6//! 3. Client sends `initialized` notification to complete handshake
7//!
8//! The initialization sequence establishes:
9//! - Protocol version compatibility
10//! - Mutual capability negotiation
11//! - Client/server identification and metadata
12//!
13//! # Examples
14//!
15//! ```rust
16//! use mcp_probe_core::messages::{InitializeRequest, ProtocolVersion, Capabilities, Implementation};
17//! use serde_json::json;
18//!
19//! // Create client initialization request
20//! let init_request = InitializeRequest {
21//!     protocol_version: ProtocolVersion::V2024_11_05,
22//!     capabilities: Capabilities::default(),
23//!     client_info: Implementation::new("mcp-probe", "0.1.0"),
24//! };
25//! ```
26
27use super::{Capabilities, Implementation, ProtocolVersion};
28use serde::{Deserialize, Serialize};
29use serde_json::Value;
30
31/// Client-to-server initialization request.
32///
33/// This is the first message sent by the client to establish the MCP session.
34/// It includes the desired protocol version, client capabilities, and client metadata.
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct InitializeRequest {
37    /// Protocol version requested by the client
38    #[serde(rename = "protocolVersion")]
39    pub protocol_version: ProtocolVersion,
40
41    /// Capabilities offered by the client
42    pub capabilities: Capabilities,
43
44    /// Information about the client implementation
45    #[serde(rename = "clientInfo")]
46    pub client_info: Implementation,
47}
48
49impl InitializeRequest {
50    /// Create a new initialization request.
51    ///
52    /// # Examples
53    ///
54    /// ```rust
55    /// use mcp_probe_core::messages::{InitializeRequest, ProtocolVersion, Capabilities, Implementation};
56    ///
57    /// let request = InitializeRequest::new(
58    ///     ProtocolVersion::V2024_11_05,
59    ///     Capabilities::default(),
60    ///     Implementation::new("my-client", "1.0.0"),
61    /// );
62    /// ```
63    pub fn new(
64        protocol_version: ProtocolVersion,
65        capabilities: Capabilities,
66        client_info: Implementation,
67    ) -> Self {
68        Self {
69            protocol_version,
70            capabilities,
71            client_info,
72        }
73    }
74
75    /// Create a basic initialization request with default capabilities.
76    ///
77    /// This is a convenience method for simple clients that don't need
78    /// to specify custom capabilities.
79    ///
80    /// # Examples
81    ///
82    /// ```rust
83    /// use mcp_probe_core::messages::InitializeRequest;
84    ///
85    /// let request = InitializeRequest::basic("my-client", "1.0.0");
86    /// ```
87    pub fn basic(client_name: impl Into<String>, client_version: impl Into<String>) -> Self {
88        Self::new(
89            ProtocolVersion::default(),
90            Capabilities::default(),
91            Implementation::new(client_name, client_version),
92        )
93    }
94
95    /// Add custom client metadata to the initialization request.
96    ///
97    /// # Examples
98    ///
99    /// ```rust
100    /// use mcp_probe_core::messages::InitializeRequest;
101    /// use serde_json::json;
102    ///
103    /// let request = InitializeRequest::basic("my-client", "1.0.0")
104    ///     .with_client_metadata("platform", json!("rust"))
105    ///     .with_client_metadata("os", json!("linux"));
106    /// ```
107    pub fn with_client_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
108        self.client_info.metadata.insert(key.into(), value);
109        self
110    }
111
112    /// Check if the requested protocol version is supported.
113    pub fn is_supported_version(&self) -> bool {
114        self.protocol_version.is_supported()
115    }
116}
117
118/// Server-to-client initialization response.
119///
120/// This is sent by the server in response to the client's initialization request.
121/// It includes the server's capabilities, server metadata, and the negotiated protocol version.
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct InitializeResponse {
124    /// Protocol version that will be used for the session
125    #[serde(rename = "protocolVersion")]
126    pub protocol_version: ProtocolVersion,
127
128    /// Capabilities offered by the server
129    pub capabilities: Capabilities,
130
131    /// Information about the server implementation
132    #[serde(rename = "serverInfo")]
133    pub server_info: Implementation,
134
135    /// Optional instructions or additional information for the client
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub instructions: Option<String>,
138}
139
140impl InitializeResponse {
141    /// Create a new initialization response.
142    ///
143    /// # Examples
144    ///
145    /// ```rust
146    /// use mcp_probe_core::messages::{InitializeResponse, ProtocolVersion, Capabilities, Implementation};
147    ///
148    /// let response = InitializeResponse::new(
149    ///     ProtocolVersion::V2024_11_05,
150    ///     Capabilities::default(),
151    ///     Implementation::new("my-server", "1.0.0"),
152    ///     None,
153    /// );
154    /// ```
155    pub fn new(
156        protocol_version: ProtocolVersion,
157        capabilities: Capabilities,
158        server_info: Implementation,
159        instructions: Option<String>,
160    ) -> Self {
161        Self {
162            protocol_version,
163            capabilities,
164            server_info,
165            instructions,
166        }
167    }
168
169    /// Create a basic initialization response with default capabilities.
170    ///
171    /// # Examples
172    ///
173    /// ```rust
174    /// use mcp_probe_core::messages::InitializeResponse;
175    ///
176    /// let response = InitializeResponse::basic("my-server", "1.0.0");
177    /// ```
178    pub fn basic(server_name: impl Into<String>, server_version: impl Into<String>) -> Self {
179        Self::new(
180            ProtocolVersion::default(),
181            Capabilities::default(),
182            Implementation::new(server_name, server_version),
183            None,
184        )
185    }
186
187    /// Add instructions for the client.
188    ///
189    /// Instructions can provide guidance to the client about how to use
190    /// the server's capabilities effectively.
191    ///
192    /// # Examples
193    ///
194    /// ```rust
195    /// use mcp_probe_core::messages::InitializeResponse;
196    ///
197    /// let response = InitializeResponse::basic("my-server", "1.0.0")
198    ///     .with_instructions("Use the 'calculator' tool for math operations");
199    /// ```
200    pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
201        self.instructions = Some(instructions.into());
202        self
203    }
204
205    /// Add custom server metadata to the initialization response.
206    ///
207    /// # Examples
208    ///
209    /// ```rust
210    /// use mcp_probe_core::messages::InitializeResponse;
211    /// use serde_json::json;
212    ///
213    /// let response = InitializeResponse::basic("my-server", "1.0.0")
214    ///     .with_server_metadata("supported_languages", json!(["python", "javascript"]));
215    /// ```
216    pub fn with_server_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
217        self.server_info.metadata.insert(key.into(), value);
218        self
219    }
220}
221
222/// Client-to-server initialization completion notification.
223///
224/// This notification is sent by the client after receiving and processing
225/// the server's initialization response. It signals that the client is
226/// ready to begin normal operations.
227///
228/// This is a notification (not a request), so the server should not respond.
229#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
230pub struct InitializedNotification {
231    /// Optional metadata about the initialization completion
232    #[serde(flatten)]
233    pub metadata: std::collections::HashMap<String, Value>,
234}
235
236impl InitializedNotification {
237    /// Create a new initialized notification.
238    ///
239    /// # Examples
240    ///
241    /// ```rust
242    /// use mcp_probe_core::messages::InitializedNotification;
243    ///
244    /// let notification = InitializedNotification::new();
245    /// ```
246    pub fn new() -> Self {
247        Self {
248            metadata: std::collections::HashMap::new(),
249        }
250    }
251
252    /// Create an initialized notification with custom metadata.
253    ///
254    /// # Examples
255    ///
256    /// ```rust
257    /// use mcp_probe_core::messages::InitializedNotification;
258    /// use serde_json::json;
259    /// use std::collections::HashMap;
260    ///
261    /// let mut metadata = HashMap::new();
262    /// metadata.insert("client_ready_time".to_string(), json!("2024-01-15T10:30:00Z"));
263    ///
264    /// let notification = InitializedNotification::with_metadata(metadata);
265    /// ```
266    pub fn with_metadata(metadata: std::collections::HashMap<String, Value>) -> Self {
267        Self { metadata }
268    }
269
270    /// Add a metadata field to the notification.
271    ///
272    /// # Examples
273    ///
274    /// ```rust
275    /// use mcp_probe_core::messages::InitializedNotification;
276    /// use serde_json::json;
277    ///
278    /// let notification = InitializedNotification::new()
279    ///     .add_metadata("timestamp", json!("2024-01-15T10:30:00Z"));
280    /// ```
281    pub fn add_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
282        self.metadata.insert(key.into(), value);
283        self
284    }
285}
286
287impl Default for InitializedNotification {
288    fn default() -> Self {
289        Self::new()
290    }
291}
292
293/// Ping request for connection health checking.
294///
295/// Either client or server can send ping requests to verify that the
296/// connection is still active and responsive.
297#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
298pub struct PingRequest {
299    /// Optional metadata for the ping
300    #[serde(flatten)]
301    pub metadata: std::collections::HashMap<String, Value>,
302}
303
304impl PingRequest {
305    /// Create a new ping request.
306    ///
307    /// # Examples
308    ///
309    /// ```rust
310    /// use mcp_probe_core::messages::PingRequest;
311    ///
312    /// let ping = PingRequest::new();
313    /// ```
314    pub fn new() -> Self {
315        Self {
316            metadata: std::collections::HashMap::new(),
317        }
318    }
319
320    /// Create a ping request with a timestamp.
321    ///
322    /// The timestamp can be used to measure round-trip time.
323    ///
324    /// # Examples
325    ///
326    /// ```rust
327    /// use mcp_probe_core::messages::PingRequest;
328    /// use serde_json::json;
329    ///
330    /// let ping = PingRequest::with_timestamp("2024-01-15T10:30:00Z");
331    /// ```
332    pub fn with_timestamp(timestamp: impl Into<String>) -> Self {
333        let mut ping = Self::new();
334        ping.metadata
335            .insert("timestamp".to_string(), Value::String(timestamp.into()));
336        ping
337    }
338
339    /// Add metadata to the ping request.
340    ///
341    /// # Examples
342    ///
343    /// ```rust
344    /// use mcp_probe_core::messages::PingRequest;
345    /// use serde_json::json;
346    ///
347    /// let ping = PingRequest::new()
348    ///     .add_metadata("source", json!("health_check"));
349    /// ```
350    pub fn add_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
351        self.metadata.insert(key.into(), value);
352        self
353    }
354}
355
356impl Default for PingRequest {
357    fn default() -> Self {
358        Self::new()
359    }
360}
361
362/// Pong response to ping requests.
363///
364/// This is sent in response to ping requests and can include the original
365/// ping metadata for round-trip time calculation.
366#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
367pub struct PongResponse {
368    /// Optional metadata echoed from the ping or added by the responder
369    #[serde(flatten)]
370    pub metadata: std::collections::HashMap<String, Value>,
371}
372
373impl PongResponse {
374    /// Create a new pong response.
375    ///
376    /// # Examples
377    ///
378    /// ```rust
379    /// use mcp_probe_core::messages::PongResponse;
380    ///
381    /// let pong = PongResponse::new();
382    /// ```
383    pub fn new() -> Self {
384        Self {
385            metadata: std::collections::HashMap::new(),
386        }
387    }
388
389    /// Create a pong response echoing ping metadata.
390    ///
391    /// This is useful for round-trip time calculation.
392    ///
393    /// # Examples
394    ///
395    /// ```rust
396    /// use mcp_probe_core::messages::{PingRequest, PongResponse};
397    /// use serde_json::json;
398    ///
399    /// let ping = PingRequest::with_timestamp("2024-01-15T10:30:00Z");
400    /// let pong = PongResponse::echo(&ping);
401    /// ```
402    pub fn echo(ping: &PingRequest) -> Self {
403        Self {
404            metadata: ping.metadata.clone(),
405        }
406    }
407
408    /// Add metadata to the pong response.
409    ///
410    /// # Examples
411    ///
412    /// ```rust
413    /// use mcp_probe_core::messages::PongResponse;
414    /// use serde_json::json;
415    ///
416    /// let pong = PongResponse::new()
417    ///     .add_metadata("response_time", json!("2024-01-15T10:30:01Z"));
418    /// ```
419    pub fn add_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
420        self.metadata.insert(key.into(), value);
421        self
422    }
423}
424
425impl Default for PongResponse {
426    fn default() -> Self {
427        Self::new()
428    }
429}
430
431#[cfg(test)]
432mod tests {
433    use super::*;
434    use serde_json::json;
435
436    #[test]
437    fn test_initialize_request_creation() {
438        let request = InitializeRequest::basic("test-client", "1.0.0");
439
440        assert_eq!(request.protocol_version, ProtocolVersion::default());
441        assert_eq!(request.client_info.name, "test-client");
442        assert_eq!(request.client_info.version, "1.0.0");
443        assert!(request.is_supported_version());
444    }
445
446    #[test]
447    fn test_initialize_request_with_metadata() {
448        let request = InitializeRequest::basic("test-client", "1.0.0")
449            .with_client_metadata("platform", json!("rust"))
450            .with_client_metadata("os", json!("linux"));
451
452        assert_eq!(
453            request.client_info.metadata.get("platform").unwrap(),
454            &json!("rust")
455        );
456        assert_eq!(
457            request.client_info.metadata.get("os").unwrap(),
458            &json!("linux")
459        );
460    }
461
462    #[test]
463    fn test_initialize_response_creation() {
464        let response = InitializeResponse::basic("test-server", "2.0.0")
465            .with_instructions("Use tools carefully")
466            .with_server_metadata("max_tools", json!(10));
467
468        assert_eq!(response.server_info.name, "test-server");
469        assert_eq!(
470            response.instructions,
471            Some("Use tools carefully".to_string())
472        );
473        assert_eq!(
474            response.server_info.metadata.get("max_tools").unwrap(),
475            &json!(10)
476        );
477    }
478
479    #[test]
480    fn test_initialized_notification() {
481        let notification =
482            InitializedNotification::new().add_metadata("timestamp", json!("2024-01-15T10:30:00Z"));
483
484        assert_eq!(
485            notification.metadata.get("timestamp").unwrap(),
486            &json!("2024-01-15T10:30:00Z")
487        );
488    }
489
490    #[test]
491    fn test_ping_pong() {
492        let ping =
493            PingRequest::with_timestamp("2024-01-15T10:30:00Z").add_metadata("sequence", json!(1));
494
495        let pong =
496            PongResponse::echo(&ping).add_metadata("response_time", json!("2024-01-15T10:30:01Z"));
497
498        assert_eq!(
499            pong.metadata.get("timestamp").unwrap(),
500            &json!("2024-01-15T10:30:00Z")
501        );
502        assert_eq!(pong.metadata.get("sequence").unwrap(), &json!(1));
503        assert_eq!(
504            pong.metadata.get("response_time").unwrap(),
505            &json!("2024-01-15T10:30:01Z")
506        );
507    }
508
509    #[test]
510    fn test_serialization_roundtrip() {
511        let request = InitializeRequest::basic("test", "1.0.0");
512        let json = serde_json::to_string(&request).unwrap();
513        let deserialized: InitializeRequest = serde_json::from_str(&json).unwrap();
514        assert_eq!(request, deserialized);
515    }
516}