Skip to main content

fastmcp_protocol/
jsonrpc.rs

1//! JSON-RPC 2.0 message types.
2
3use std::borrow::Cow;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use serde_json::Value;
7
8/// The JSON-RPC version string. Used as a static reference to avoid allocations.
9pub const JSONRPC_VERSION: &str = "2.0";
10
11/// Serializes the jsonrpc version field.
12fn serialize_jsonrpc_version<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
13where
14    S: Serializer,
15{
16    serializer.serialize_str(value)
17}
18
19/// Deserializes the jsonrpc version field, returning a borrowed reference for "2.0".
20fn deserialize_jsonrpc_version<'de, D>(deserializer: D) -> Result<Cow<'static, str>, D::Error>
21where
22    D: Deserializer<'de>,
23{
24    // Deserialize as a borrowed-or-owned string so we can avoid allocating for the common "2.0".
25    let s: Cow<'de, str> = Cow::deserialize(deserializer)?;
26    if s == JSONRPC_VERSION {
27        Ok(Cow::Borrowed(JSONRPC_VERSION))
28    } else {
29        Ok(Cow::Owned(s.into_owned()))
30    }
31}
32
33/// JSON-RPC request ID.
34#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
35#[serde(untagged)]
36pub enum RequestId {
37    /// Integer ID.
38    Number(i64),
39    /// String ID.
40    String(String),
41}
42
43impl From<i64> for RequestId {
44    fn from(id: i64) -> Self {
45        RequestId::Number(id)
46    }
47}
48
49impl From<String> for RequestId {
50    fn from(id: String) -> Self {
51        RequestId::String(id)
52    }
53}
54
55impl From<&str> for RequestId {
56    fn from(id: &str) -> Self {
57        RequestId::String(id.to_owned())
58    }
59}
60
61impl std::fmt::Display for RequestId {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        match self {
64            RequestId::Number(n) => write!(f, "{n}"),
65            RequestId::String(s) => write!(f, "{s}"),
66        }
67    }
68}
69
70/// JSON-RPC 2.0 request.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct JsonRpcRequest {
73    /// Protocol version (always "2.0").
74    #[serde(
75        serialize_with = "serialize_jsonrpc_version",
76        deserialize_with = "deserialize_jsonrpc_version"
77    )]
78    pub jsonrpc: Cow<'static, str>,
79    /// Method name.
80    pub method: String,
81    /// Request parameters.
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub params: Option<Value>,
84    /// Request ID (absent for notifications).
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub id: Option<RequestId>,
87}
88
89impl JsonRpcRequest {
90    /// Creates a new request with the given method and parameters.
91    #[must_use]
92    pub fn new(method: impl Into<String>, params: Option<Value>, id: impl Into<RequestId>) -> Self {
93        Self {
94            jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
95            method: method.into(),
96            params,
97            id: Some(id.into()),
98        }
99    }
100
101    /// Creates a notification (request without ID).
102    #[must_use]
103    pub fn notification(method: impl Into<String>, params: Option<Value>) -> Self {
104        Self {
105            jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
106            method: method.into(),
107            params,
108            id: None,
109        }
110    }
111
112    /// Returns true if this is a notification (no ID).
113    #[must_use]
114    pub fn is_notification(&self) -> bool {
115        self.id.is_none()
116    }
117}
118
119/// JSON-RPC 2.0 error object.
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct JsonRpcError {
122    /// Error code.
123    pub code: i32,
124    /// Error message.
125    pub message: String,
126    /// Additional error data.
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub data: Option<Value>,
129}
130
131impl From<fastmcp_core::McpError> for JsonRpcError {
132    fn from(err: fastmcp_core::McpError) -> Self {
133        Self {
134            code: err.code.into(),
135            message: err.message,
136            data: err.data,
137        }
138    }
139}
140
141/// JSON-RPC 2.0 response.
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct JsonRpcResponse {
144    /// Protocol version (always "2.0").
145    #[serde(
146        serialize_with = "serialize_jsonrpc_version",
147        deserialize_with = "deserialize_jsonrpc_version"
148    )]
149    pub jsonrpc: Cow<'static, str>,
150    /// Result (present on success).
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub result: Option<Value>,
153    /// Error (present on failure).
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub error: Option<JsonRpcError>,
156    /// Request ID this is responding to.
157    pub id: Option<RequestId>,
158}
159
160impl JsonRpcResponse {
161    /// Creates a success response.
162    #[must_use]
163    pub fn success(id: RequestId, result: Value) -> Self {
164        Self {
165            jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
166            result: Some(result),
167            error: None,
168            id: Some(id),
169        }
170    }
171
172    /// Creates an error response.
173    #[must_use]
174    pub fn error(id: Option<RequestId>, error: JsonRpcError) -> Self {
175        Self {
176            jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
177            result: None,
178            error: Some(error),
179            id,
180        }
181    }
182
183    /// Returns true if this is an error response.
184    #[must_use]
185    pub fn is_error(&self) -> bool {
186        self.error.is_some()
187    }
188}
189
190/// A JSON-RPC message (request, response, or notification).
191#[derive(Debug, Clone, Serialize, Deserialize)]
192#[serde(untagged)]
193pub enum JsonRpcMessage {
194    /// A request or notification.
195    Request(JsonRpcRequest),
196    /// A response.
197    Response(JsonRpcResponse),
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    use serde_json::json;
204
205    // ========================================================================
206    // RequestId Tests
207    // ========================================================================
208
209    #[test]
210    fn request_id_number_serialization() {
211        let id = RequestId::Number(42);
212        let value = serde_json::to_value(&id).expect("serialize");
213        assert_eq!(value, 42);
214    }
215
216    #[test]
217    fn request_id_string_serialization() {
218        let id = RequestId::String("req-1".to_string());
219        let value = serde_json::to_value(&id).expect("serialize");
220        assert_eq!(value, "req-1");
221    }
222
223    #[test]
224    fn request_id_number_deserialization() {
225        let id: RequestId = serde_json::from_value(json!(99)).expect("deserialize");
226        assert_eq!(id, RequestId::Number(99));
227    }
228
229    #[test]
230    fn request_id_string_deserialization() {
231        let id: RequestId = serde_json::from_value(json!("abc")).expect("deserialize");
232        assert_eq!(id, RequestId::String("abc".to_string()));
233    }
234
235    #[test]
236    fn request_id_from_i64() {
237        let id: RequestId = 7i64.into();
238        assert_eq!(id, RequestId::Number(7));
239    }
240
241    #[test]
242    fn request_id_from_string() {
243        let id: RequestId = "test-id".to_string().into();
244        assert_eq!(id, RequestId::String("test-id".to_string()));
245    }
246
247    #[test]
248    fn request_id_from_str() {
249        let id: RequestId = "test-id".into();
250        assert_eq!(id, RequestId::String("test-id".to_string()));
251    }
252
253    #[test]
254    fn request_id_display() {
255        assert_eq!(format!("{}", RequestId::Number(42)), "42");
256        assert_eq!(
257            format!("{}", RequestId::String("req-1".to_string())),
258            "req-1"
259        );
260    }
261
262    #[test]
263    fn request_id_equality() {
264        assert_eq!(RequestId::Number(1), RequestId::Number(1));
265        assert_ne!(RequestId::Number(1), RequestId::Number(2));
266        assert_eq!(
267            RequestId::String("a".to_string()),
268            RequestId::String("a".to_string())
269        );
270        assert_ne!(RequestId::Number(1), RequestId::String("1".to_string()));
271    }
272
273    // ========================================================================
274    // JsonRpcRequest Tests
275    // ========================================================================
276
277    #[test]
278    fn jsonrpc_version_deserialize_borrows_static_for_request() {
279        let req: JsonRpcRequest =
280            serde_json::from_str(r#"{"jsonrpc":"2.0","method":"tools/list","id":1}"#)
281                .expect("deserialize");
282        assert!(matches!(req.jsonrpc, Cow::Borrowed(JSONRPC_VERSION)));
283    }
284
285    #[test]
286    fn jsonrpc_version_deserialize_owns_nonstandard_for_request() {
287        let req: JsonRpcRequest =
288            serde_json::from_str(r#"{"jsonrpc":"2.1","method":"tools/list","id":1}"#)
289                .expect("deserialize");
290        let owned = match req.jsonrpc {
291            Cow::Owned(s) => Some(s),
292            Cow::Borrowed(_) => None,
293        };
294        assert_eq!(owned.as_deref(), Some("2.1"));
295    }
296
297    #[test]
298    fn request_serialization() {
299        let req = JsonRpcRequest::new("tools/list", None, 1i64);
300        let json = serde_json::to_string(&req).unwrap();
301        assert!(json.contains("\"jsonrpc\":\"2.0\""));
302        assert!(json.contains("\"method\":\"tools/list\""));
303        assert!(json.contains("\"id\":1"));
304    }
305
306    #[test]
307    fn request_with_params() {
308        let params = json!({"name": "greet", "arguments": {"name": "World"}});
309        let req = JsonRpcRequest::new("tools/call", Some(params.clone()), 2i64);
310        let value = serde_json::to_value(&req).expect("serialize");
311        assert_eq!(value["jsonrpc"], "2.0");
312        assert_eq!(value["method"], "tools/call");
313        assert_eq!(value["params"]["name"], "greet");
314        assert_eq!(value["id"], 2);
315    }
316
317    #[test]
318    fn request_without_params_omits_field() {
319        let req = JsonRpcRequest::new("tools/list", None, 1i64);
320        let value = serde_json::to_value(&req).expect("serialize");
321        assert!(value.get("params").is_none());
322    }
323
324    #[test]
325    fn notification_has_no_id() {
326        let notif = JsonRpcRequest::notification("notifications/progress", None);
327        assert!(notif.is_notification());
328        assert!(notif.id.is_none());
329        let value = serde_json::to_value(&notif).expect("serialize");
330        assert!(value.get("id").is_none());
331    }
332
333    #[test]
334    fn notification_with_params() {
335        let params = json!({"uri": "file://changed.txt"});
336        let notif = JsonRpcRequest::notification("notifications/resources/updated", Some(params));
337        assert!(notif.is_notification());
338        let value = serde_json::to_value(&notif).expect("serialize");
339        assert_eq!(value["params"]["uri"], "file://changed.txt");
340    }
341
342    #[test]
343    fn request_is_not_notification() {
344        let req = JsonRpcRequest::new("tools/list", None, 1i64);
345        assert!(!req.is_notification());
346    }
347
348    #[test]
349    fn request_with_string_id() {
350        let req = JsonRpcRequest::new("tools/list", None, "req-abc");
351        let value = serde_json::to_value(&req).expect("serialize");
352        assert_eq!(value["id"], "req-abc");
353    }
354
355    #[test]
356    fn request_round_trip() {
357        let original = JsonRpcRequest::new(
358            "tools/call",
359            Some(json!({"name": "add", "arguments": {"a": 1, "b": 2}})),
360            42i64,
361        );
362        let json_str = serde_json::to_string(&original).expect("serialize");
363        let deserialized: JsonRpcRequest = serde_json::from_str(&json_str).expect("deserialize");
364        assert_eq!(deserialized.method, "tools/call");
365        assert_eq!(deserialized.id, Some(RequestId::Number(42)));
366        assert!(deserialized.params.is_some());
367    }
368
369    // ========================================================================
370    // JsonRpcError Tests
371    // ========================================================================
372
373    #[test]
374    fn jsonrpc_error_from_mcp_error_preserves_code_message_and_data() {
375        let err = fastmcp_core::McpError::with_data(
376            fastmcp_core::McpErrorCode::InvalidParams,
377            "bad params",
378            json!({"field":"name"}),
379        );
380        let rpc_err: JsonRpcError = err.into();
381        assert_eq!(rpc_err.code, -32602);
382        assert_eq!(rpc_err.message, "bad params");
383        assert_eq!(rpc_err.data, Some(json!({"field":"name"})));
384    }
385
386    #[test]
387    fn jsonrpc_error_serialization() {
388        let error = JsonRpcError {
389            code: -32600,
390            message: "Invalid Request".to_string(),
391            data: None,
392        };
393        let value = serde_json::to_value(&error).expect("serialize");
394        assert_eq!(value["code"], -32600);
395        assert_eq!(value["message"], "Invalid Request");
396        assert!(value.get("data").is_none());
397    }
398
399    #[test]
400    fn jsonrpc_error_with_data() {
401        let error = JsonRpcError {
402            code: -32602,
403            message: "Invalid params".to_string(),
404            data: Some(json!({"field": "name", "reason": "required"})),
405        };
406        let value = serde_json::to_value(&error).expect("serialize");
407        assert_eq!(value["code"], -32602);
408        assert_eq!(value["data"]["field"], "name");
409    }
410
411    #[test]
412    fn jsonrpc_error_standard_codes() {
413        // Parse error
414        let err = JsonRpcError {
415            code: -32700,
416            message: "Parse error".to_string(),
417            data: None,
418        };
419        assert_eq!(serde_json::to_value(&err).unwrap()["code"], -32700);
420
421        // Method not found
422        let err = JsonRpcError {
423            code: -32601,
424            message: "Method not found".to_string(),
425            data: None,
426        };
427        assert_eq!(serde_json::to_value(&err).unwrap()["code"], -32601);
428
429        // Internal error
430        let err = JsonRpcError {
431            code: -32603,
432            message: "Internal error".to_string(),
433            data: None,
434        };
435        assert_eq!(serde_json::to_value(&err).unwrap()["code"], -32603);
436    }
437
438    // ========================================================================
439    // JsonRpcResponse Tests
440    // ========================================================================
441
442    #[test]
443    fn jsonrpc_version_deserialize_borrows_static_for_response() {
444        let resp: JsonRpcResponse =
445            serde_json::from_str(r#"{"jsonrpc":"2.0","result":{"tools":[]},"id":1}"#)
446                .expect("deserialize");
447        assert!(matches!(resp.jsonrpc, Cow::Borrowed(JSONRPC_VERSION)));
448    }
449
450    #[test]
451    fn jsonrpc_version_deserialize_owns_nonstandard_for_response() {
452        let resp: JsonRpcResponse =
453            serde_json::from_str(r#"{"jsonrpc":"2.1","result":{"tools":[]},"id":1}"#)
454                .expect("deserialize");
455        let owned = match resp.jsonrpc {
456            Cow::Owned(s) => Some(s),
457            Cow::Borrowed(_) => None,
458        };
459        assert_eq!(owned.as_deref(), Some("2.1"));
460    }
461
462    #[test]
463    fn response_success() {
464        let resp = JsonRpcResponse::success(RequestId::Number(1), json!({"result": "ok"}));
465        let value = serde_json::to_value(&resp).expect("serialize");
466        assert_eq!(value["jsonrpc"], "2.0");
467        assert_eq!(value["result"]["result"], "ok");
468        assert_eq!(value["id"], 1);
469        assert!(value.get("error").is_none());
470        assert!(!resp.is_error());
471    }
472
473    #[test]
474    fn response_error() {
475        let error = JsonRpcError {
476            code: -32601,
477            message: "Method not found".to_string(),
478            data: None,
479        };
480        let resp = JsonRpcResponse::error(Some(RequestId::Number(1)), error);
481        let value = serde_json::to_value(&resp).expect("serialize");
482        assert_eq!(value["jsonrpc"], "2.0");
483        assert!(value.get("result").is_none());
484        assert_eq!(value["error"]["code"], -32601);
485        assert_eq!(value["error"]["message"], "Method not found");
486        assert_eq!(value["id"], 1);
487        assert!(resp.is_error());
488    }
489
490    #[test]
491    fn response_error_null_id() {
492        let error = JsonRpcError {
493            code: -32700,
494            message: "Parse error".to_string(),
495            data: None,
496        };
497        let resp = JsonRpcResponse::error(None, error);
498        let value = serde_json::to_value(&resp).expect("serialize");
499        assert!(value["id"].is_null());
500    }
501
502    #[test]
503    fn response_round_trip() {
504        let original =
505            JsonRpcResponse::success(RequestId::String("abc".to_string()), json!({"tools": []}));
506        let json_str = serde_json::to_string(&original).expect("serialize");
507        let deserialized: JsonRpcResponse = serde_json::from_str(&json_str).expect("deserialize");
508        assert!(!deserialized.is_error());
509        assert!(deserialized.result.is_some());
510        assert_eq!(deserialized.id, Some(RequestId::String("abc".to_string())));
511    }
512
513    // ========================================================================
514    // JsonRpcMessage Tests
515    // ========================================================================
516
517    #[test]
518    fn message_request_variant() {
519        let req = JsonRpcRequest::new("tools/list", None, 1i64);
520        let msg = JsonRpcMessage::Request(req);
521        let value = serde_json::to_value(&msg).expect("serialize");
522        assert_eq!(value["method"], "tools/list");
523    }
524
525    #[test]
526    fn message_response_variant() {
527        let resp = JsonRpcResponse::success(RequestId::Number(1), json!("ok"));
528        let msg = JsonRpcMessage::Response(resp);
529        let value = serde_json::to_value(&msg).expect("serialize");
530        assert_eq!(value["result"], "ok");
531    }
532
533    #[test]
534    fn message_deserialize_as_request() {
535        let json_str = r#"{"jsonrpc":"2.0","method":"tools/list","id":1}"#;
536        let msg: JsonRpcMessage = serde_json::from_str(json_str).expect("deserialize");
537        let (method, id) = match msg {
538            JsonRpcMessage::Request(req) => (req.method, req.id),
539            JsonRpcMessage::Response(_) => (String::new(), None),
540        };
541        assert_eq!(method, "tools/list");
542        assert_eq!(id, Some(RequestId::Number(1)));
543    }
544
545    #[test]
546    fn message_deserialize_as_response() {
547        let json_str = r#"{"jsonrpc":"2.0","result":{"tools":[]},"id":1}"#;
548        let msg: JsonRpcMessage = serde_json::from_str(json_str).expect("deserialize");
549        let (is_error, id) = match msg {
550            JsonRpcMessage::Response(resp) => (resp.is_error(), resp.id),
551            JsonRpcMessage::Request(_) => (true, None),
552        };
553        assert!(!is_error);
554        assert_eq!(id, Some(RequestId::Number(1)));
555    }
556
557    // ========================================================================
558    // JSONRPC_VERSION constant test
559    // ========================================================================
560
561    #[test]
562    fn jsonrpc_version_constant() {
563        assert_eq!(JSONRPC_VERSION, "2.0");
564    }
565}