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}