1pub mod schema;
51pub mod schema_manager;
52pub mod session;
53
54use serde_json::Value;
55
56#[derive(Debug, Clone, PartialEq)]
60pub enum JsonRpcId {
61 Number(i64),
62 String(String),
63}
64
65impl std::fmt::Display for JsonRpcId {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Self::Number(n) => write!(f, "{n}"),
69 Self::String(s) => write!(f, "{s}"),
70 }
71 }
72}
73
74#[derive(Debug)]
76pub enum JsonRpcMessage {
77 Request(JsonRpcRequest),
79 Notification(JsonRpcNotification),
81 Response(JsonRpcResponse),
83}
84
85#[derive(Debug)]
86pub struct JsonRpcRequest {
87 pub id: JsonRpcId,
88 pub method: String,
89 pub params: Option<Value>,
90}
91
92#[derive(Debug)]
93pub struct JsonRpcNotification {
94 pub method: String,
95 pub params: Option<Value>,
96}
97
98#[derive(Debug)]
99pub struct JsonRpcResponse {
100 pub id: JsonRpcId,
101 pub result: Option<Value>,
102 pub error: Option<JsonRpcError>,
103}
104
105#[derive(Debug)]
106pub struct JsonRpcError {
107 pub code: i64,
108 pub message: String,
109 pub data: Option<Value>,
110}
111
112pub mod error_code {
115 pub const PARSE_ERROR: i64 = -32700;
117 pub const INVALID_REQUEST: i64 = -32600;
119 pub const METHOD_NOT_FOUND: i64 = -32601;
121 pub const INVALID_PARAMS: i64 = -32602;
123 pub const INTERNAL_ERROR: i64 = -32603;
125
126 pub fn label(code: i64) -> &'static str {
128 match code {
129 PARSE_ERROR => "Parse error",
130 INVALID_REQUEST => "Invalid request",
131 METHOD_NOT_FOUND => "Method not found",
132 INVALID_PARAMS => "Invalid params",
133 INTERNAL_ERROR => "Internal error",
134 -32099..=-32000 => "Server error",
135 _ => "Unknown error",
136 }
137 }
138}
139
140pub fn error_response(id: &Value, code: i64, message: &str) -> Vec<u8> {
145 let resp = serde_json::json!({
146 "jsonrpc": "2.0",
147 "id": id,
148 "error": {
149 "code": code,
150 "message": message,
151 }
152 });
153 serde_json::to_vec(&resp).unwrap_or_default()
154}
155
156pub fn extract_error_code(body: &Value) -> Option<(i64, &str)> {
158 let err = body.get("error")?;
159 let code = err.get("code")?.as_i64()?;
160 let message = err.get("message")?.as_str()?;
161 Some((code, message))
162}
163
164#[derive(Debug, Clone, PartialEq)]
168pub enum McpMethod {
169 Initialize,
170 Initialized,
171 Ping,
172 ToolsList,
173 ToolsCall,
174 ResourcesList,
175 ResourcesRead,
176 ResourcesTemplatesList,
177 ResourcesSubscribe,
178 ResourcesUnsubscribe,
179 PromptsList,
180 PromptsGet,
181 LoggingSetLevel,
182 CompletionComplete,
183 NotificationsToolsListChanged,
184 NotificationsCancelled,
185 NotificationsProgress,
186 Notification(String),
188 Unknown(String),
190}
191
192impl McpMethod {
193 pub fn needs_response_buffering(&self) -> bool {
201 matches!(
202 self,
203 McpMethod::ToolsList
204 | McpMethod::ToolsCall
205 | McpMethod::ResourcesList
206 | McpMethod::ResourcesTemplatesList
207 | McpMethod::ResourcesRead
208 )
209 }
210}
211
212pub const INITIALIZE: &str = "initialize";
214pub const INITIALIZED: &str = "notifications/initialized";
215pub const PING: &str = "ping";
216pub const TOOLS_LIST: &str = "tools/list";
217pub const TOOLS_CALL: &str = "tools/call";
218pub const RESOURCES_LIST: &str = "resources/list";
219pub const RESOURCES_READ: &str = "resources/read";
220pub const RESOURCES_SUBSCRIBE: &str = "resources/subscribe";
221pub const RESOURCES_UNSUBSCRIBE: &str = "resources/unsubscribe";
222pub const PROMPTS_LIST: &str = "prompts/list";
223pub const PROMPTS_GET: &str = "prompts/get";
224pub const LOGGING_SET_LEVEL: &str = "logging/setLevel";
225pub const COMPLETION_COMPLETE: &str = "completion/complete";
226pub const RESOURCES_TEMPLATES_LIST: &str = "resources/templates/list";
227pub const NOTIFICATIONS_TOOLS_LIST_CHANGED: &str = "notifications/tools/list_changed";
228pub const NOTIFICATIONS_CANCELLED: &str = "notifications/cancelled";
229pub const NOTIFICATIONS_PROGRESS: &str = "notifications/progress";
230
231impl McpMethod {
232 pub fn parse(method: &str) -> Self {
233 match method {
234 INITIALIZE => Self::Initialize,
235 INITIALIZED => Self::Initialized,
236 PING => Self::Ping,
237 TOOLS_LIST => Self::ToolsList,
238 TOOLS_CALL => Self::ToolsCall,
239 RESOURCES_LIST => Self::ResourcesList,
240 RESOURCES_READ => Self::ResourcesRead,
241 RESOURCES_TEMPLATES_LIST => Self::ResourcesTemplatesList,
242 RESOURCES_SUBSCRIBE => Self::ResourcesSubscribe,
243 RESOURCES_UNSUBSCRIBE => Self::ResourcesUnsubscribe,
244 PROMPTS_LIST => Self::PromptsList,
245 PROMPTS_GET => Self::PromptsGet,
246 LOGGING_SET_LEVEL => Self::LoggingSetLevel,
247 COMPLETION_COMPLETE => Self::CompletionComplete,
248 NOTIFICATIONS_TOOLS_LIST_CHANGED => Self::NotificationsToolsListChanged,
249 NOTIFICATIONS_CANCELLED => Self::NotificationsCancelled,
250 NOTIFICATIONS_PROGRESS => Self::NotificationsProgress,
251 m if m.starts_with("notifications/") => Self::Notification(m.to_string()),
252 m => Self::Unknown(m.to_string()),
253 }
254 }
255
256 pub fn as_str(&self) -> &str {
258 match self {
259 Self::Initialize => INITIALIZE,
260 Self::Initialized => INITIALIZED,
261 Self::Ping => PING,
262 Self::ToolsList => TOOLS_LIST,
263 Self::ToolsCall => TOOLS_CALL,
264 Self::ResourcesList => RESOURCES_LIST,
265 Self::ResourcesRead => RESOURCES_READ,
266 Self::ResourcesTemplatesList => RESOURCES_TEMPLATES_LIST,
267 Self::ResourcesSubscribe => RESOURCES_SUBSCRIBE,
268 Self::ResourcesUnsubscribe => RESOURCES_UNSUBSCRIBE,
269 Self::PromptsList => PROMPTS_LIST,
270 Self::PromptsGet => PROMPTS_GET,
271 Self::LoggingSetLevel => LOGGING_SET_LEVEL,
272 Self::CompletionComplete => COMPLETION_COMPLETE,
273 Self::NotificationsToolsListChanged => NOTIFICATIONS_TOOLS_LIST_CHANGED,
274 Self::NotificationsCancelled => NOTIFICATIONS_CANCELLED,
275 Self::NotificationsProgress => NOTIFICATIONS_PROGRESS,
276 Self::Notification(m) => m.as_str(),
277 Self::Unknown(m) => m.as_str(),
278 }
279 }
280}
281
282fn parse_id(value: &Value) -> Option<JsonRpcId> {
286 match value {
287 Value::Number(n) => n.as_i64().map(JsonRpcId::Number),
288 Value::String(s) => Some(JsonRpcId::String(s.clone())),
289 _ => None,
290 }
291}
292
293fn parse_error(value: &Value) -> Option<JsonRpcError> {
295 let obj = value.as_object()?;
296 Some(JsonRpcError {
297 code: obj.get("code")?.as_i64()?,
298 message: obj.get("message")?.as_str()?.to_string(),
299 data: obj.get("data").cloned(),
300 })
301}
302
303pub fn parse_message(value: &Value) -> Option<JsonRpcMessage> {
306 let obj = value.as_object()?;
307
308 if obj.get("jsonrpc")?.as_str()? != "2.0" {
310 return None;
311 }
312
313 let id = obj.get("id").and_then(parse_id);
314 let method = obj.get("method").and_then(|m| m.as_str()).map(String::from);
315 let params = obj.get("params").cloned();
316
317 match (method, id) {
318 (Some(method), Some(id)) => Some(JsonRpcMessage::Request(JsonRpcRequest {
320 id,
321 method,
322 params,
323 })),
324 (Some(method), None) => Some(JsonRpcMessage::Notification(JsonRpcNotification {
326 method,
327 params,
328 })),
329 (None, Some(id)) => {
331 let result = obj.get("result").cloned();
332 let error = obj.get("error").and_then(parse_error);
333 Some(JsonRpcMessage::Response(JsonRpcResponse {
334 id,
335 result,
336 error,
337 }))
338 }
339 (None, None) => None,
341 }
342}
343
344#[derive(Debug)]
346pub struct ParsedBody {
347 pub messages: Vec<JsonRpcMessage>,
348 pub is_batch: bool,
349}
350
351impl ParsedBody {
352 pub fn method_str(&self) -> &str {
355 self.messages
356 .iter()
357 .find_map(|m| match m {
358 JsonRpcMessage::Request(r) => Some(r.method.as_str()),
359 JsonRpcMessage::Notification(n) => Some(n.method.as_str()),
360 _ => None,
361 })
362 .unwrap_or("unknown")
363 }
364
365 pub fn mcp_method(&self) -> McpMethod {
367 McpMethod::parse(self.method_str())
368 }
369
370 pub fn first_request_id(&self) -> Option<&JsonRpcId> {
372 self.messages.iter().find_map(|m| match m {
373 JsonRpcMessage::Request(r) => Some(&r.id),
374 _ => None,
375 })
376 }
377
378 pub fn is_notification_only(&self) -> bool {
380 self.messages
381 .iter()
382 .all(|m| matches!(m, JsonRpcMessage::Notification(_)))
383 }
384
385 pub fn detail(&self) -> Option<String> {
390 let params = self.first_params()?;
391 let method = self.mcp_method();
392 match method {
393 McpMethod::ToolsCall => params.get("name")?.as_str().map(String::from),
394 McpMethod::ResourcesRead => params.get("uri")?.as_str().map(String::from),
395 McpMethod::PromptsGet => params.get("name")?.as_str().map(String::from),
396 McpMethod::NotificationsCancelled => {
397 params.get("requestId").map(|v| match v {
399 Value::String(s) => s.clone(),
400 Value::Number(n) => n.to_string(),
401 _ => v.to_string(),
402 })
403 }
404 McpMethod::NotificationsProgress => {
405 params.get("progressToken").map(|v| match v {
407 Value::String(s) => s.clone(),
408 Value::Number(n) => n.to_string(),
409 _ => v.to_string(),
410 })
411 }
412 _ => None,
413 }
414 }
415
416 pub fn first_params(&self) -> Option<&Value> {
418 self.messages.iter().find_map(|m| match m {
419 JsonRpcMessage::Request(r) => r.params.as_ref(),
420 JsonRpcMessage::Notification(n) => n.params.as_ref(),
421 _ => None,
422 })
423 }
424}
425
426pub fn parse_body(body: &[u8]) -> Option<ParsedBody> {
429 let value: Value = serde_json::from_slice(body).ok()?;
430
431 if let Some(arr) = value.as_array() {
432 let messages: Vec<_> = arr.iter().filter_map(parse_message).collect();
434 if messages.is_empty() {
435 return None;
436 }
437 Some(ParsedBody {
438 messages,
439 is_batch: true,
440 })
441 } else {
442 let msg = parse_message(&value)?;
444 Some(ParsedBody {
445 messages: vec![msg],
446 is_batch: false,
447 })
448 }
449}
450
451#[cfg(test)]
454#[allow(non_snake_case)]
455mod tests {
456 use super::*;
457 use serde_json::json;
458
459 #[test]
462 fn parse_message__request() {
463 let val = json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "get_weather"}});
464 let msg = parse_message(&val).unwrap();
465 match msg {
466 JsonRpcMessage::Request(r) => {
467 assert_eq!(r.id, JsonRpcId::Number(1));
468 assert_eq!(r.method, "tools/call");
469 assert!(r.params.is_some());
470 }
471 _ => panic!("expected Request"),
472 }
473 }
474
475 #[test]
476 fn parse_message__request_string_id() {
477 let val = json!({"jsonrpc": "2.0", "id": "abc-123", "method": "initialize"});
478 let msg = parse_message(&val).unwrap();
479 match msg {
480 JsonRpcMessage::Request(r) => {
481 assert_eq!(r.id, JsonRpcId::String("abc-123".into()));
482 assert_eq!(r.method, "initialize");
483 }
484 _ => panic!("expected Request"),
485 }
486 }
487
488 #[test]
489 fn parse_message__notification() {
490 let val = json!({"jsonrpc": "2.0", "method": "notifications/initialized"});
491 let msg = parse_message(&val).unwrap();
492 match msg {
493 JsonRpcMessage::Notification(n) => {
494 assert_eq!(n.method, "notifications/initialized");
495 assert!(n.params.is_none());
496 }
497 _ => panic!("expected Notification"),
498 }
499 }
500
501 #[test]
502 fn parse_message__response_result() {
503 let val = json!({"jsonrpc": "2.0", "id": 1, "result": {"tools": []}});
504 let msg = parse_message(&val).unwrap();
505 match msg {
506 JsonRpcMessage::Response(r) => {
507 assert_eq!(r.id, JsonRpcId::Number(1));
508 assert!(r.result.is_some());
509 assert!(r.error.is_none());
510 }
511 _ => panic!("expected Response"),
512 }
513 }
514
515 #[test]
516 fn parse_message__response_error() {
517 let val = json!({"jsonrpc": "2.0", "id": 1, "error": {"code": -32601, "message": "Method not found"}});
518 let msg = parse_message(&val).unwrap();
519 match msg {
520 JsonRpcMessage::Response(r) => {
521 assert_eq!(r.id, JsonRpcId::Number(1));
522 assert!(r.result.is_none());
523 let err = r.error.unwrap();
524 assert_eq!(err.code, -32601);
525 assert_eq!(err.message, "Method not found");
526 }
527 _ => panic!("expected Response"),
528 }
529 }
530
531 #[test]
532 fn parse_message__rejects_wrong_version() {
533 let val = json!({"jsonrpc": "1.0", "id": 1, "method": "test"});
534 assert!(parse_message(&val).is_none());
535 }
536
537 #[test]
538 fn parse_message__rejects_missing_jsonrpc() {
539 let val = json!({"id": 1, "method": "test"});
540 assert!(parse_message(&val).is_none());
541 }
542
543 #[test]
544 fn parse_message__rejects_no_method_no_id() {
545 let val = json!({"jsonrpc": "2.0"});
546 assert!(parse_message(&val).is_none());
547 }
548
549 #[test]
550 fn parse_message__rejects_non_object() {
551 let val = json!("hello");
552 assert!(parse_message(&val).is_none());
553 }
554
555 #[test]
556 fn parse_message__rejects_oauth_register() {
557 let val = json!({
558 "client_name": "My App",
559 "redirect_uris": ["https://example.com/callback"],
560 "grant_types": ["authorization_code"]
561 });
562 assert!(parse_message(&val).is_none());
563 }
564
565 #[test]
568 fn parse_body__single_request() {
569 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
570 let parsed = parse_body(body).unwrap();
571 assert!(!parsed.is_batch);
572 assert_eq!(parsed.messages.len(), 1);
573 assert_eq!(parsed.method_str(), "tools/list");
574 assert_eq!(parsed.mcp_method(), McpMethod::ToolsList);
575 }
576
577 #[test]
578 fn parse_body__batch_requests() {
579 let body = br#"[
580 {"jsonrpc":"2.0","id":1,"method":"tools/list"},
581 {"jsonrpc":"2.0","id":2,"method":"resources/list"}
582 ]"#;
583 let parsed = parse_body(body).unwrap();
584 assert!(parsed.is_batch);
585 assert_eq!(parsed.messages.len(), 2);
586 assert_eq!(parsed.method_str(), "tools/list");
587 }
588
589 #[test]
590 fn parse_body__notification_only() {
591 let body = br#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#;
592 let parsed = parse_body(body).unwrap();
593 assert!(parsed.is_notification_only());
594 assert_eq!(parsed.mcp_method(), McpMethod::Initialized);
595 }
596
597 #[test]
598 fn parse_body__mixed_batch() {
599 let body = br#"[
600 {"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":1}},
601 {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather"}}
602 ]"#;
603 let parsed = parse_body(body).unwrap();
604 assert!(parsed.is_batch);
605 assert!(!parsed.is_notification_only());
606 assert_eq!(parsed.first_request_id(), Some(&JsonRpcId::Number(2)));
607 }
608
609 #[test]
610 fn parse_body__rejects_empty_batch() {
611 let body = b"[]";
612 assert!(parse_body(body).is_none());
613 }
614
615 #[test]
616 fn parse_body__rejects_invalid_json() {
617 assert!(parse_body(b"not json").is_none());
618 }
619
620 #[test]
621 fn parse_body__rejects_non_jsonrpc() {
622 let body = br#"{"grant_type":"client_credentials","client_id":"abc"}"#;
623 assert!(parse_body(body).is_none());
624 }
625
626 #[test]
627 fn parse_body__rejects_batch_of_non_jsonrpc() {
628 let body = br#"[{"foo":"bar"},{"baz":1}]"#;
629 assert!(parse_body(body).is_none());
630 }
631
632 #[test]
635 fn mcp_method__known_methods() {
636 assert_eq!(McpMethod::parse("initialize"), McpMethod::Initialize);
637 assert_eq!(McpMethod::parse("tools/call"), McpMethod::ToolsCall);
638 assert_eq!(McpMethod::parse("tools/list"), McpMethod::ToolsList);
639 assert_eq!(McpMethod::parse("resources/read"), McpMethod::ResourcesRead);
640 assert_eq!(McpMethod::parse("resources/list"), McpMethod::ResourcesList);
641 assert_eq!(
642 McpMethod::parse("resources/templates/list"),
643 McpMethod::ResourcesTemplatesList
644 );
645 assert_eq!(McpMethod::parse("prompts/list"), McpMethod::PromptsList);
646 assert_eq!(McpMethod::parse("prompts/get"), McpMethod::PromptsGet);
647 assert_eq!(McpMethod::parse("ping"), McpMethod::Ping);
648 assert_eq!(
649 McpMethod::parse("logging/setLevel"),
650 McpMethod::LoggingSetLevel
651 );
652 assert_eq!(
653 McpMethod::parse("completion/complete"),
654 McpMethod::CompletionComplete
655 );
656 assert_eq!(
657 McpMethod::parse("notifications/tools/list_changed"),
658 McpMethod::NotificationsToolsListChanged
659 );
660 assert_eq!(
661 McpMethod::parse("notifications/cancelled"),
662 McpMethod::NotificationsCancelled
663 );
664 assert_eq!(
665 McpMethod::parse("notifications/progress"),
666 McpMethod::NotificationsProgress
667 );
668 }
669
670 #[test]
671 fn mcp_method__notifications() {
672 assert_eq!(
673 McpMethod::parse("notifications/initialized"),
674 McpMethod::Initialized
675 );
676 assert_eq!(
678 McpMethod::parse("notifications/cancelled"),
679 McpMethod::NotificationsCancelled
680 );
681 assert_eq!(
682 McpMethod::parse("notifications/progress"),
683 McpMethod::NotificationsProgress
684 );
685 assert_eq!(
686 McpMethod::parse("notifications/tools/list_changed"),
687 McpMethod::NotificationsToolsListChanged
688 );
689 assert_eq!(
691 McpMethod::parse("notifications/resources/updated"),
692 McpMethod::Notification("notifications/resources/updated".into())
693 );
694 }
695
696 #[test]
697 fn mcp_method__unknown() {
698 assert_eq!(
699 McpMethod::parse("custom/method"),
700 McpMethod::Unknown("custom/method".into())
701 );
702 }
703
704 #[test]
705 fn mcp_method__as_str_roundtrip() {
706 let methods = [
707 "initialize",
708 "notifications/initialized",
709 "ping",
710 "tools/list",
711 "tools/call",
712 "resources/list",
713 "resources/read",
714 "resources/templates/list",
715 "prompts/list",
716 "prompts/get",
717 "logging/setLevel",
718 "completion/complete",
719 "notifications/tools/list_changed",
720 "notifications/cancelled",
721 "notifications/progress",
722 ];
723 for m in methods {
724 assert_eq!(McpMethod::parse(m).as_str(), m);
725 }
726 }
727
728 #[test]
731 fn jsonrpc_id__display() {
732 assert_eq!(JsonRpcId::Number(42).to_string(), "42");
733 assert_eq!(JsonRpcId::String("abc".into()).to_string(), "abc");
734 }
735
736 #[test]
739 fn parsed_body__first_params_from_request() {
740 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo"}}"#;
741 let parsed = parse_body(body).unwrap();
742 let params = parsed.first_params().unwrap();
743 assert_eq!(params["name"], "echo");
744 }
745
746 #[test]
747 fn parsed_body__first_params_none_for_response() {
748 let body = br#"{"jsonrpc":"2.0","id":1,"result":{}}"#;
749 let parsed = parse_body(body).unwrap();
750 assert!(parsed.first_params().is_none());
751 }
752
753 #[test]
754 fn parsed_body__method_str_defaults_to_unknown() {
755 let body = br#"{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}"#;
756 let parsed = parse_body(body).unwrap();
757 assert_eq!(parsed.method_str(), "unknown");
758 }
759
760 #[test]
763 fn parsed_body__detail_tools_call() {
764 let body =
765 br#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_weather"}}"#;
766 let parsed = parse_body(body).unwrap();
767 assert_eq!(parsed.detail().as_deref(), Some("get_weather"));
768 }
769
770 #[test]
771 fn parsed_body__detail_resources_read() {
772 let body = br#"{"jsonrpc":"2.0","id":1,"method":"resources/read","params":{"uri":"ui://widget/clock.html"}}"#;
773 let parsed = parse_body(body).unwrap();
774 assert_eq!(parsed.detail().as_deref(), Some("ui://widget/clock.html"));
775 }
776
777 #[test]
778 fn parsed_body__detail_none_for_tools_list() {
779 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
780 let parsed = parse_body(body).unwrap();
781 assert!(parsed.detail().is_none());
782 }
783
784 #[test]
785 fn parsed_body__detail_notifications_cancelled() {
786 let body = br#"{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":"req-42","reason":"timeout"}}"#;
787 let parsed = parse_body(body).unwrap();
788 assert_eq!(parsed.detail().as_deref(), Some("req-42"));
789 }
790
791 #[test]
792 fn parsed_body__detail_cancelled_numeric_id() {
793 let body =
794 br#"{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":7}}"#;
795 let parsed = parse_body(body).unwrap();
796 assert_eq!(parsed.detail().as_deref(), Some("7"));
797 }
798
799 #[test]
800 fn parsed_body__detail_notifications_progress() {
801 let body = br#"{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"tok-1","progress":50,"total":100}}"#;
802 let parsed = parse_body(body).unwrap();
803 assert_eq!(parsed.detail().as_deref(), Some("tok-1"));
804 }
805
806 #[test]
807 fn parsed_body__detail_progress_numeric_token() {
808 let body = br#"{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":99,"progress":10}}"#;
809 let parsed = parse_body(body).unwrap();
810 assert_eq!(parsed.detail().as_deref(), Some("99"));
811 }
812
813 #[test]
816 fn error_code__labels() {
817 assert_eq!(error_code::label(error_code::PARSE_ERROR), "Parse error");
818 assert_eq!(
819 error_code::label(error_code::METHOD_NOT_FOUND),
820 "Method not found"
821 );
822 assert_eq!(
823 error_code::label(error_code::INVALID_PARAMS),
824 "Invalid params"
825 );
826 assert_eq!(
827 error_code::label(error_code::INTERNAL_ERROR),
828 "Internal error"
829 );
830 assert_eq!(error_code::label(-32000), "Server error");
831 assert_eq!(error_code::label(-32099), "Server error");
832 assert_eq!(error_code::label(42), "Unknown error");
833 }
834
835 #[test]
838 fn error_response__numeric_id() {
839 let body = error_response(&json!(1), error_code::METHOD_NOT_FOUND, "Method not found");
840 let parsed: Value = serde_json::from_slice(&body).unwrap();
841 assert_eq!(parsed["jsonrpc"], "2.0");
842 assert_eq!(parsed["id"], 1);
843 assert_eq!(parsed["error"]["code"], -32601);
844 assert_eq!(parsed["error"]["message"], "Method not found");
845 }
846
847 #[test]
848 fn error_response__null_id() {
849 let body = error_response(&Value::Null, error_code::PARSE_ERROR, "Parse error");
850 let parsed: Value = serde_json::from_slice(&body).unwrap();
851 assert_eq!(parsed["id"], Value::Null);
852 assert_eq!(parsed["error"]["code"], -32700);
853 }
854
855 #[test]
858 fn extract_error_code__from_error_response() {
859 let val = json!({"jsonrpc": "2.0", "id": 1, "error": {"code": -32601, "message": "Method not found"}});
860 let (code, msg) = extract_error_code(&val).unwrap();
861 assert_eq!(code, -32601);
862 assert_eq!(msg, "Method not found");
863 }
864
865 #[test]
866 fn extract_error_code__none_for_success() {
867 let val = json!({"jsonrpc": "2.0", "id": 1, "result": {"tools": []}});
868 assert!(extract_error_code(&val).is_none());
869 }
870}