Skip to main content

agentic_codebase/mcp/
protocol.rs

1//! JSON-RPC 2.0 protocol types for the MCP server.
2//!
3//! Defines the wire format for JSON-RPC messages used in the
4//! Model Context Protocol server implementation.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// JSON-RPC 2.0 request message.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct JsonRpcRequest {
12    /// Must be "2.0".
13    pub jsonrpc: String,
14    /// Request ID (number or string). Absent for notifications.
15    #[serde(default)]
16    pub id: Option<Value>,
17    /// Method name.
18    pub method: String,
19    /// Method parameters (optional).
20    #[serde(default)]
21    pub params: Value,
22}
23
24impl JsonRpcRequest {
25    /// Create a new JSON-RPC request.
26    pub fn new(id: impl Into<Value>, method: impl Into<String>) -> Self {
27        Self {
28            jsonrpc: "2.0".to_string(),
29            id: Some(id.into()),
30            method: method.into(),
31            params: Value::Null,
32        }
33    }
34
35    /// Create a new JSON-RPC request with parameters.
36    pub fn with_params(id: impl Into<Value>, method: impl Into<String>, params: Value) -> Self {
37        Self {
38            jsonrpc: "2.0".to_string(),
39            id: Some(id.into()),
40            method: method.into(),
41            params,
42        }
43    }
44}
45
46/// JSON-RPC 2.0 error object.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct JsonRpcError {
49    /// Error code.
50    pub code: i32,
51    /// Error message.
52    pub message: String,
53    /// Additional error data (optional).
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub data: Option<Value>,
56}
57
58impl JsonRpcError {
59    /// Standard error: Parse error (-32700).
60    pub fn parse_error(detail: impl Into<String>) -> Self {
61        Self {
62            code: -32700,
63            message: "Parse error".to_string(),
64            data: Some(Value::String(detail.into())),
65        }
66    }
67
68    /// Standard error: Invalid request (-32600).
69    pub fn invalid_request(detail: impl Into<String>) -> Self {
70        Self {
71            code: -32600,
72            message: "Invalid Request".to_string(),
73            data: Some(Value::String(detail.into())),
74        }
75    }
76
77    /// Standard error: Method not found (-32601).
78    pub fn method_not_found(method: impl Into<String>) -> Self {
79        Self {
80            code: -32601,
81            message: "Method not found".to_string(),
82            data: Some(Value::String(method.into())),
83        }
84    }
85
86    /// MCP error: Tool not found (-32803).
87    pub fn tool_not_found(tool: impl Into<String>) -> Self {
88        Self {
89            code: -32803,
90            message: "Tool not found".to_string(),
91            data: Some(Value::String(tool.into())),
92        }
93    }
94
95    /// Standard error: Invalid params (-32602).
96    pub fn invalid_params(detail: impl Into<String>) -> Self {
97        Self {
98            code: -32602,
99            message: "Invalid params".to_string(),
100            data: Some(Value::String(detail.into())),
101        }
102    }
103
104    /// Standard error: Internal error (-32603).
105    pub fn internal_error(detail: impl Into<String>) -> Self {
106        Self {
107            code: -32603,
108            message: "Internal error".to_string(),
109            data: Some(Value::String(detail.into())),
110        }
111    }
112}
113
114/// JSON-RPC 2.0 response message.
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct JsonRpcResponse {
117    /// Must be "2.0".
118    pub jsonrpc: String,
119    /// Must match the request ID.
120    pub id: Value,
121    /// The result (present on success).
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub result: Option<Value>,
124    /// The error (present on failure).
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub error: Option<JsonRpcError>,
127}
128
129impl JsonRpcResponse {
130    /// Create a success response.
131    pub fn success(id: Value, result: Value) -> Self {
132        Self {
133            jsonrpc: "2.0".to_string(),
134            id,
135            result: Some(result),
136            error: None,
137        }
138    }
139
140    /// Create an error response.
141    pub fn error(id: Value, error: JsonRpcError) -> Self {
142        Self {
143            jsonrpc: "2.0".to_string(),
144            id,
145            result: None,
146            error: Some(error),
147        }
148    }
149
150    /// Create a tool error response (isError: true per MCP spec).
151    /// Use this for tool execution failures instead of `error()`.
152    pub fn tool_error(id: Value, message: impl Into<String>) -> Self {
153        Self::success(
154            id,
155            serde_json::json!({
156                "content": [{"type": "text", "text": message.into()}],
157                "isError": true
158            }),
159        )
160    }
161
162    /// If this is a JSON-RPC error response, convert it to a tool error
163    /// (isError: true) per MCP spec. Protocol errors pass through unchanged.
164    pub fn into_tool_error_if_needed(self) -> Self {
165        if let Some(ref err) = self.error {
166            // Only convert tool execution errors, not protocol-level errors.
167            // Protocol errors: parse (-32700), invalid request (-32600),
168            // method not found (-32601), tool not found (-32803).
169            match err.code {
170                -32700 | -32600 | -32601 | -32803 => self, // keep as JSON-RPC error
171                _ => {
172                    // Convert to isError: true
173                    let msg = if let Some(ref data) = err.data {
174                        format!("{}: {}", err.message, data)
175                    } else {
176                        err.message.clone()
177                    };
178                    Self::tool_error(self.id.clone(), msg)
179                }
180            }
181        } else {
182            self
183        }
184    }
185}
186
187/// Parse a raw JSON string into a JSON-RPC request.
188///
189/// Returns an error response if parsing fails.
190#[allow(clippy::result_large_err)]
191pub fn parse_request(raw: &str) -> Result<JsonRpcRequest, JsonRpcResponse> {
192    let value: Value = serde_json::from_str(raw).map_err(|e| {
193        JsonRpcResponse::error(Value::Null, JsonRpcError::parse_error(e.to_string()))
194    })?;
195
196    let request: JsonRpcRequest = serde_json::from_value(value).map_err(|e| {
197        JsonRpcResponse::error(Value::Null, JsonRpcError::invalid_request(e.to_string()))
198    })?;
199
200    if request.jsonrpc != "2.0" {
201        return Err(JsonRpcResponse::error(
202            request.id.unwrap_or(Value::Null),
203            JsonRpcError::invalid_request("jsonrpc must be \"2.0\""),
204        ));
205    }
206
207    Ok(request)
208}