Skip to main content

dcp/compat/
json_rpc.rs

1//! JSON-RPC 2.0 parser for MCP compatibility.
2//!
3//! Provides parsing and formatting of JSON-RPC 2.0 messages for
4//! translation between MCP and DCP protocols.
5
6use serde::de::{self, MapAccess, SeqAccess, Visitor};
7use serde::{Deserialize, Deserializer, Serialize};
8use serde_json::{Map, Value};
9use std::collections::HashSet;
10use std::fmt;
11use thiserror::Error;
12
13use crate::security::{sanitize_json_value, sanitize_text};
14
15/// JSON-RPC 2.0 version string
16pub const JSONRPC_VERSION: &str = "2.0";
17
18/// Default maximum JSON-RPC request size for adapter entry points.
19pub const DEFAULT_MAX_JSONRPC_REQUEST_SIZE: usize = 10 * 1024 * 1024;
20
21/// Maximum accepted byte length for string request/response ids.
22pub const MAX_JSONRPC_ID_STRING_BYTES: usize = 256;
23
24/// JSON-RPC parsing errors
25#[derive(Debug, Clone, Error, PartialEq, Eq)]
26pub enum JsonRpcParseError {
27    #[error("invalid JSON: {0}")]
28    InvalidJson(String),
29    #[error("missing jsonrpc field")]
30    MissingVersion,
31    #[error("invalid jsonrpc version: expected 2.0")]
32    InvalidVersion,
33    #[error("missing method field")]
34    MissingMethod,
35    #[error("missing id field")]
36    MissingId,
37    #[error("invalid request structure")]
38    InvalidStructure,
39    #[error("request too large")]
40    RequestTooLarge,
41    #[error("request id too large")]
42    RequestIdTooLarge,
43    #[error("request id contains sensitive data")]
44    RequestIdSensitive,
45    #[error("duplicate JSON object field")]
46    DuplicateField,
47    #[error("JSON-RPC batch requests are unsupported")]
48    BatchUnsupported,
49}
50
51const DUPLICATE_FIELD_MESSAGE: &str = "duplicate JSON object field";
52const REQUEST_ENVELOPE_FIELDS: &[&str] = &["jsonrpc", "method", "params", "id"];
53const RESPONSE_ENVELOPE_FIELDS: &[&str] = &["jsonrpc", "result", "error", "id"];
54
55struct DuplicateKeyDetector;
56
57impl<'de> Deserialize<'de> for DuplicateKeyDetector {
58    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
59    where
60        D: Deserializer<'de>,
61    {
62        deserializer.deserialize_any(DuplicateKeyVisitor)
63    }
64}
65
66struct DuplicateKeyVisitor;
67
68impl<'de> Visitor<'de> for DuplicateKeyVisitor {
69    type Value = DuplicateKeyDetector;
70
71    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
72        formatter.write_str("any JSON value without duplicate object fields")
73    }
74
75    fn visit_bool<E>(self, _value: bool) -> Result<Self::Value, E> {
76        Ok(DuplicateKeyDetector)
77    }
78
79    fn visit_i64<E>(self, _value: i64) -> Result<Self::Value, E> {
80        Ok(DuplicateKeyDetector)
81    }
82
83    fn visit_u64<E>(self, _value: u64) -> Result<Self::Value, E> {
84        Ok(DuplicateKeyDetector)
85    }
86
87    fn visit_f64<E>(self, _value: f64) -> Result<Self::Value, E> {
88        Ok(DuplicateKeyDetector)
89    }
90
91    fn visit_str<E>(self, _value: &str) -> Result<Self::Value, E>
92    where
93        E: de::Error,
94    {
95        Ok(DuplicateKeyDetector)
96    }
97
98    fn visit_string<E>(self, _value: String) -> Result<Self::Value, E> {
99        Ok(DuplicateKeyDetector)
100    }
101
102    fn visit_none<E>(self) -> Result<Self::Value, E> {
103        Ok(DuplicateKeyDetector)
104    }
105
106    fn visit_unit<E>(self) -> Result<Self::Value, E> {
107        Ok(DuplicateKeyDetector)
108    }
109
110    fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
111    where
112        D: Deserializer<'de>,
113    {
114        DuplicateKeyDetector::deserialize(deserializer)
115    }
116
117    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
118    where
119        A: SeqAccess<'de>,
120    {
121        while seq.next_element::<DuplicateKeyDetector>()?.is_some() {}
122        Ok(DuplicateKeyDetector)
123    }
124
125    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
126    where
127        A: MapAccess<'de>,
128    {
129        let mut seen = HashSet::new();
130        while let Some(key) = map.next_key::<String>()? {
131            if !seen.insert(key) {
132                return Err(de::Error::custom(DUPLICATE_FIELD_MESSAGE));
133            }
134            map.next_value::<DuplicateKeyDetector>()?;
135        }
136        Ok(DuplicateKeyDetector)
137    }
138}
139
140fn map_json_error(error: serde_json::Error) -> JsonRpcParseError {
141    if error.to_string().contains(DUPLICATE_FIELD_MESSAGE) {
142        JsonRpcParseError::DuplicateField
143    } else {
144        JsonRpcParseError::InvalidJson(error.to_string())
145    }
146}
147
148fn ensure_no_duplicate_object_fields(json: &str) -> Result<(), JsonRpcParseError> {
149    let mut deserializer = serde_json::Deserializer::from_str(json);
150    DuplicateKeyDetector::deserialize(&mut deserializer).map_err(map_json_error)?;
151    deserializer.end().map_err(map_json_error)
152}
153
154fn ensure_only_known_fields(
155    obj: &Map<String, Value>,
156    allowed: &[&str],
157) -> Result<(), JsonRpcParseError> {
158    if obj.keys().any(|key| !allowed.contains(&key.as_str())) {
159        return Err(JsonRpcParseError::InvalidStructure);
160    }
161
162    Ok(())
163}
164
165fn validate_request_id(id: &RequestId) -> Result<(), JsonRpcParseError> {
166    if let RequestId::String(id) = id {
167        if id.len() > MAX_JSONRPC_ID_STRING_BYTES {
168            return Err(JsonRpcParseError::RequestIdTooLarge);
169        }
170        if sanitize_text(id) != *id {
171            return Err(JsonRpcParseError::RequestIdSensitive);
172        }
173    }
174
175    Ok(())
176}
177
178fn validate_method_name(method: &str) -> Result<(), JsonRpcParseError> {
179    if method.is_empty() || method.starts_with("rpc.") || method.chars().any(char::is_control) {
180        return Err(JsonRpcParseError::InvalidStructure);
181    }
182
183    Ok(())
184}
185
186fn validate_response_shape(response: &JsonRpcResponse) -> Result<(), JsonRpcParseError> {
187    if response.jsonrpc != JSONRPC_VERSION {
188        return Err(JsonRpcParseError::InvalidVersion);
189    }
190
191    if matches!(response.id, RequestId::Missing) {
192        return Err(JsonRpcParseError::MissingId);
193    }
194
195    if response.result.is_some() == response.error.is_some() {
196        return Err(JsonRpcParseError::InvalidStructure);
197    }
198
199    validate_request_id(&response.id)
200}
201
202fn parse_request_id_value(
203    value: Option<&Value>,
204    missing_id: RequestId,
205) -> Result<RequestId, JsonRpcParseError> {
206    let id = match value {
207        Some(Value::String(s)) => RequestId::String(s.clone()),
208        Some(Value::Number(n)) => {
209            if let Some(i) = n.as_i64() {
210                RequestId::Number(i)
211            } else {
212                return Err(JsonRpcParseError::InvalidStructure);
213            }
214        }
215        Some(Value::Null) => RequestId::Null,
216        None => missing_id,
217        _ => return Err(JsonRpcParseError::InvalidStructure),
218    };
219
220    validate_request_id(&id)?;
221    Ok(id)
222}
223
224/// JSON-RPC request ID (can be string, number, or null)
225#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
226#[serde(untagged)]
227pub enum RequestId {
228    String(String),
229    Number(i64),
230    Null,
231    #[default]
232    Missing,
233}
234
235impl RequestId {
236    /// Parse and validate a JSON-RPC request id from a JSON value.
237    pub fn try_from_json_value(value: &Value) -> Result<Self, JsonRpcParseError> {
238        parse_request_id_value(Some(value), RequestId::Missing)
239    }
240}
241
242/// JSON-RPC 2.0 request
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct JsonRpcRequest {
245    /// JSON-RPC version (must be "2.0")
246    pub jsonrpc: String,
247    /// Method name
248    pub method: String,
249    /// Request parameters (optional)
250    #[serde(default, skip_serializing_if = "Option::is_none")]
251    pub params: Option<Value>,
252    /// Request ID (optional for notifications)
253    #[serde(default, skip_serializing_if = "is_missing_id")]
254    pub id: RequestId,
255}
256
257fn is_missing_id(id: &RequestId) -> bool {
258    matches!(id, RequestId::Missing)
259}
260
261impl JsonRpcRequest {
262    /// Create a new request
263    pub fn new(method: impl Into<String>, params: Option<Value>, id: RequestId) -> Self {
264        Self {
265            jsonrpc: JSONRPC_VERSION.to_string(),
266            method: method.into(),
267            params,
268            id,
269        }
270    }
271
272    /// Create a notification (no id)
273    pub fn notification(method: impl Into<String>, params: Option<Value>) -> Self {
274        Self {
275            jsonrpc: JSONRPC_VERSION.to_string(),
276            method: method.into(),
277            params,
278            id: RequestId::Missing,
279        }
280    }
281
282    /// Check if this is a notification (no id)
283    pub fn is_notification(&self) -> bool {
284        matches!(self.id, RequestId::Missing)
285    }
286
287    /// Get params as a specific type
288    pub fn params_as<T: for<'de> Deserialize<'de>>(&self) -> Option<T> {
289        self.params
290            .as_ref()
291            .and_then(|p| serde_json::from_value(p.clone()).ok())
292    }
293}
294
295/// JSON-RPC 2.0 response
296#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct JsonRpcResponse {
298    /// JSON-RPC version (must be "2.0")
299    pub jsonrpc: String,
300    /// Result (present on success)
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub result: Option<Value>,
303    /// Error (present on failure)
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub error: Option<JsonRpcError>,
306    /// Request ID
307    pub id: RequestId,
308}
309
310impl JsonRpcResponse {
311    /// Create a success response
312    pub fn success(id: RequestId, result: Value) -> Self {
313        Self {
314            jsonrpc: JSONRPC_VERSION.to_string(),
315            result: Some(result),
316            error: None,
317            id,
318        }
319    }
320
321    /// Create an error response
322    pub fn error(id: RequestId, error: JsonRpcError) -> Self {
323        Self {
324            jsonrpc: JSONRPC_VERSION.to_string(),
325            result: None,
326            error: Some(error),
327            id,
328        }
329    }
330
331    /// Check if this is a success response
332    pub fn is_success(&self) -> bool {
333        self.result.is_some() && self.error.is_none()
334    }
335
336    /// Check if this is an error response
337    pub fn is_error(&self) -> bool {
338        self.error.is_some()
339    }
340}
341
342/// JSON-RPC 2.0 error object
343#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
344pub struct JsonRpcError {
345    /// Error code
346    pub code: i32,
347    /// Error message
348    pub message: String,
349    /// Additional error data (optional)
350    #[serde(skip_serializing_if = "Option::is_none")]
351    pub data: Option<Value>,
352}
353
354impl JsonRpcError {
355    /// Create a new error
356    pub fn new(code: i32, message: impl Into<String>) -> Self {
357        Self {
358            code,
359            message: sanitize_text(&message.into()),
360            data: None,
361        }
362    }
363
364    /// Create an error with data
365    pub fn with_data(code: i32, message: impl Into<String>, data: Value) -> Self {
366        Self {
367            code,
368            message: sanitize_text(&message.into()),
369            data: Some(sanitize_json_value(&data)),
370        }
371    }
372
373    // Standard JSON-RPC error codes
374
375    /// Parse error (-32700)
376    pub fn parse_error() -> Self {
377        Self::new(-32700, "Parse error")
378    }
379
380    /// Invalid request (-32600)
381    pub fn invalid_request() -> Self {
382        Self::new(-32600, "Invalid Request")
383    }
384
385    /// Method not found (-32601)
386    pub fn method_not_found() -> Self {
387        Self::new(-32601, "Method not found")
388    }
389
390    /// Invalid params (-32602)
391    pub fn invalid_params() -> Self {
392        Self::new(-32602, "Invalid params")
393    }
394
395    /// Internal error (-32603)
396    pub fn internal_error() -> Self {
397        Self::new(-32603, "Internal error")
398    }
399}
400
401/// JSON-RPC parser
402pub struct JsonRpcParser;
403
404impl JsonRpcParser {
405    /// Parse a JSON-RPC request from a string
406    pub fn parse_request(json: &str) -> Result<JsonRpcRequest, JsonRpcParseError> {
407        Self::parse_request_with_limit(json, DEFAULT_MAX_JSONRPC_REQUEST_SIZE)
408    }
409
410    /// Parse a JSON-RPC request from a string with a maximum byte size.
411    pub fn parse_request_with_limit(
412        json: &str,
413        max_size: usize,
414    ) -> Result<JsonRpcRequest, JsonRpcParseError> {
415        if json.len() > max_size {
416            return Err(JsonRpcParseError::RequestTooLarge);
417        }
418
419        ensure_no_duplicate_object_fields(json)?;
420
421        let value: Value = serde_json::from_str(json).map_err(map_json_error)?;
422
423        Self::parse_request_value(&value)
424    }
425
426    /// Parse a JSON-RPC request from a Value
427    pub fn parse_request_value(value: &Value) -> Result<JsonRpcRequest, JsonRpcParseError> {
428        if value.is_array() {
429            return Err(JsonRpcParseError::BatchUnsupported);
430        }
431
432        let obj = value
433            .as_object()
434            .ok_or(JsonRpcParseError::InvalidStructure)?;
435        ensure_only_known_fields(obj, REQUEST_ENVELOPE_FIELDS)?;
436
437        // Check jsonrpc version
438        let version = obj
439            .get("jsonrpc")
440            .and_then(|v| v.as_str())
441            .ok_or(JsonRpcParseError::MissingVersion)?;
442
443        if version != JSONRPC_VERSION {
444            return Err(JsonRpcParseError::InvalidVersion);
445        }
446
447        // Get method
448        let method = obj
449            .get("method")
450            .and_then(|v| v.as_str())
451            .ok_or(JsonRpcParseError::MissingMethod)?
452            .to_string();
453
454        validate_method_name(&method)?;
455
456        if obj.contains_key("result") || obj.contains_key("error") {
457            return Err(JsonRpcParseError::InvalidStructure);
458        }
459
460        // Get params (optional)
461        let params = obj.get("params").cloned();
462        if let Some(params) = &params {
463            if !(params.is_object() || params.is_array()) {
464                return Err(JsonRpcParseError::InvalidStructure);
465            }
466        }
467
468        // Get id (optional for notifications)
469        let id = parse_request_id_value(obj.get("id"), RequestId::Missing)?;
470
471        Ok(JsonRpcRequest {
472            jsonrpc: JSONRPC_VERSION.to_string(),
473            method,
474            params,
475            id,
476        })
477    }
478
479    /// Parse a JSON-RPC response from a string
480    pub fn parse_response(json: &str) -> Result<JsonRpcResponse, JsonRpcParseError> {
481        Self::parse_response_with_limit(json, DEFAULT_MAX_JSONRPC_REQUEST_SIZE)
482    }
483
484    /// Parse a JSON-RPC response from a string with a maximum byte size.
485    pub fn parse_response_with_limit(
486        json: &str,
487        max_size: usize,
488    ) -> Result<JsonRpcResponse, JsonRpcParseError> {
489        if json.len() > max_size {
490            return Err(JsonRpcParseError::RequestTooLarge);
491        }
492
493        ensure_no_duplicate_object_fields(json)?;
494
495        let value: Value = serde_json::from_str(json).map_err(map_json_error)?;
496
497        Self::parse_response_value(&value)
498    }
499
500    /// Parse a JSON-RPC response from a Value
501    pub fn parse_response_value(value: &Value) -> Result<JsonRpcResponse, JsonRpcParseError> {
502        let obj = value
503            .as_object()
504            .ok_or(JsonRpcParseError::InvalidStructure)?;
505        ensure_only_known_fields(obj, RESPONSE_ENVELOPE_FIELDS)?;
506
507        // Check jsonrpc version
508        let version = obj
509            .get("jsonrpc")
510            .and_then(|v| v.as_str())
511            .ok_or(JsonRpcParseError::MissingVersion)?;
512
513        if version != JSONRPC_VERSION {
514            return Err(JsonRpcParseError::InvalidVersion);
515        }
516
517        if obj.contains_key("method") || obj.contains_key("params") {
518            return Err(JsonRpcParseError::InvalidStructure);
519        }
520
521        // Get id
522        let id = parse_request_id_value(obj.get("id"), RequestId::Missing)?;
523        if matches!(id, RequestId::Missing) {
524            return Err(JsonRpcParseError::MissingId);
525        }
526
527        // Get result or error
528        let result = obj.get("result").cloned();
529        let error = obj
530            .get("error")
531            .map(|e| serde_json::from_value(e.clone()))
532            .transpose()
533            .map_err(|_| JsonRpcParseError::InvalidStructure)?;
534
535        if result.is_some() == error.is_some() {
536            return Err(JsonRpcParseError::InvalidStructure);
537        }
538
539        Ok(JsonRpcResponse {
540            jsonrpc: JSONRPC_VERSION.to_string(),
541            result,
542            error,
543            id,
544        })
545    }
546
547    /// Format a request to JSON string
548    pub fn format_request(request: &JsonRpcRequest) -> Result<String, JsonRpcParseError> {
549        if request.jsonrpc != JSONRPC_VERSION {
550            return Err(JsonRpcParseError::InvalidVersion);
551        }
552        validate_method_name(&request.method)?;
553        validate_request_id(&request.id)?;
554        serde_json::to_string(request).map_err(|e| JsonRpcParseError::InvalidJson(e.to_string()))
555    }
556
557    /// Format a response to JSON string
558    pub fn format_response(response: &JsonRpcResponse) -> Result<String, JsonRpcParseError> {
559        validate_response_shape(response)?;
560        let mut response = response.clone();
561        if let Some(error) = &mut response.error {
562            error.message = sanitize_text(&error.message);
563            error.data = error.data.as_ref().map(sanitize_json_value);
564        }
565
566        serde_json::to_string(&response).map_err(|e| JsonRpcParseError::InvalidJson(e.to_string()))
567    }
568}
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573
574    #[test]
575    fn test_parse_request() {
576        let json = r#"{"jsonrpc":"2.0","method":"test","params":{"foo":"bar"},"id":1}"#;
577        let request = JsonRpcParser::parse_request(json).unwrap();
578
579        assert_eq!(request.jsonrpc, "2.0");
580        assert_eq!(request.method, "test");
581        assert_eq!(request.id, RequestId::Number(1));
582        assert!(request.params.is_some());
583    }
584
585    #[test]
586    fn test_parse_notification() {
587        let json = r#"{"jsonrpc":"2.0","method":"notify","params":{}}"#;
588        let request = JsonRpcParser::parse_request(json).unwrap();
589
590        assert_eq!(request.method, "notify");
591        assert!(request.is_notification());
592    }
593
594    #[test]
595    fn test_parse_request_string_id() {
596        let json = r#"{"jsonrpc":"2.0","method":"test","id":"abc-123"}"#;
597        let request = JsonRpcParser::parse_request(json).unwrap();
598
599        assert_eq!(request.id, RequestId::String("abc-123".to_string()));
600    }
601
602    #[test]
603    fn test_parse_invalid_version() {
604        let json = r#"{"jsonrpc":"1.0","method":"test","id":1}"#;
605        let result = JsonRpcParser::parse_request(json);
606
607        assert!(matches!(result, Err(JsonRpcParseError::InvalidVersion)));
608    }
609
610    #[test]
611    fn test_parse_missing_method() {
612        let json = r#"{"jsonrpc":"2.0","id":1}"#;
613        let result = JsonRpcParser::parse_request(json);
614
615        assert!(matches!(result, Err(JsonRpcParseError::MissingMethod)));
616    }
617
618    #[test]
619    fn test_format_request() {
620        let request = JsonRpcRequest::new(
621            "test",
622            Some(serde_json::json!({"a": 1})),
623            RequestId::Number(42),
624        );
625        let json = JsonRpcParser::format_request(&request).unwrap();
626
627        // Parse it back
628        let parsed = JsonRpcParser::parse_request(&json).unwrap();
629        assert_eq!(parsed.method, "test");
630        assert_eq!(parsed.id, RequestId::Number(42));
631    }
632
633    #[test]
634    fn test_success_response() {
635        let response =
636            JsonRpcResponse::success(RequestId::Number(1), serde_json::json!({"result": "ok"}));
637
638        assert!(response.is_success());
639        assert!(!response.is_error());
640
641        let json = JsonRpcParser::format_response(&response).unwrap();
642        let parsed = JsonRpcParser::parse_response(&json).unwrap();
643
644        assert!(parsed.is_success());
645        assert_eq!(parsed.id, RequestId::Number(1));
646    }
647
648    #[test]
649    fn test_error_response() {
650        let response =
651            JsonRpcResponse::error(RequestId::Number(1), JsonRpcError::method_not_found());
652
653        assert!(response.is_error());
654        assert!(!response.is_success());
655
656        let json = JsonRpcParser::format_response(&response).unwrap();
657        let parsed = JsonRpcParser::parse_response(&json).unwrap();
658
659        assert!(parsed.is_error());
660        assert_eq!(parsed.error.unwrap().code, -32601);
661    }
662
663    #[test]
664    fn test_error_codes() {
665        assert_eq!(JsonRpcError::parse_error().code, -32700);
666        assert_eq!(JsonRpcError::invalid_request().code, -32600);
667        assert_eq!(JsonRpcError::method_not_found().code, -32601);
668        assert_eq!(JsonRpcError::invalid_params().code, -32602);
669        assert_eq!(JsonRpcError::internal_error().code, -32603);
670    }
671
672    #[test]
673    fn test_request_round_trip() {
674        let original = JsonRpcRequest::new(
675            "tools/call",
676            Some(serde_json::json!({
677                "name": "read_file",
678                "arguments": {"path": "/tmp/test.txt"}
679            })),
680            RequestId::String("req-001".to_string()),
681        );
682
683        let json = JsonRpcParser::format_request(&original).unwrap();
684        let parsed = JsonRpcParser::parse_request(&json).unwrap();
685
686        assert_eq!(parsed.method, original.method);
687        assert_eq!(parsed.id, original.id);
688        assert_eq!(parsed.params, original.params);
689    }
690
691    #[test]
692    fn test_response_round_trip() {
693        let original = JsonRpcResponse::success(
694            RequestId::Number(123),
695            serde_json::json!({
696                "content": [{"type": "text", "text": "Hello"}]
697            }),
698        );
699
700        let json = JsonRpcParser::format_response(&original).unwrap();
701        let parsed = JsonRpcParser::parse_response(&json).unwrap();
702
703        assert_eq!(parsed.id, original.id);
704        assert_eq!(parsed.result, original.result);
705    }
706}