mcp_sdk_rs/
protocol.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::fmt;
4
5use crate::error::{Error, ErrorCode};
6
7/// The latest supported protocol version of MCP
8///
9/// This version represents the most recent protocol specification that this SDK supports.
10/// It is used during client-server handshake to ensure compatibility.
11pub const LATEST_PROTOCOL_VERSION: &str = "2025-03-26";
12
13/// List of all protocol versions supported by this SDK
14///
15/// This list is used during version negotiation to determine compatibility between
16/// client and server. The versions are listed in order of preference, with the
17/// most recent version first.
18pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] =
19    &[LATEST_PROTOCOL_VERSION, "2024-11-05", "2024-10-07"];
20
21/// JSON-RPC version used by the MCP protocol
22///
23/// MCP uses JSON-RPC 2.0 for its message format. This constant is used to ensure
24/// all messages conform to the correct specification.
25pub const JSONRPC_VERSION: &str = "2.0";
26
27/// A unique identifier for a request
28///
29/// This enum represents the possible types of request identifiers in the MCP protocol.
30/// It can be either a string or a number, as per JSON-RPC 2.0 specification.
31#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
32#[serde(untagged)]
33pub enum RequestId {
34    /// String representation of the request ID
35    String(String),
36    /// Numeric representation of the request ID
37    Number(i64),
38}
39
40/// Base JSON-RPC request structure
41///
42/// This struct represents a JSON-RPC request in the MCP protocol.
43/// It includes the JSON-RPC version, method name, optional parameters, and a unique identifier.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct Request {
46    /// JSON-RPC version (always "2.0")
47    pub jsonrpc: String,
48    /// Name of the method to be invoked
49    pub method: String,
50    /// Optional parameters for the method
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub params: Option<Value>,
53    /// Unique identifier for the request
54    pub id: RequestId,
55}
56
57/// Base JSON-RPC notification structure
58///
59/// This struct represents a JSON-RPC notification in the MCP protocol.
60/// It is similar to a request but does not include an id field, as it does not expect a response.
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Notification {
63    /// JSON-RPC version (always "2.0")
64    pub jsonrpc: String,
65    /// Name of the method to be invoked
66    pub method: String,
67    /// Optional parameters for the method
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub params: Option<Value>,
70}
71
72/// Base JSON-RPC response structure
73///
74/// This struct represents a JSON-RPC response in the MCP protocol.
75/// It includes the JSON-RPC version, the id of the request it's responding to,
76/// and either a result or an error field.
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct Response {
79    /// JSON-RPC version (always "2.0")
80    pub jsonrpc: String,
81    /// ID of the request this response corresponds to
82    pub id: RequestId,
83    /// The result of a successful request (null if there was an error)
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub result: Option<Value>,
86    /// The error object if the request failed (null if the request was successful)
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub error: Option<ResponseError>,
89}
90
91/// JSON-RPC error object
92///
93/// This struct represents an error that occurred during the processing of a JSON-RPC request.
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ResponseError {
96    /// The error code
97    pub code: i32,
98    /// A short description of the error
99    pub message: String,
100    /// Additional information about the error
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub data: Option<Value>,
103}
104
105impl Request {
106    /// Creates a new Request instance
107    ///
108    /// # Arguments
109    ///
110    /// * `method` - The name of the method to be invoked
111    /// * `params` - Optional parameters for the method
112    /// * `id` - Unique identifier for the request
113    ///
114    /// # Returns
115    ///
116    /// A new Request instance
117    pub fn new(method: impl Into<String>, params: Option<Value>, id: RequestId) -> Self {
118        Self {
119            jsonrpc: crate::JSONRPC_VERSION.to_string(),
120            method: method.into(),
121            params,
122            id,
123        }
124    }
125}
126
127impl Notification {
128    /// Creates a new Notification instance
129    ///
130    /// # Arguments
131    ///
132    /// * `method` - The name of the method to be invoked
133    /// * `params` - Optional parameters for the method
134    ///
135    /// # Returns
136    ///
137    /// A new Notification instance
138    pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
139        Self {
140            jsonrpc: crate::JSONRPC_VERSION.to_string(),
141            method: method.into(),
142            params,
143        }
144    }
145}
146
147impl Response {
148    /// Creates a new successful Response instance
149    ///
150    /// # Arguments
151    ///
152    /// * `id` - The id of the request this response corresponds to
153    /// * `result` - The result of the successful request
154    ///
155    /// # Returns
156    ///
157    /// A new Response instance representing a successful result
158    pub fn success(id: RequestId, result: Option<Value>) -> Self {
159        Self {
160            jsonrpc: crate::JSONRPC_VERSION.to_string(),
161            id,
162            result,
163            error: None,
164        }
165    }
166
167    /// Creates a new error Response instance
168    ///
169    /// # Arguments
170    ///
171    /// * `id` - The id of the request this response corresponds to
172    /// * `error` - The error that occurred during request processing
173    ///
174    /// # Returns
175    ///
176    /// A new Response instance representing an error
177    pub fn error(id: RequestId, error: ResponseError) -> Self {
178        Self {
179            jsonrpc: crate::JSONRPC_VERSION.to_string(),
180            id,
181            result: None,
182            error: Some(error),
183        }
184    }
185}
186
187impl From<Error> for ResponseError {
188    fn from(err: Error) -> Self {
189        match err {
190            Error::Protocol {
191                code,
192                message,
193                data,
194            } => ResponseError {
195                code: code.into(),
196                message,
197                data,
198            },
199            Error::Transport(msg) => ResponseError {
200                code: ErrorCode::InternalError.into(),
201                message: format!("Transport error: {}", msg),
202                data: None,
203            },
204            Error::Serialization(err) => ResponseError {
205                code: ErrorCode::ParseError.into(),
206                message: err.to_string(),
207                data: None,
208            },
209            Error::Io(err) => ResponseError {
210                code: ErrorCode::InternalError.into(),
211                message: err.to_string(),
212                data: None,
213            },
214            Error::Other(msg) => ResponseError {
215                code: ErrorCode::InternalError.into(),
216                message: msg,
217                data: None,
218            },
219        }
220    }
221}
222
223impl fmt::Display for RequestId {
224    /// Provides a string representation of the RequestId
225    ///
226    /// This implementation allows RequestId to be easily printed or converted to a string.
227    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228        match self {
229            RequestId::String(s) => write!(f, "{}", s),
230            RequestId::Number(n) => write!(f, "{}", n),
231        }
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use serde_json::json;
239
240    #[test]
241    fn test_request_creation() {
242        let id = RequestId::Number(1);
243        let params = Some(json!({"key": "value"}));
244        let request = Request::new("test_method", params.clone(), id.clone());
245
246        assert_eq!(request.jsonrpc, JSONRPC_VERSION);
247        assert_eq!(request.method, "test_method");
248        assert_eq!(request.params, params);
249        assert_eq!(request.id, id);
250    }
251
252    #[test]
253    fn test_notification_creation() {
254        let params = Some(json!({"event": "update"}));
255        let notification = Notification::new("test_event", params.clone());
256
257        assert_eq!(notification.jsonrpc, JSONRPC_VERSION);
258        assert_eq!(notification.method, "test_event");
259        assert_eq!(notification.params, params);
260    }
261
262    #[test]
263    fn test_response_success() {
264        let id = RequestId::String("test-1".to_string());
265        let result = Some(json!({"status": "ok"}));
266        let response = Response::success(id.clone(), result.clone());
267
268        assert_eq!(response.jsonrpc, JSONRPC_VERSION);
269        assert_eq!(response.id, id);
270        assert_eq!(response.result, result);
271        assert!(response.error.is_none());
272    }
273
274    #[test]
275    fn test_response_error() {
276        let id = RequestId::Number(123);
277        let error = ResponseError {
278            code: -32600,
279            message: "Invalid Request".to_string(),
280            data: Some(json!({"details": "missing method"})),
281        };
282        let response = Response::error(id.clone(), error.clone());
283
284        assert_eq!(response.jsonrpc, JSONRPC_VERSION);
285        assert_eq!(response.id, id);
286        assert!(response.result.is_none());
287
288        let response_error = response.error.unwrap();
289        assert_eq!(response_error.code, error.code);
290        assert_eq!(response_error.message, error.message);
291    }
292
293    #[test]
294    fn test_request_id_display() {
295        let num_id = RequestId::Number(42);
296        let str_id = RequestId::String("test-id".to_string());
297
298        assert_eq!(num_id.to_string(), "42");
299        assert_eq!(str_id.to_string(), "test-id");
300    }
301
302    #[test]
303    fn test_protocol_versions() {
304        assert!(SUPPORTED_PROTOCOL_VERSIONS.contains(&LATEST_PROTOCOL_VERSION));
305        assert_eq!(JSONRPC_VERSION, "2.0");
306    }
307}