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 = "2024-11-05";
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] = &[LATEST_PROTOCOL_VERSION, "2024-10-07"];
19
20/// JSON-RPC version used by the MCP protocol
21///
22/// MCP uses JSON-RPC 2.0 for its message format. This constant is used to ensure
23/// all messages conform to the correct specification.
24pub const JSONRPC_VERSION: &str = "2.0";
25
26/// A unique identifier for a request
27#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
28#[serde(untagged)]
29pub enum RequestId {
30    String(String),
31    Number(i64),
32}
33
34/// Base JSON-RPC request structure
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct Request {
37    pub jsonrpc: String,
38    pub method: String,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub params: Option<Value>,
41    pub id: RequestId,
42}
43
44/// Base JSON-RPC notification structure
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct Notification {
47    pub jsonrpc: String,
48    pub method: String,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub params: Option<Value>,
51}
52
53/// Base JSON-RPC response structure
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct Response {
56    pub jsonrpc: String,
57    pub id: RequestId,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub result: Option<Value>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub error: Option<ResponseError>,
62}
63
64/// JSON-RPC error object
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ResponseError {
67    pub code: i32,
68    pub message: String,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub data: Option<Value>,
71}
72
73impl Request {
74    pub fn new(method: impl Into<String>, params: Option<Value>, id: RequestId) -> Self {
75        Self {
76            jsonrpc: crate::JSONRPC_VERSION.to_string(),
77            method: method.into(),
78            params,
79            id,
80        }
81    }
82}
83
84impl Notification {
85    pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
86        Self {
87            jsonrpc: crate::JSONRPC_VERSION.to_string(),
88            method: method.into(),
89            params,
90        }
91    }
92}
93
94impl Response {
95    pub fn success(id: RequestId, result: Option<Value>) -> Self {
96        Self {
97            jsonrpc: crate::JSONRPC_VERSION.to_string(),
98            id,
99            result,
100            error: None,
101        }
102    }
103
104    pub fn error(id: RequestId, error: ResponseError) -> Self {
105        Self {
106            jsonrpc: crate::JSONRPC_VERSION.to_string(),
107            id,
108            result: None,
109            error: Some(error),
110        }
111    }
112}
113
114impl From<Error> for ResponseError {
115    fn from(err: Error) -> Self {
116        match err {
117            Error::Protocol {
118                code,
119                message,
120                data,
121            } => ResponseError {
122                code: code.into(),
123                message,
124                data,
125            },
126            Error::Transport(msg) => ResponseError {
127                code: ErrorCode::InternalError.into(),
128                message: format!("Transport error: {}", msg),
129                data: None,
130            },
131            Error::Serialization(err) => ResponseError {
132                code: ErrorCode::ParseError.into(),
133                message: err.to_string(),
134                data: None,
135            },
136            Error::Io(err) => ResponseError {
137                code: ErrorCode::InternalError.into(),
138                message: err.to_string(),
139                data: None,
140            },
141            Error::Other(msg) => ResponseError {
142                code: ErrorCode::InternalError.into(),
143                message: msg,
144                data: None,
145            },
146        }
147    }
148}
149
150impl fmt::Display for RequestId {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        match self {
153            RequestId::String(s) => write!(f, "{}", s),
154            RequestId::Number(n) => write!(f, "{}", n),
155        }
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use serde_json::json;
163
164    #[test]
165    fn test_request_creation() {
166        let id = RequestId::Number(1);
167        let params = Some(json!({"key": "value"}));
168        let request = Request::new("test_method", params.clone(), id.clone());
169
170        assert_eq!(request.jsonrpc, JSONRPC_VERSION);
171        assert_eq!(request.method, "test_method");
172        assert_eq!(request.params, params);
173        assert_eq!(request.id, id);
174    }
175
176    #[test]
177    fn test_notification_creation() {
178        let params = Some(json!({"event": "update"}));
179        let notification = Notification::new("test_event", params.clone());
180
181        assert_eq!(notification.jsonrpc, JSONRPC_VERSION);
182        assert_eq!(notification.method, "test_event");
183        assert_eq!(notification.params, params);
184    }
185
186    #[test]
187    fn test_response_success() {
188        let id = RequestId::String("test-1".to_string());
189        let result = Some(json!({"status": "ok"}));
190        let response = Response::success(id.clone(), result.clone());
191
192        assert_eq!(response.jsonrpc, JSONRPC_VERSION);
193        assert_eq!(response.id, id);
194        assert_eq!(response.result, result);
195        assert!(response.error.is_none());
196    }
197
198    #[test]
199    fn test_response_error() {
200        let id = RequestId::Number(123);
201        let error = ResponseError {
202            code: -32600,
203            message: "Invalid Request".to_string(),
204            data: Some(json!({"details": "missing method"})),
205        };
206        let response = Response::error(id.clone(), error.clone());
207
208        assert_eq!(response.jsonrpc, JSONRPC_VERSION);
209        assert_eq!(response.id, id);
210        assert!(response.result.is_none());
211
212        let response_error = response.error.unwrap();
213        assert_eq!(response_error.code, error.code);
214        assert_eq!(response_error.message, error.message);
215    }
216
217    #[test]
218    fn test_request_id_display() {
219        let num_id = RequestId::Number(42);
220        let str_id = RequestId::String("test-id".to_string());
221
222        assert_eq!(num_id.to_string(), "42");
223        assert_eq!(str_id.to_string(), "test-id");
224    }
225
226    #[test]
227    fn test_protocol_versions() {
228        assert!(SUPPORTED_PROTOCOL_VERSIONS.contains(&LATEST_PROTOCOL_VERSION));
229        assert_eq!(JSONRPC_VERSION, "2.0");
230    }
231}