1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9pub const JSONRPC_VERSION: &str = "2.0";
10
11pub const MCP_VERSION: &str = "2025-11-25";
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct JsonRpcRequest {
19 pub jsonrpc: String,
20 pub id: RequestId,
21 pub method: String,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub params: Option<Value>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct JsonRpcResponse {
28 pub jsonrpc: String,
29 pub id: RequestId,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub result: Option<Value>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub error: Option<JsonRpcError>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct JsonRpcNotification {
39 pub jsonrpc: String,
40 pub method: String,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub params: Option<Value>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
47#[serde(untagged)]
48pub enum RequestId {
49 String(String),
50 Number(i64),
51 Null,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct JsonRpcError {
57 pub code: i32,
58 pub message: String,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub data: Option<Value>,
61}
62
63impl JsonRpcError {
65 pub const PARSE_ERROR: i32 = -32700;
66 pub const INVALID_REQUEST: i32 = -32600;
67 pub const METHOD_NOT_FOUND: i32 = -32601;
68 pub const INVALID_PARAMS: i32 = -32602;
69 pub const INTERNAL_ERROR: i32 = -32603;
70
71 pub fn parse_error(msg: &str) -> Self {
72 Self {
73 code: Self::PARSE_ERROR,
74 message: format!("Parse error: {}", msg),
75 data: None,
76 }
77 }
78
79 pub fn invalid_request(msg: &str) -> Self {
80 Self {
81 code: Self::INVALID_REQUEST,
82 message: format!("Invalid request: {}", msg),
83 data: None,
84 }
85 }
86
87 pub fn method_not_found(method: &str) -> Self {
88 Self {
89 code: Self::METHOD_NOT_FOUND,
90 message: format!("Method not found: {}", method),
91 data: None,
92 }
93 }
94
95 pub fn invalid_params(msg: &str) -> Self {
96 Self {
97 code: Self::INVALID_PARAMS,
98 message: format!("Invalid params: {}", msg),
99 data: None,
100 }
101 }
102
103 pub fn internal_error(msg: &str) -> Self {
104 Self {
105 code: Self::INTERNAL_ERROR,
106 message: format!("Internal error: {}", msg),
107 data: None,
108 }
109 }
110}
111
112impl JsonRpcResponse {
113 pub fn success(id: RequestId, result: Value) -> Self {
115 Self {
116 jsonrpc: JSONRPC_VERSION.to_string(),
117 id,
118 result: Some(result),
119 error: None,
120 }
121 }
122
123 pub fn error(id: RequestId, error: JsonRpcError) -> Self {
125 Self {
126 jsonrpc: JSONRPC_VERSION.to_string(),
127 id,
128 result: None,
129 error: Some(error),
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct InitializeParams {
142 pub protocol_version: String,
143 pub capabilities: ClientCapabilities,
144 pub client_info: ClientInfo,
145}
146
147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
148pub struct ClientCapabilities {
149 #[serde(default)]
150 pub roots: Option<RootsCapability>,
151 #[serde(default)]
152 pub sampling: Option<SamplingCapability>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct RootsCapability {
157 #[serde(default)]
158 pub list_changed: bool,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct SamplingCapability {}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct ClientInfo {
166 pub name: String,
167 pub version: String,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub struct InitializeResult {
174 pub protocol_version: String,
175 pub capabilities: ServerCapabilities,
176 pub server_info: ServerInfo,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct ServerCapabilities {
181 #[serde(skip_serializing_if = "Option::is_none")]
182 pub tools: Option<ToolsCapability>,
183 #[serde(skip_serializing_if = "Option::is_none")]
184 pub resources: Option<ResourcesCapability>,
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub prompts: Option<PromptsCapability>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct ToolsCapability {
191 #[serde(default)]
192 pub list_changed: bool,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct ResourcesCapability {
197 #[serde(default)]
198 pub subscribe: bool,
199 #[serde(default)]
200 pub list_changed: bool,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct PromptsCapability {
205 #[serde(default)]
206 pub list_changed: bool,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct ServerInfo {
211 pub name: String,
212 pub version: String,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
216#[serde(rename_all = "camelCase")]
217pub struct ToolDefinition {
218 pub name: String,
219 pub description: String,
220 pub input_schema: Value,
221 #[serde(skip)]
223 pub category: Option<devboy_core::ToolCategory>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct ToolsListResult {
228 pub tools: Vec<ToolDefinition>,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct ToolCallParams {
233 pub name: String,
234 #[serde(default)]
235 pub arguments: Option<Value>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
239#[serde(rename_all = "camelCase")]
240pub struct ToolCallResult {
241 pub content: Vec<ToolResultContent>,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub is_error: Option<bool>,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247#[serde(tag = "type")]
248pub enum ToolResultContent {
249 #[serde(rename = "text")]
251 Text {
252 text: String,
254 },
255}
256
257impl ToolCallResult {
258 pub fn text(content: String) -> Self {
260 Self {
261 content: vec![ToolResultContent::Text { text: content }],
262 is_error: None,
263 }
264 }
265
266 pub fn error(message: String) -> Self {
268 Self {
269 content: vec![ToolResultContent::Text { text: message }],
270 is_error: Some(true),
271 }
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_request_serialization() {
281 let req = JsonRpcRequest {
282 jsonrpc: JSONRPC_VERSION.to_string(),
283 id: RequestId::Number(1),
284 method: "initialize".to_string(),
285 params: Some(serde_json::json!({"test": true})),
286 };
287
288 let json = serde_json::to_string(&req).unwrap();
289 assert!(json.contains("\"jsonrpc\":\"2.0\""));
290 assert!(json.contains("\"id\":1"));
291 }
292
293 #[test]
294 fn test_response_success() {
295 let resp = JsonRpcResponse::success(
296 RequestId::String("abc".to_string()),
297 serde_json::json!({"result": "ok"}),
298 );
299
300 assert!(resp.error.is_none());
301 assert!(resp.result.is_some());
302 }
303
304 #[test]
305 fn test_response_error() {
306 let resp =
307 JsonRpcResponse::error(RequestId::Number(1), JsonRpcError::method_not_found("test"));
308
309 assert!(resp.result.is_none());
310 assert!(resp.error.is_some());
311 assert_eq!(resp.error.unwrap().code, JsonRpcError::METHOD_NOT_FOUND);
312 }
313
314 #[test]
315 fn test_tool_call_result() {
316 let result = ToolCallResult::text("Hello".to_string());
317 let json = serde_json::to_string(&result).unwrap();
318 assert!(json.contains("\"type\":\"text\""));
319 assert!(json.contains("\"text\":\"Hello\""));
320 }
321
322 #[test]
323 fn test_tool_call_result_error() {
324 let result = ToolCallResult::error("Something failed".to_string());
325 assert_eq!(result.is_error, Some(true));
326 let json = serde_json::to_string(&result).unwrap();
327 assert!(json.contains("Something failed"));
328 }
329
330 #[test]
331 fn test_parse_error() {
332 let err = JsonRpcError::parse_error("bad json");
333 assert_eq!(err.code, JsonRpcError::PARSE_ERROR);
334 assert!(err.message.contains("bad json"));
335 assert!(err.data.is_none());
336 }
337
338 #[test]
339 fn test_invalid_request_error() {
340 let err = JsonRpcError::invalid_request("not initialized");
341 assert_eq!(err.code, JsonRpcError::INVALID_REQUEST);
342 assert!(err.message.contains("not initialized"));
343 }
344
345 #[test]
346 fn test_invalid_params_error() {
347 let err = JsonRpcError::invalid_params("missing field");
348 assert_eq!(err.code, JsonRpcError::INVALID_PARAMS);
349 assert!(err.message.contains("missing field"));
350 }
351
352 #[test]
353 fn test_internal_error() {
354 let err = JsonRpcError::internal_error("unexpected");
355 assert_eq!(err.code, JsonRpcError::INTERNAL_ERROR);
356 assert!(err.message.contains("unexpected"));
357 }
358
359 #[test]
360 fn test_request_id_variants() {
361 let num = RequestId::Number(42);
362 let str_id = RequestId::String("abc".to_string());
363 let null = RequestId::Null;
364
365 assert_eq!(num, RequestId::Number(42));
366 assert_eq!(str_id, RequestId::String("abc".to_string()));
367 assert_eq!(null, RequestId::Null);
368
369 let json = serde_json::to_string(&num).unwrap();
371 assert_eq!(json, "42");
372
373 let json = serde_json::to_string(&str_id).unwrap();
374 assert_eq!(json, "\"abc\"");
375
376 let json = serde_json::to_string(&null).unwrap();
377 assert_eq!(json, "null");
378 }
379
380 #[test]
381 fn test_notification_serialization() {
382 let notif = JsonRpcNotification {
383 jsonrpc: JSONRPC_VERSION.to_string(),
384 method: "initialized".to_string(),
385 params: None,
386 };
387
388 let json = serde_json::to_string(¬if).unwrap();
389 assert!(json.contains("\"method\":\"initialized\""));
390 assert!(!json.contains("params"));
392 }
393}