ubl_mcp/
protocol.rs

1//! JSON-RPC 2.0 protocol types for MCP.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// JSON-RPC 2.0 request/response ID.
7#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
8#[serde(untagged)]
9pub enum RequestId {
10    /// String ID
11    String(String),
12    /// Numeric ID
13    Number(i64),
14}
15
16impl From<i64> for RequestId {
17    fn from(n: i64) -> Self {
18        Self::Number(n)
19    }
20}
21
22impl From<String> for RequestId {
23    fn from(s: String) -> Self {
24        Self::String(s)
25    }
26}
27
28/// JSON-RPC 2.0 error object.
29#[derive(Clone, Debug, Serialize, Deserialize)]
30pub struct JsonRpcError {
31    /// Error code
32    pub code: i32,
33    /// Error message
34    pub message: String,
35    /// Optional additional data
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub data: Option<Value>,
38}
39
40impl JsonRpcError {
41    /// Parse error (-32700)
42    pub fn parse_error(msg: impl Into<String>) -> Self {
43        Self {
44            code: -32700,
45            message: msg.into(),
46            data: None,
47        }
48    }
49
50    /// Invalid request (-32600)
51    pub fn invalid_request(msg: impl Into<String>) -> Self {
52        Self {
53            code: -32600,
54            message: msg.into(),
55            data: None,
56        }
57    }
58
59    /// Method not found (-32601)
60    pub fn method_not_found(method: &str) -> Self {
61        Self {
62            code: -32601,
63            message: format!("Method not found: {method}"),
64            data: None,
65        }
66    }
67
68    /// Invalid params (-32602)
69    pub fn invalid_params(msg: impl Into<String>) -> Self {
70        Self {
71            code: -32602,
72            message: msg.into(),
73            data: None,
74        }
75    }
76
77    /// Internal error (-32603)
78    pub fn internal_error(msg: impl Into<String>) -> Self {
79        Self {
80            code: -32603,
81            message: msg.into(),
82            data: None,
83        }
84    }
85}
86
87/// JSON-RPC 2.0 request.
88#[derive(Clone, Debug, Serialize, Deserialize)]
89pub struct JsonRpcRequest {
90    /// Protocol version (always "2.0")
91    pub jsonrpc: String,
92    /// Request ID
93    pub id: RequestId,
94    /// Method name
95    pub method: String,
96    /// Method parameters
97    #[serde(default, skip_serializing_if = "Value::is_null")]
98    pub params: Value,
99}
100
101impl JsonRpcRequest {
102    /// Create a new request.
103    pub fn new(id: impl Into<RequestId>, method: impl Into<String>, params: Value) -> Self {
104        Self {
105            jsonrpc: "2.0".into(),
106            id: id.into(),
107            method: method.into(),
108            params,
109        }
110    }
111}
112
113/// JSON-RPC 2.0 response.
114#[derive(Clone, Debug, Serialize, Deserialize)]
115pub struct JsonRpcResponse {
116    /// Protocol version (always "2.0")
117    pub jsonrpc: String,
118    /// Request ID this is responding to
119    pub id: RequestId,
120    /// Result (on success)
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub result: Option<Value>,
123    /// Error (on failure)
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub error: Option<JsonRpcError>,
126}
127
128impl JsonRpcResponse {
129    /// Create a success response.
130    pub fn success(id: RequestId, result: Value) -> Self {
131        Self {
132            jsonrpc: "2.0".into(),
133            id,
134            result: Some(result),
135            error: None,
136        }
137    }
138
139    /// Create an error response.
140    pub fn error(id: RequestId, error: JsonRpcError) -> Self {
141        Self {
142            jsonrpc: "2.0".into(),
143            id,
144            result: None,
145            error: Some(error),
146        }
147    }
148}
149
150/// JSON-RPC 2.0 notification (no ID, no response expected).
151#[derive(Clone, Debug, Serialize, Deserialize)]
152pub struct JsonRpcNotification {
153    /// Protocol version
154    pub jsonrpc: String,
155    /// Method name
156    pub method: String,
157    /// Parameters
158    #[serde(default, skip_serializing_if = "Value::is_null")]
159    pub params: Value,
160}
161
162/// MCP tool definition.
163#[derive(Clone, Debug, Serialize, Deserialize)]
164pub struct ToolDefinition {
165    /// Tool name
166    pub name: String,
167    /// Human-readable description
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub description: Option<String>,
170    /// JSON Schema for input parameters
171    #[serde(rename = "inputSchema")]
172    pub input_schema: Value,
173}
174
175/// MCP tool execution result.
176#[derive(Clone, Debug, Serialize, Deserialize)]
177pub struct ToolResult {
178    /// Content blocks in the result
179    pub content: Vec<ContentBlock>,
180    /// Whether this result represents an error
181    #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
182    pub is_error: Option<bool>,
183}
184
185impl ToolResult {
186    /// Create a text result.
187    pub fn text(s: impl Into<String>) -> Self {
188        Self {
189            content: vec![ContentBlock::Text { text: s.into() }],
190            is_error: Some(false),
191        }
192    }
193
194    /// Create an error result.
195    pub fn error(msg: impl Into<String>) -> Self {
196        Self {
197            content: vec![ContentBlock::Text { text: msg.into() }],
198            is_error: Some(true),
199        }
200    }
201}
202
203/// Content block in a tool result.
204#[derive(Clone, Debug, Serialize, Deserialize)]
205#[serde(tag = "type")]
206pub enum ContentBlock {
207    /// Text content
208    #[serde(rename = "text")]
209    Text {
210        /// The text content
211        text: String,
212    },
213    /// Image content (base64)
214    #[serde(rename = "image")]
215    Image {
216        /// Base64-encoded image data
217        data: String,
218        /// MIME type
219        #[serde(rename = "mimeType")]
220        mime_type: String,
221    },
222    /// Resource reference
223    #[serde(rename = "resource")]
224    Resource {
225        /// Resource URI
226        uri: String,
227        /// MIME type
228        #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
229        mime_type: Option<String>,
230        /// Optional text content
231        #[serde(skip_serializing_if = "Option::is_none")]
232        text: Option<String>,
233    },
234}
235
236/// MCP errors specific to this crate.
237#[derive(Debug, thiserror::Error)]
238pub enum McpError {
239    /// Protocol-level error
240    #[error("protocol error: {0}")]
241    Protocol(String),
242    /// Tool execution failed
243    #[error("tool execution failed: {0}")]
244    ToolFailure(String),
245    /// Security policy violation
246    #[error("security policy violation: {0}")]
247    PolicyViolation(String),
248    /// Transport error
249    #[error("transport error: {0}")]
250    Transport(String),
251    /// Serialization error
252    #[error("serialization error: {0}")]
253    Serialization(String),
254}
255
256impl From<serde_json::Error> for McpError {
257    fn from(e: serde_json::Error) -> Self {
258        Self::Serialization(e.to_string())
259    }
260}
261
262impl From<std::io::Error> for McpError {
263    fn from(e: std::io::Error) -> Self {
264        Self::Transport(e.to_string())
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271
272    #[test]
273    fn request_serialization() {
274        let req = JsonRpcRequest::new(1i64, "tools/call", serde_json::json!({"name": "echo"}));
275        let json = serde_json::to_string(&req).unwrap();
276        assert!(json.contains("\"jsonrpc\":\"2.0\""));
277        assert!(json.contains("\"method\":\"tools/call\""));
278    }
279
280    #[test]
281    fn response_success() {
282        let resp = JsonRpcResponse::success(RequestId::Number(1), serde_json::json!({"ok": true}));
283        assert!(resp.result.is_some());
284        assert!(resp.error.is_none());
285    }
286
287    #[test]
288    fn response_error() {
289        let resp = JsonRpcResponse::error(
290            RequestId::Number(1),
291            JsonRpcError::method_not_found("unknown"),
292        );
293        assert!(resp.result.is_none());
294        assert!(resp.error.is_some());
295        assert_eq!(resp.error.as_ref().unwrap().code, -32601);
296    }
297
298    #[test]
299    fn tool_result_text() {
300        let result = ToolResult::text("hello");
301        assert_eq!(result.content.len(), 1);
302        assert_eq!(result.is_error, Some(false));
303    }
304
305    #[test]
306    fn content_block_serialization() {
307        let block = ContentBlock::Text {
308            text: "hi".into(),
309        };
310        let json = serde_json::to_string(&block).unwrap();
311        assert!(json.contains("\"type\":\"text\""));
312    }
313}