1use serde_json::Value;
7
8#[derive(Debug, Clone, PartialEq)]
12pub enum JsonRpcId {
13 Number(i64),
14 String(String),
15}
16
17impl std::fmt::Display for JsonRpcId {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 match self {
20 Self::Number(n) => write!(f, "{n}"),
21 Self::String(s) => write!(f, "{s}"),
22 }
23 }
24}
25
26#[derive(Debug)]
28pub enum JsonRpcMessage {
29 Request(JsonRpcRequest),
31 Notification(JsonRpcNotification),
33 Response(JsonRpcResponse),
35}
36
37#[derive(Debug)]
38pub struct JsonRpcRequest {
39 pub id: JsonRpcId,
40 pub method: String,
41 pub params: Option<Value>,
42}
43
44#[derive(Debug)]
45pub struct JsonRpcNotification {
46 pub method: String,
47 pub params: Option<Value>,
48}
49
50#[derive(Debug)]
51pub struct JsonRpcResponse {
52 pub id: JsonRpcId,
53 pub result: Option<Value>,
54 pub error: Option<JsonRpcError>,
55}
56
57#[derive(Debug)]
58pub struct JsonRpcError {
59 pub code: i64,
60 pub message: String,
61 pub data: Option<Value>,
62}
63
64pub mod error_code {
67 pub const PARSE_ERROR: i64 = -32700;
69 pub const INVALID_REQUEST: i64 = -32600;
71 pub const METHOD_NOT_FOUND: i64 = -32601;
73 pub const INVALID_PARAMS: i64 = -32602;
75 pub const INTERNAL_ERROR: i64 = -32603;
77
78 pub fn label(code: i64) -> &'static str {
80 match code {
81 PARSE_ERROR => "Parse error",
82 INVALID_REQUEST => "Invalid request",
83 METHOD_NOT_FOUND => "Method not found",
84 INVALID_PARAMS => "Invalid params",
85 INTERNAL_ERROR => "Internal error",
86 -32099..=-32000 => "Server error",
87 _ => "Unknown error",
88 }
89 }
90}
91
92pub fn error_response(id: &Value, code: i64, message: &str) -> Vec<u8> {
97 let resp = serde_json::json!({
98 "jsonrpc": "2.0",
99 "id": id,
100 "error": {
101 "code": code,
102 "message": message,
103 }
104 });
105 serde_json::to_vec(&resp).unwrap_or_default()
106}
107
108pub fn extract_error_code(body: &Value) -> Option<(i64, &str)> {
110 let err = body.get("error")?;
111 let code = err.get("code")?.as_i64()?;
112 let message = err.get("message")?.as_str()?;
113 Some((code, message))
114}
115
116#[derive(Debug, Clone, PartialEq)]
120pub enum McpMethod {
121 Initialize,
122 Initialized,
123 Ping,
124 ToolsList,
125 ToolsCall,
126 ResourcesList,
127 ResourcesRead,
128 ResourcesSubscribe,
129 ResourcesUnsubscribe,
130 PromptsList,
131 PromptsGet,
132 LoggingSetLevel,
133 CompletionComplete,
134 Notification(String),
136 Unknown(String),
138}
139
140pub const INITIALIZE: &str = "initialize";
142pub const INITIALIZED: &str = "notifications/initialized";
143pub const PING: &str = "ping";
144pub const TOOLS_LIST: &str = "tools/list";
145pub const TOOLS_CALL: &str = "tools/call";
146pub const RESOURCES_LIST: &str = "resources/list";
147pub const RESOURCES_READ: &str = "resources/read";
148pub const RESOURCES_SUBSCRIBE: &str = "resources/subscribe";
149pub const RESOURCES_UNSUBSCRIBE: &str = "resources/unsubscribe";
150pub const PROMPTS_LIST: &str = "prompts/list";
151pub const PROMPTS_GET: &str = "prompts/get";
152pub const LOGGING_SET_LEVEL: &str = "logging/setLevel";
153pub const COMPLETION_COMPLETE: &str = "completion/complete";
154
155impl McpMethod {
156 pub fn parse(method: &str) -> Self {
157 match method {
158 INITIALIZE => Self::Initialize,
159 INITIALIZED => Self::Initialized,
160 PING => Self::Ping,
161 TOOLS_LIST => Self::ToolsList,
162 TOOLS_CALL => Self::ToolsCall,
163 RESOURCES_LIST => Self::ResourcesList,
164 RESOURCES_READ => Self::ResourcesRead,
165 RESOURCES_SUBSCRIBE => Self::ResourcesSubscribe,
166 RESOURCES_UNSUBSCRIBE => Self::ResourcesUnsubscribe,
167 PROMPTS_LIST => Self::PromptsList,
168 PROMPTS_GET => Self::PromptsGet,
169 LOGGING_SET_LEVEL => Self::LoggingSetLevel,
170 COMPLETION_COMPLETE => Self::CompletionComplete,
171 m if m.starts_with("notifications/") => Self::Notification(m.to_string()),
172 m => Self::Unknown(m.to_string()),
173 }
174 }
175
176 pub fn as_str(&self) -> &str {
178 match self {
179 Self::Initialize => INITIALIZE,
180 Self::Initialized => INITIALIZED,
181 Self::Ping => PING,
182 Self::ToolsList => TOOLS_LIST,
183 Self::ToolsCall => TOOLS_CALL,
184 Self::ResourcesList => RESOURCES_LIST,
185 Self::ResourcesRead => RESOURCES_READ,
186 Self::ResourcesSubscribe => RESOURCES_SUBSCRIBE,
187 Self::ResourcesUnsubscribe => RESOURCES_UNSUBSCRIBE,
188 Self::PromptsList => PROMPTS_LIST,
189 Self::PromptsGet => PROMPTS_GET,
190 Self::LoggingSetLevel => LOGGING_SET_LEVEL,
191 Self::CompletionComplete => COMPLETION_COMPLETE,
192 Self::Notification(m) => m.as_str(),
193 Self::Unknown(m) => m.as_str(),
194 }
195 }
196}
197
198fn parse_id(value: &Value) -> Option<JsonRpcId> {
202 match value {
203 Value::Number(n) => n.as_i64().map(JsonRpcId::Number),
204 Value::String(s) => Some(JsonRpcId::String(s.clone())),
205 _ => None,
206 }
207}
208
209fn parse_error(value: &Value) -> Option<JsonRpcError> {
211 let obj = value.as_object()?;
212 Some(JsonRpcError {
213 code: obj.get("code")?.as_i64()?,
214 message: obj.get("message")?.as_str()?.to_string(),
215 data: obj.get("data").cloned(),
216 })
217}
218
219pub fn parse_message(value: &Value) -> Option<JsonRpcMessage> {
222 let obj = value.as_object()?;
223
224 if obj.get("jsonrpc")?.as_str()? != "2.0" {
226 return None;
227 }
228
229 let id = obj.get("id").and_then(parse_id);
230 let method = obj.get("method").and_then(|m| m.as_str()).map(String::from);
231 let params = obj.get("params").cloned();
232
233 match (method, id) {
234 (Some(method), Some(id)) => Some(JsonRpcMessage::Request(JsonRpcRequest {
236 id,
237 method,
238 params,
239 })),
240 (Some(method), None) => Some(JsonRpcMessage::Notification(JsonRpcNotification {
242 method,
243 params,
244 })),
245 (None, Some(id)) => {
247 let result = obj.get("result").cloned();
248 let error = obj.get("error").and_then(parse_error);
249 Some(JsonRpcMessage::Response(JsonRpcResponse {
250 id,
251 result,
252 error,
253 }))
254 }
255 (None, None) => None,
257 }
258}
259
260#[derive(Debug)]
262pub struct ParsedBody {
263 pub messages: Vec<JsonRpcMessage>,
264 pub is_batch: bool,
265}
266
267impl ParsedBody {
268 pub fn method_str(&self) -> &str {
271 self.messages
272 .iter()
273 .find_map(|m| match m {
274 JsonRpcMessage::Request(r) => Some(r.method.as_str()),
275 JsonRpcMessage::Notification(n) => Some(n.method.as_str()),
276 _ => None,
277 })
278 .unwrap_or("unknown")
279 }
280
281 pub fn mcp_method(&self) -> McpMethod {
283 McpMethod::parse(self.method_str())
284 }
285
286 pub fn first_request_id(&self) -> Option<&JsonRpcId> {
288 self.messages.iter().find_map(|m| match m {
289 JsonRpcMessage::Request(r) => Some(&r.id),
290 _ => None,
291 })
292 }
293
294 pub fn is_notification_only(&self) -> bool {
296 self.messages
297 .iter()
298 .all(|m| matches!(m, JsonRpcMessage::Notification(_)))
299 }
300
301 pub fn detail(&self) -> Option<String> {
306 let params = self.first_params()?;
307 let method = self.mcp_method();
308 match method {
309 McpMethod::ToolsCall => params.get("name")?.as_str().map(String::from),
310 McpMethod::ResourcesRead => params.get("uri")?.as_str().map(String::from),
311 McpMethod::PromptsGet => params.get("name")?.as_str().map(String::from),
312 _ => None,
313 }
314 }
315
316 pub fn first_params(&self) -> Option<&Value> {
318 self.messages.iter().find_map(|m| match m {
319 JsonRpcMessage::Request(r) => r.params.as_ref(),
320 JsonRpcMessage::Notification(n) => n.params.as_ref(),
321 _ => None,
322 })
323 }
324}
325
326pub fn parse_body(body: &[u8]) -> Option<ParsedBody> {
329 let value: Value = serde_json::from_slice(body).ok()?;
330
331 if let Some(arr) = value.as_array() {
332 let messages: Vec<_> = arr.iter().filter_map(parse_message).collect();
334 if messages.is_empty() {
335 return None;
336 }
337 Some(ParsedBody {
338 messages,
339 is_batch: true,
340 })
341 } else {
342 let msg = parse_message(&value)?;
344 Some(ParsedBody {
345 messages: vec![msg],
346 is_batch: false,
347 })
348 }
349}
350
351#[cfg(test)]
354mod tests {
355 use super::*;
356 use serde_json::json;
357
358 #[test]
361 fn parse_request() {
362 let val = json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "get_weather"}});
363 let msg = parse_message(&val).unwrap();
364 match msg {
365 JsonRpcMessage::Request(r) => {
366 assert_eq!(r.id, JsonRpcId::Number(1));
367 assert_eq!(r.method, "tools/call");
368 assert!(r.params.is_some());
369 }
370 _ => panic!("expected Request"),
371 }
372 }
373
374 #[test]
375 fn parse_request_string_id() {
376 let val = json!({"jsonrpc": "2.0", "id": "abc-123", "method": "initialize"});
377 let msg = parse_message(&val).unwrap();
378 match msg {
379 JsonRpcMessage::Request(r) => {
380 assert_eq!(r.id, JsonRpcId::String("abc-123".into()));
381 assert_eq!(r.method, "initialize");
382 }
383 _ => panic!("expected Request"),
384 }
385 }
386
387 #[test]
388 fn parse_notification() {
389 let val = json!({"jsonrpc": "2.0", "method": "notifications/initialized"});
390 let msg = parse_message(&val).unwrap();
391 match msg {
392 JsonRpcMessage::Notification(n) => {
393 assert_eq!(n.method, "notifications/initialized");
394 assert!(n.params.is_none());
395 }
396 _ => panic!("expected Notification"),
397 }
398 }
399
400 #[test]
401 fn parse_response_result() {
402 let val = json!({"jsonrpc": "2.0", "id": 1, "result": {"tools": []}});
403 let msg = parse_message(&val).unwrap();
404 match msg {
405 JsonRpcMessage::Response(r) => {
406 assert_eq!(r.id, JsonRpcId::Number(1));
407 assert!(r.result.is_some());
408 assert!(r.error.is_none());
409 }
410 _ => panic!("expected Response"),
411 }
412 }
413
414 #[test]
415 fn parse_response_error() {
416 let val = json!({"jsonrpc": "2.0", "id": 1, "error": {"code": -32601, "message": "Method not found"}});
417 let msg = parse_message(&val).unwrap();
418 match msg {
419 JsonRpcMessage::Response(r) => {
420 assert_eq!(r.id, JsonRpcId::Number(1));
421 assert!(r.result.is_none());
422 let err = r.error.unwrap();
423 assert_eq!(err.code, -32601);
424 assert_eq!(err.message, "Method not found");
425 }
426 _ => panic!("expected Response"),
427 }
428 }
429
430 #[test]
431 fn reject_wrong_jsonrpc_version() {
432 let val = json!({"jsonrpc": "1.0", "id": 1, "method": "test"});
433 assert!(parse_message(&val).is_none());
434 }
435
436 #[test]
437 fn reject_missing_jsonrpc() {
438 let val = json!({"id": 1, "method": "test"});
439 assert!(parse_message(&val).is_none());
440 }
441
442 #[test]
443 fn reject_no_method_no_id() {
444 let val = json!({"jsonrpc": "2.0"});
445 assert!(parse_message(&val).is_none());
446 }
447
448 #[test]
449 fn reject_non_object() {
450 let val = json!("hello");
451 assert!(parse_message(&val).is_none());
452 }
453
454 #[test]
455 fn reject_oauth_register_body() {
456 let val = json!({
458 "client_name": "My App",
459 "redirect_uris": ["https://example.com/callback"],
460 "grant_types": ["authorization_code"]
461 });
462 assert!(parse_message(&val).is_none());
463 }
464
465 #[test]
468 fn parse_single_request() {
469 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
470 let parsed = parse_body(body).unwrap();
471 assert!(!parsed.is_batch);
472 assert_eq!(parsed.messages.len(), 1);
473 assert_eq!(parsed.method_str(), "tools/list");
474 assert_eq!(parsed.mcp_method(), McpMethod::ToolsList);
475 }
476
477 #[test]
478 fn parse_batch_requests() {
479 let body = br#"[
480 {"jsonrpc":"2.0","id":1,"method":"tools/list"},
481 {"jsonrpc":"2.0","id":2,"method":"resources/list"}
482 ]"#;
483 let parsed = parse_body(body).unwrap();
484 assert!(parsed.is_batch);
485 assert_eq!(parsed.messages.len(), 2);
486 assert_eq!(parsed.method_str(), "tools/list");
488 }
489
490 #[test]
491 fn parse_notification_only() {
492 let body = br#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#;
493 let parsed = parse_body(body).unwrap();
494 assert!(parsed.is_notification_only());
495 assert_eq!(parsed.mcp_method(), McpMethod::Initialized);
496 }
497
498 #[test]
499 fn parse_mixed_batch() {
500 let body = br#"[
501 {"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":1}},
502 {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather"}}
503 ]"#;
504 let parsed = parse_body(body).unwrap();
505 assert!(parsed.is_batch);
506 assert!(!parsed.is_notification_only());
507 assert_eq!(parsed.first_request_id(), Some(&JsonRpcId::Number(2)));
509 }
510
511 #[test]
512 fn reject_empty_batch() {
513 let body = b"[]";
514 assert!(parse_body(body).is_none());
515 }
516
517 #[test]
518 fn reject_invalid_json() {
519 assert!(parse_body(b"not json").is_none());
520 }
521
522 #[test]
523 fn reject_non_jsonrpc_json() {
524 let body = br#"{"grant_type":"client_credentials","client_id":"abc"}"#;
526 assert!(parse_body(body).is_none());
527 }
528
529 #[test]
530 fn reject_batch_of_non_jsonrpc() {
531 let body = br#"[{"foo":"bar"},{"baz":1}]"#;
532 assert!(parse_body(body).is_none());
533 }
534
535 #[test]
538 fn mcp_method_known() {
539 assert_eq!(McpMethod::parse("initialize"), McpMethod::Initialize);
540 assert_eq!(McpMethod::parse("tools/call"), McpMethod::ToolsCall);
541 assert_eq!(McpMethod::parse("tools/list"), McpMethod::ToolsList);
542 assert_eq!(McpMethod::parse("resources/read"), McpMethod::ResourcesRead);
543 assert_eq!(McpMethod::parse("resources/list"), McpMethod::ResourcesList);
544 assert_eq!(McpMethod::parse("prompts/list"), McpMethod::PromptsList);
545 assert_eq!(McpMethod::parse("prompts/get"), McpMethod::PromptsGet);
546 assert_eq!(McpMethod::parse("ping"), McpMethod::Ping);
547 assert_eq!(
548 McpMethod::parse("logging/setLevel"),
549 McpMethod::LoggingSetLevel
550 );
551 assert_eq!(
552 McpMethod::parse("completion/complete"),
553 McpMethod::CompletionComplete
554 );
555 }
556
557 #[test]
558 fn mcp_method_notification() {
559 assert_eq!(
560 McpMethod::parse("notifications/initialized"),
561 McpMethod::Initialized
562 );
563 assert_eq!(
564 McpMethod::parse("notifications/cancelled"),
565 McpMethod::Notification("notifications/cancelled".into())
566 );
567 assert_eq!(
568 McpMethod::parse("notifications/resources/updated"),
569 McpMethod::Notification("notifications/resources/updated".into())
570 );
571 }
572
573 #[test]
574 fn mcp_method_unknown() {
575 assert_eq!(
576 McpMethod::parse("custom/method"),
577 McpMethod::Unknown("custom/method".into())
578 );
579 }
580
581 #[test]
582 fn mcp_method_as_str_roundtrip() {
583 let methods = [
584 "initialize",
585 "notifications/initialized",
586 "ping",
587 "tools/list",
588 "tools/call",
589 "resources/list",
590 "resources/read",
591 "prompts/list",
592 "prompts/get",
593 "logging/setLevel",
594 "completion/complete",
595 ];
596 for m in methods {
597 assert_eq!(McpMethod::parse(m).as_str(), m);
598 }
599 }
600
601 #[test]
604 fn id_display() {
605 assert_eq!(JsonRpcId::Number(42).to_string(), "42");
606 assert_eq!(JsonRpcId::String("abc".into()).to_string(), "abc");
607 }
608
609 #[test]
612 fn first_params_from_request() {
613 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo"}}"#;
614 let parsed = parse_body(body).unwrap();
615 let params = parsed.first_params().unwrap();
616 assert_eq!(params["name"], "echo");
617 }
618
619 #[test]
620 fn first_params_none_for_response() {
621 let body = br#"{"jsonrpc":"2.0","id":1,"result":{}}"#;
622 let parsed = parse_body(body).unwrap();
623 assert!(parsed.first_params().is_none());
624 }
625
626 #[test]
627 fn method_str_defaults_to_unknown_for_responses() {
628 let body = br#"{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}"#;
629 let parsed = parse_body(body).unwrap();
630 assert_eq!(parsed.method_str(), "unknown");
631 }
632
633 #[test]
636 fn detail_tools_call() {
637 let body =
638 br#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_weather"}}"#;
639 let parsed = parse_body(body).unwrap();
640 assert_eq!(parsed.detail().as_deref(), Some("get_weather"));
641 }
642
643 #[test]
644 fn detail_resources_read() {
645 let body = br#"{"jsonrpc":"2.0","id":1,"method":"resources/read","params":{"uri":"ui://widget/clock.html"}}"#;
646 let parsed = parse_body(body).unwrap();
647 assert_eq!(parsed.detail().as_deref(), Some("ui://widget/clock.html"));
648 }
649
650 #[test]
651 fn detail_none_for_tools_list() {
652 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
653 let parsed = parse_body(body).unwrap();
654 assert!(parsed.detail().is_none());
655 }
656
657 #[test]
660 fn error_code_labels() {
661 assert_eq!(error_code::label(error_code::PARSE_ERROR), "Parse error");
662 assert_eq!(
663 error_code::label(error_code::METHOD_NOT_FOUND),
664 "Method not found"
665 );
666 assert_eq!(
667 error_code::label(error_code::INVALID_PARAMS),
668 "Invalid params"
669 );
670 assert_eq!(
671 error_code::label(error_code::INTERNAL_ERROR),
672 "Internal error"
673 );
674 assert_eq!(error_code::label(-32000), "Server error");
675 assert_eq!(error_code::label(-32099), "Server error");
676 assert_eq!(error_code::label(42), "Unknown error");
677 }
678
679 #[test]
682 fn error_response_with_numeric_id() {
683 let body = error_response(&json!(1), error_code::METHOD_NOT_FOUND, "Method not found");
684 let parsed: Value = serde_json::from_slice(&body).unwrap();
685 assert_eq!(parsed["jsonrpc"], "2.0");
686 assert_eq!(parsed["id"], 1);
687 assert_eq!(parsed["error"]["code"], -32601);
688 assert_eq!(parsed["error"]["message"], "Method not found");
689 }
690
691 #[test]
692 fn error_response_with_null_id() {
693 let body = error_response(&Value::Null, error_code::PARSE_ERROR, "Parse error");
694 let parsed: Value = serde_json::from_slice(&body).unwrap();
695 assert_eq!(parsed["id"], Value::Null);
696 assert_eq!(parsed["error"]["code"], -32700);
697 }
698
699 #[test]
702 fn extract_error_from_response() {
703 let val = json!({"jsonrpc": "2.0", "id": 1, "error": {"code": -32601, "message": "Method not found"}});
704 let (code, msg) = extract_error_code(&val).unwrap();
705 assert_eq!(code, -32601);
706 assert_eq!(msg, "Method not found");
707 }
708
709 #[test]
710 fn extract_no_error_from_success() {
711 let val = json!({"jsonrpc": "2.0", "id": 1, "result": {"tools": []}});
712 assert!(extract_error_code(&val).is_none());
713 }
714}