mcp_probe_core/messages/
core.rs

1//! Core JSON-RPC 2.0 message structures for MCP communication.
2//!
3//! This module provides the fundamental JSON-RPC message types that form the
4//! foundation of all MCP communication. These types strictly follow the
5//! JSON-RPC 2.0 specification with MCP-specific extensions.
6//!
7//! # Message Types
8//!
9//! - **Request**: Client-to-server messages expecting a response
10//! - **Response**: Server-to-client messages in reply to requests
11//! - **Notification**: One-way messages that don't expect responses
12//! - **Error**: Error responses for failed requests
13//!
14//! # Examples
15//!
16//! ```rust
17//! use mcp_probe_core::messages::core::{JsonRpcRequest, JsonRpcResponse, JsonRpcError};
18//! use serde_json::json;
19//!
20//! // Create a request
21//! let request = JsonRpcRequest::new(
22//!     "1".to_string(),
23//!     "tools/list".to_string(),
24//!     json!({}),
25//! );
26//!
27//! // Create a success response
28//! let response = JsonRpcResponse::success("1".to_string(), json!({"tools": []}));
29//!
30//! // Create an error response
31//! let error_response = JsonRpcResponse::error(
32//!     "1".to_string(),
33//!     JsonRpcError::method_not_found("unknown_method"),
34//! );
35//! ```
36
37use serde::{Deserialize, Serialize};
38use serde_json::Value;
39use uuid::Uuid;
40
41/// JSON-RPC 2.0 request message.
42///
43/// Represents a request from client to server that expects a response.
44/// All MCP operations (except notifications) use this message type.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct JsonRpcRequest {
47    /// JSON-RPC version (always "2.0")
48    pub jsonrpc: String,
49
50    /// Unique identifier for request/response correlation
51    pub id: RequestId,
52
53    /// Method name being invoked
54    pub method: String,
55
56    /// Parameters for the method (can be object or array)
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub params: Option<Value>,
59}
60
61impl JsonRpcRequest {
62    /// Create a new JSON-RPC request with the given ID, method, and parameters.
63    ///
64    /// # Examples
65    ///
66    /// ```rust
67    /// use mcp_probe_core::messages::core::JsonRpcRequest;
68    /// use serde_json::json;
69    ///
70    /// let request = JsonRpcRequest::new(
71    ///     "1".to_string(),
72    ///     "initialize".to_string(),
73    ///     json!({"protocolVersion": "2024-11-05"}),
74    /// );
75    /// ```
76    pub fn new(id: impl Into<RequestId>, method: impl Into<String>, params: Value) -> Self {
77        Self {
78            jsonrpc: "2.0".to_string(),
79            id: id.into(),
80            method: method.into(),
81            params: Some(params),
82        }
83    }
84
85    /// Create a new JSON-RPC request without parameters.
86    ///
87    /// # Examples
88    ///
89    /// ```rust
90    /// use mcp_probe_core::messages::core::JsonRpcRequest;
91    ///
92    /// let request = JsonRpcRequest::without_params("1", "tools/list");
93    /// ```
94    pub fn without_params(id: impl Into<RequestId>, method: impl Into<String>) -> Self {
95        Self {
96            jsonrpc: "2.0".to_string(),
97            id: id.into(),
98            method: method.into(),
99            params: None,
100        }
101    }
102
103    /// Generate a new request with a random UUID as the ID.
104    ///
105    /// This is useful when you don't need to track specific request IDs.
106    pub fn with_random_id(method: impl Into<String>, params: Value) -> Self {
107        Self::new(Uuid::new_v4().to_string(), method, params)
108    }
109
110    /// Check if this request has parameters.
111    pub fn has_params(&self) -> bool {
112        self.params.is_some()
113    }
114
115    /// Get the parameters as a specific type.
116    ///
117    /// # Examples
118    ///
119    /// ```rust
120    /// use mcp_probe_core::messages::core::JsonRpcRequest;
121    /// use serde_json::json;
122    /// use serde::{Deserialize, Serialize};
123    ///
124    /// #[derive(Deserialize, Serialize)]
125    /// struct InitParams {
126    ///     #[serde(rename = "protocolVersion")]
127    ///     protocol_version: String,
128    /// }
129    ///
130    /// let request = JsonRpcRequest::new(
131    ///     "1",
132    ///     "initialize",
133    ///     json!({"protocolVersion": "2024-11-05"})
134    /// );
135    ///
136    /// let params: InitParams = request.params_as().unwrap();
137    /// assert_eq!(params.protocol_version, "2024-11-05");
138    /// ```
139    pub fn params_as<T>(&self) -> Result<T, serde_json::Error>
140    where
141        T: for<'de> Deserialize<'de>,
142    {
143        match &self.params {
144            Some(params) => serde_json::from_value(params.clone()),
145            None => serde_json::from_value(Value::Null),
146        }
147    }
148}
149
150/// JSON-RPC 2.0 response message.
151///
152/// Represents a response from server to client for a previous request.
153/// Can contain either a successful result or an error.
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155pub struct JsonRpcResponse {
156    /// JSON-RPC version (always "2.0")
157    pub jsonrpc: String,
158
159    /// ID from the corresponding request
160    pub id: RequestId,
161
162    /// Success result (mutually exclusive with error)
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub result: Option<Value>,
165
166    /// Error result (mutually exclusive with result)
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub error: Option<JsonRpcError>,
169}
170
171impl JsonRpcResponse {
172    /// Create a successful response with the given result.
173    ///
174    /// # Examples
175    ///
176    /// ```rust
177    /// use mcp_probe_core::messages::core::JsonRpcResponse;
178    /// use serde_json::json;
179    ///
180    /// let response = JsonRpcResponse::success("1", json!({"status": "ok"}));
181    /// ```
182    pub fn success(id: impl Into<RequestId>, result: Value) -> Self {
183        Self {
184            jsonrpc: "2.0".to_string(),
185            id: id.into(),
186            result: Some(result),
187            error: None,
188        }
189    }
190
191    /// Create an error response with the given error.
192    ///
193    /// # Examples
194    ///
195    /// ```rust
196    /// use mcp_probe_core::messages::core::{JsonRpcResponse, JsonRpcError};
197    ///
198    /// let response = JsonRpcResponse::error(
199    ///     "1",
200    ///     JsonRpcError::method_not_found("unknown_method"),
201    /// );
202    /// ```
203    pub fn error(id: impl Into<RequestId>, error: JsonRpcError) -> Self {
204        Self {
205            jsonrpc: "2.0".to_string(),
206            id: id.into(),
207            result: None,
208            error: Some(error),
209        }
210    }
211
212    /// Check if this response represents a success.
213    pub fn is_success(&self) -> bool {
214        self.result.is_some() && self.error.is_none()
215    }
216
217    /// Check if this response represents an error.
218    pub fn is_error(&self) -> bool {
219        self.error.is_some()
220    }
221
222    /// Get the result as a specific type.
223    ///
224    /// Returns an error if the response is an error response or if
225    /// deserialization fails.
226    pub fn result_as<T>(&self) -> Result<T, Box<dyn std::error::Error + Send + Sync>>
227    where
228        T: for<'de> Deserialize<'de>,
229    {
230        match (&self.result, &self.error) {
231            (Some(result), None) => Ok(serde_json::from_value(result.clone())?),
232            (None, Some(error)) => Err(format!("JSON-RPC error: {}", error).into()),
233            _ => Err("Invalid response: both result and error are present or missing".into()),
234        }
235    }
236}
237
238/// JSON-RPC 2.0 notification message.
239///
240/// Represents a one-way message that doesn't expect a response.
241/// Used for events, logging, and other fire-and-forget communications.
242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
243pub struct JsonRpcNotification {
244    /// JSON-RPC version (always "2.0")
245    pub jsonrpc: String,
246
247    /// Method name being invoked
248    pub method: String,
249
250    /// Parameters for the method (can be object or array)
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub params: Option<Value>,
253}
254
255impl JsonRpcNotification {
256    /// Create a new JSON-RPC notification with the given method and parameters.
257    ///
258    /// # Examples
259    ///
260    /// ```rust
261    /// use mcp_probe_core::messages::core::JsonRpcNotification;
262    /// use serde_json::json;
263    ///
264    /// let notification = JsonRpcNotification::new(
265    ///     "notifications/cancelled",
266    ///     json!({"requestId": "1"}),
267    /// );
268    /// ```
269    pub fn new(method: impl Into<String>, params: Value) -> Self {
270        Self {
271            jsonrpc: "2.0".to_string(),
272            method: method.into(),
273            params: Some(params),
274        }
275    }
276
277    /// Create a new JSON-RPC notification without parameters.
278    ///
279    /// # Examples
280    ///
281    /// ```rust
282    /// use mcp_probe_core::messages::core::JsonRpcNotification;
283    ///
284    /// let notification = JsonRpcNotification::without_params("ping");
285    /// ```
286    pub fn without_params(method: impl Into<String>) -> Self {
287        Self {
288            jsonrpc: "2.0".to_string(),
289            method: method.into(),
290            params: None,
291        }
292    }
293
294    /// Check if this notification has parameters.
295    pub fn has_params(&self) -> bool {
296        self.params.is_some()
297    }
298
299    /// Get the parameters as a specific type.
300    pub fn params_as<T>(&self) -> Result<T, serde_json::Error>
301    where
302        T: for<'de> Deserialize<'de>,
303    {
304        match &self.params {
305            Some(params) => serde_json::from_value(params.clone()),
306            None => serde_json::from_value(Value::Null),
307        }
308    }
309}
310
311/// JSON-RPC 2.0 error object.
312///
313/// Represents an error that occurred during request processing.
314/// Includes standard error codes and optional additional data.
315#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
316pub struct JsonRpcError {
317    /// Numeric error code
318    pub code: i32,
319
320    /// Human-readable error message
321    pub message: String,
322
323    /// Additional error data (optional)
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub data: Option<Value>,
326}
327
328impl JsonRpcError {
329    /// Create a new JSON-RPC error.
330    ///
331    /// # Examples
332    ///
333    /// ```rust
334    /// use mcp_probe_core::messages::core::JsonRpcError;
335    /// use serde_json::json;
336    ///
337    /// let error = JsonRpcError::new(-32000, "Custom error", Some(json!({"details": "More info"})));
338    /// ```
339    pub fn new(code: i32, message: impl Into<String>, data: Option<Value>) -> Self {
340        Self {
341            code,
342            message: message.into(),
343            data,
344        }
345    }
346
347    /// Create a "Parse error" (-32700).
348    ///
349    /// Used when the JSON cannot be parsed.
350    pub fn parse_error() -> Self {
351        Self::new(-32700, "Parse error", None)
352    }
353
354    /// Create an "Invalid Request" error (-32600).
355    ///
356    /// Used when the request is not a valid JSON-RPC request.
357    pub fn invalid_request(details: impl Into<String>) -> Self {
358        Self::new(
359            -32600,
360            "Invalid Request",
361            Some(Value::String(details.into())),
362        )
363    }
364
365    /// Create a "Method not found" error (-32601).
366    ///
367    /// Used when the requested method doesn't exist.
368    pub fn method_not_found(method: impl Into<String>) -> Self {
369        Self::new(
370            -32601,
371            "Method not found",
372            Some(Value::String(format!(
373                "Method '{}' not found",
374                method.into()
375            ))),
376        )
377    }
378
379    /// Create an "Invalid params" error (-32602).
380    ///
381    /// Used when method parameters are invalid.
382    pub fn invalid_params(details: impl Into<String>) -> Self {
383        Self::new(
384            -32602,
385            "Invalid params",
386            Some(Value::String(details.into())),
387        )
388    }
389
390    /// Create an "Internal error" (-32603).
391    ///
392    /// Used for server-side internal errors.
393    pub fn internal_error(details: impl Into<String>) -> Self {
394        Self::new(
395            -32603,
396            "Internal error",
397            Some(Value::String(details.into())),
398        )
399    }
400
401    /// Create a custom application error.
402    ///
403    /// Custom error codes should be in the range -32000 to -32099.
404    ///
405    /// # Examples
406    ///
407    /// ```rust
408    /// use mcp_probe_core::messages::core::JsonRpcError;
409    ///
410    /// let error = JsonRpcError::application_error(
411    ///     -32000,
412    ///     "Tool execution failed",
413    ///     "Tool 'calculator' returned non-zero exit code",
414    /// );
415    /// ```
416    pub fn application_error(
417        code: i32,
418        message: impl Into<String>,
419        details: impl Into<String>,
420    ) -> Self {
421        Self::new(code, message, Some(Value::String(details.into())))
422    }
423
424    /// Check if this is a standard JSON-RPC error (vs application-specific).
425    pub fn is_standard_error(&self) -> bool {
426        matches!(self.code, -32700..=-32600)
427    }
428
429    /// Check if this is an application-specific error.
430    pub fn is_application_error(&self) -> bool {
431        matches!(self.code, -32099..=-32000)
432    }
433}
434
435impl std::fmt::Display for JsonRpcError {
436    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
437        write!(f, "JSON-RPC Error {}: {}", self.code, self.message)?;
438        if let Some(data) = &self.data {
439            write!(f, " ({})", data)?;
440        }
441        Ok(())
442    }
443}
444
445impl std::error::Error for JsonRpcError {}
446
447/// Request ID for JSON-RPC messages.
448///
449/// Can be a string, number, or null according to JSON-RPC 2.0 specification.
450/// MCP typically uses string IDs for better traceability.
451#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
452#[serde(untagged)]
453pub enum RequestId {
454    /// String identifier
455    String(String),
456    /// Numeric identifier
457    Number(i64),
458    /// Null identifier (discouraged in MCP)
459    Null,
460}
461
462impl From<String> for RequestId {
463    fn from(s: String) -> Self {
464        Self::String(s)
465    }
466}
467
468impl From<&str> for RequestId {
469    fn from(s: &str) -> Self {
470        Self::String(s.to_string())
471    }
472}
473
474impl From<i64> for RequestId {
475    fn from(n: i64) -> Self {
476        Self::Number(n)
477    }
478}
479
480impl From<i32> for RequestId {
481    fn from(n: i32) -> Self {
482        Self::Number(n as i64)
483    }
484}
485
486impl std::fmt::Display for RequestId {
487    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
488        match self {
489            Self::String(s) => write!(f, "{}", s),
490            Self::Number(n) => write!(f, "{}", n),
491            Self::Null => write!(f, "null"),
492        }
493    }
494}
495
496/// Enum for any JSON-RPC message type.
497///
498/// This is useful for generic message handling where you need to
499/// differentiate between requests, responses, and notifications.
500#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
501#[serde(untagged)]
502pub enum JsonRpcMessage {
503    /// Request message
504    Request(JsonRpcRequest),
505    /// Response message
506    Response(JsonRpcResponse),
507    /// Notification message
508    Notification(JsonRpcNotification),
509}
510
511impl JsonRpcMessage {
512    /// Get the method name if this is a request or notification.
513    pub fn method(&self) -> Option<&str> {
514        match self {
515            Self::Request(req) => Some(&req.method),
516            Self::Notification(notif) => Some(&notif.method),
517            Self::Response(_) => None,
518        }
519    }
520
521    /// Get the request ID if this is a request or response.
522    pub fn id(&self) -> Option<&RequestId> {
523        match self {
524            Self::Request(req) => Some(&req.id),
525            Self::Response(resp) => Some(&resp.id),
526            Self::Notification(_) => None,
527        }
528    }
529
530    /// Check if this message expects a response.
531    pub fn expects_response(&self) -> bool {
532        matches!(self, Self::Request(_))
533    }
534}
535
536impl From<JsonRpcRequest> for JsonRpcMessage {
537    fn from(req: JsonRpcRequest) -> Self {
538        Self::Request(req)
539    }
540}
541
542impl From<JsonRpcResponse> for JsonRpcMessage {
543    fn from(resp: JsonRpcResponse) -> Self {
544        Self::Response(resp)
545    }
546}
547
548impl From<JsonRpcNotification> for JsonRpcMessage {
549    fn from(notif: JsonRpcNotification) -> Self {
550        Self::Notification(notif)
551    }
552}
553
554/// Type alias for request IDs to match client code expectations.
555pub type JsonRpcId = RequestId;
556
557#[cfg(test)]
558mod tests {
559    use super::*;
560    use serde_json::json;
561
562    #[test]
563    fn test_request_creation() {
564        let request = JsonRpcRequest::new("1", "test_method", json!({"param": "value"}));
565
566        assert_eq!(request.jsonrpc, "2.0");
567        assert_eq!(request.id, RequestId::String("1".to_string()));
568        assert_eq!(request.method, "test_method");
569        assert!(request.has_params());
570    }
571
572    #[test]
573    fn test_request_without_params() {
574        let request = JsonRpcRequest::without_params("1", "test_method");
575
576        assert!(!request.has_params());
577        assert_eq!(request.params, None);
578    }
579
580    #[test]
581    fn test_success_response() {
582        let response = JsonRpcResponse::success("1", json!({"result": "ok"}));
583
584        assert!(response.is_success());
585        assert!(!response.is_error());
586        assert_eq!(response.id, RequestId::String("1".to_string()));
587    }
588
589    #[test]
590    fn test_error_response() {
591        let error = JsonRpcError::method_not_found("unknown");
592        let response = JsonRpcResponse::error("1", error);
593
594        assert!(!response.is_success());
595        assert!(response.is_error());
596        assert_eq!(response.error.as_ref().unwrap().code, -32601);
597    }
598
599    #[test]
600    fn test_notification_creation() {
601        let notification = JsonRpcNotification::new("event", json!({"data": "value"}));
602
603        assert_eq!(notification.method, "event");
604        assert!(notification.has_params());
605    }
606
607    #[test]
608    fn test_json_rpc_error_types() {
609        let parse_error = JsonRpcError::parse_error();
610        assert_eq!(parse_error.code, -32700);
611        assert!(parse_error.is_standard_error());
612
613        let app_error = JsonRpcError::application_error(-32000, "App error", "Details");
614        assert_eq!(app_error.code, -32000);
615        assert!(app_error.is_application_error());
616    }
617
618    #[test]
619    fn test_request_id_variants() {
620        let string_id = RequestId::from("test");
621        let number_id = RequestId::from(42i64);
622        let null_id = RequestId::Null;
623
624        assert_eq!(string_id.to_string(), "test");
625        assert_eq!(number_id.to_string(), "42");
626        assert_eq!(null_id.to_string(), "null");
627    }
628
629    #[test]
630    fn test_message_serialization() {
631        let request = JsonRpcRequest::new("1", "test", json!({}));
632        let json = serde_json::to_string(&request).unwrap();
633        let deserialized: JsonRpcRequest = serde_json::from_str(&json).unwrap();
634        assert_eq!(request, deserialized);
635    }
636
637    #[test]
638    fn test_generic_message_handling() {
639        let request = JsonRpcMessage::Request(JsonRpcRequest::new("1", "test", json!({})));
640        let notification =
641            JsonRpcMessage::Notification(JsonRpcNotification::new("event", json!({})));
642
643        assert_eq!(request.method(), Some("test"));
644        assert_eq!(notification.method(), Some("event"));
645
646        assert!(request.expects_response());
647        assert!(!notification.expects_response());
648    }
649}