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 {
204 matches!(
205 self,
206 McpMethod::Initialize
207 | McpMethod::ToolsList
208 | McpMethod::ToolsCall
209 | McpMethod::ResourcesList
210 | McpMethod::ResourcesTemplatesList
211 | McpMethod::ResourcesRead
212 | McpMethod::PromptsList
213 )
214 }
215}
216
217pub const INITIALIZE: &str = "initialize";
219pub const INITIALIZED: &str = "notifications/initialized";
220pub const PING: &str = "ping";
221pub const TOOLS_LIST: &str = "tools/list";
222pub const TOOLS_CALL: &str = "tools/call";
223pub const RESOURCES_LIST: &str = "resources/list";
224pub const RESOURCES_READ: &str = "resources/read";
225pub const RESOURCES_SUBSCRIBE: &str = "resources/subscribe";
226pub const RESOURCES_UNSUBSCRIBE: &str = "resources/unsubscribe";
227pub const PROMPTS_LIST: &str = "prompts/list";
228pub const PROMPTS_GET: &str = "prompts/get";
229pub const LOGGING_SET_LEVEL: &str = "logging/setLevel";
230pub const COMPLETION_COMPLETE: &str = "completion/complete";
231pub const RESOURCES_TEMPLATES_LIST: &str = "resources/templates/list";
232pub const NOTIFICATIONS_TOOLS_LIST_CHANGED: &str = "notifications/tools/list_changed";
233pub const NOTIFICATIONS_CANCELLED: &str = "notifications/cancelled";
234pub const NOTIFICATIONS_PROGRESS: &str = "notifications/progress";
235
236impl McpMethod {
237 pub fn parse(method: &str) -> Self {
238 match method {
239 INITIALIZE => Self::Initialize,
240 INITIALIZED => Self::Initialized,
241 PING => Self::Ping,
242 TOOLS_LIST => Self::ToolsList,
243 TOOLS_CALL => Self::ToolsCall,
244 RESOURCES_LIST => Self::ResourcesList,
245 RESOURCES_READ => Self::ResourcesRead,
246 RESOURCES_TEMPLATES_LIST => Self::ResourcesTemplatesList,
247 RESOURCES_SUBSCRIBE => Self::ResourcesSubscribe,
248 RESOURCES_UNSUBSCRIBE => Self::ResourcesUnsubscribe,
249 PROMPTS_LIST => Self::PromptsList,
250 PROMPTS_GET => Self::PromptsGet,
251 LOGGING_SET_LEVEL => Self::LoggingSetLevel,
252 COMPLETION_COMPLETE => Self::CompletionComplete,
253 NOTIFICATIONS_TOOLS_LIST_CHANGED => Self::NotificationsToolsListChanged,
254 NOTIFICATIONS_CANCELLED => Self::NotificationsCancelled,
255 NOTIFICATIONS_PROGRESS => Self::NotificationsProgress,
256 m if m.starts_with("notifications/") => Self::Notification(m.to_string()),
257 m => Self::Unknown(m.to_string()),
258 }
259 }
260
261 pub fn as_str(&self) -> &str {
263 match self {
264 Self::Initialize => INITIALIZE,
265 Self::Initialized => INITIALIZED,
266 Self::Ping => PING,
267 Self::ToolsList => TOOLS_LIST,
268 Self::ToolsCall => TOOLS_CALL,
269 Self::ResourcesList => RESOURCES_LIST,
270 Self::ResourcesRead => RESOURCES_READ,
271 Self::ResourcesTemplatesList => RESOURCES_TEMPLATES_LIST,
272 Self::ResourcesSubscribe => RESOURCES_SUBSCRIBE,
273 Self::ResourcesUnsubscribe => RESOURCES_UNSUBSCRIBE,
274 Self::PromptsList => PROMPTS_LIST,
275 Self::PromptsGet => PROMPTS_GET,
276 Self::LoggingSetLevel => LOGGING_SET_LEVEL,
277 Self::CompletionComplete => COMPLETION_COMPLETE,
278 Self::NotificationsToolsListChanged => NOTIFICATIONS_TOOLS_LIST_CHANGED,
279 Self::NotificationsCancelled => NOTIFICATIONS_CANCELLED,
280 Self::NotificationsProgress => NOTIFICATIONS_PROGRESS,
281 Self::Notification(m) => m.as_str(),
282 Self::Unknown(m) => m.as_str(),
283 }
284 }
285}
286
287fn parse_id(value: &Value) -> Option<JsonRpcId> {
291 match value {
292 Value::Number(n) => n.as_i64().map(JsonRpcId::Number),
293 Value::String(s) => Some(JsonRpcId::String(s.clone())),
294 _ => None,
295 }
296}
297
298fn parse_error(value: &Value) -> Option<JsonRpcError> {
300 let obj = value.as_object()?;
301 Some(JsonRpcError {
302 code: obj.get("code")?.as_i64()?,
303 message: obj.get("message")?.as_str()?.to_string(),
304 data: obj.get("data").cloned(),
305 })
306}
307
308pub fn parse_message(value: &Value) -> Option<JsonRpcMessage> {
311 let obj = value.as_object()?;
312
313 if obj.get("jsonrpc")?.as_str()? != "2.0" {
315 return None;
316 }
317
318 let id = obj.get("id").and_then(parse_id);
319 let method = obj.get("method").and_then(|m| m.as_str()).map(String::from);
320 let params = obj.get("params").cloned();
321
322 match (method, id) {
323 (Some(method), Some(id)) => Some(JsonRpcMessage::Request(JsonRpcRequest {
325 id,
326 method,
327 params,
328 })),
329 (Some(method), None) => Some(JsonRpcMessage::Notification(JsonRpcNotification {
331 method,
332 params,
333 })),
334 (None, Some(id)) => {
336 let result = obj.get("result").cloned();
337 let error = obj.get("error").and_then(parse_error);
338 Some(JsonRpcMessage::Response(JsonRpcResponse {
339 id,
340 result,
341 error,
342 }))
343 }
344 (None, None) => None,
346 }
347}
348
349#[derive(Debug)]
351pub struct ParsedBody {
352 pub messages: Vec<JsonRpcMessage>,
353 pub is_batch: bool,
354}
355
356impl ParsedBody {
357 pub fn method_str(&self) -> &str {
360 self.messages
361 .iter()
362 .find_map(|m| match m {
363 JsonRpcMessage::Request(r) => Some(r.method.as_str()),
364 JsonRpcMessage::Notification(n) => Some(n.method.as_str()),
365 _ => None,
366 })
367 .unwrap_or("unknown")
368 }
369
370 pub fn mcp_method(&self) -> McpMethod {
372 McpMethod::parse(self.method_str())
373 }
374
375 pub fn first_request_id(&self) -> Option<&JsonRpcId> {
377 self.messages.iter().find_map(|m| match m {
378 JsonRpcMessage::Request(r) => Some(&r.id),
379 _ => None,
380 })
381 }
382
383 pub fn is_notification_only(&self) -> bool {
385 self.messages
386 .iter()
387 .all(|m| matches!(m, JsonRpcMessage::Notification(_)))
388 }
389
390 pub fn detail(&self) -> Option<String> {
395 let params = self.first_params()?;
396 let method = self.mcp_method();
397 match method {
398 McpMethod::ToolsCall => params.get("name")?.as_str().map(String::from),
399 McpMethod::ResourcesRead => params.get("uri")?.as_str().map(String::from),
400 McpMethod::PromptsGet => params.get("name")?.as_str().map(String::from),
401 McpMethod::NotificationsCancelled => {
402 params.get("requestId").map(|v| match v {
404 Value::String(s) => s.clone(),
405 Value::Number(n) => n.to_string(),
406 _ => v.to_string(),
407 })
408 }
409 McpMethod::NotificationsProgress => {
410 params.get("progressToken").map(|v| match v {
412 Value::String(s) => s.clone(),
413 Value::Number(n) => n.to_string(),
414 _ => v.to_string(),
415 })
416 }
417 _ => None,
418 }
419 }
420
421 pub fn first_params(&self) -> Option<&Value> {
423 self.messages.iter().find_map(|m| match m {
424 JsonRpcMessage::Request(r) => r.params.as_ref(),
425 JsonRpcMessage::Notification(n) => n.params.as_ref(),
426 _ => None,
427 })
428 }
429}
430
431pub fn parse_body(body: &[u8]) -> Option<ParsedBody> {
434 let value: Value = serde_json::from_slice(body).ok()?;
435
436 if let Some(arr) = value.as_array() {
437 let messages: Vec<_> = arr.iter().filter_map(parse_message).collect();
439 if messages.is_empty() {
440 return None;
441 }
442 Some(ParsedBody {
443 messages,
444 is_batch: true,
445 })
446 } else {
447 let msg = parse_message(&value)?;
449 Some(ParsedBody {
450 messages: vec![msg],
451 is_batch: false,
452 })
453 }
454}
455
456#[cfg(test)]
459#[allow(non_snake_case)]
460mod tests {
461 use super::*;
462 use serde_json::json;
463
464 #[test]
467 fn parse_message__request() {
468 let val = json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "get_weather"}});
469 let msg = parse_message(&val).unwrap();
470 match msg {
471 JsonRpcMessage::Request(r) => {
472 assert_eq!(r.id, JsonRpcId::Number(1));
473 assert_eq!(r.method, "tools/call");
474 assert!(r.params.is_some());
475 }
476 _ => panic!("expected Request"),
477 }
478 }
479
480 #[test]
481 fn parse_message__request_string_id() {
482 let val = json!({"jsonrpc": "2.0", "id": "abc-123", "method": "initialize"});
483 let msg = parse_message(&val).unwrap();
484 match msg {
485 JsonRpcMessage::Request(r) => {
486 assert_eq!(r.id, JsonRpcId::String("abc-123".into()));
487 assert_eq!(r.method, "initialize");
488 }
489 _ => panic!("expected Request"),
490 }
491 }
492
493 #[test]
494 fn parse_message__notification() {
495 let val = json!({"jsonrpc": "2.0", "method": "notifications/initialized"});
496 let msg = parse_message(&val).unwrap();
497 match msg {
498 JsonRpcMessage::Notification(n) => {
499 assert_eq!(n.method, "notifications/initialized");
500 assert!(n.params.is_none());
501 }
502 _ => panic!("expected Notification"),
503 }
504 }
505
506 #[test]
507 fn parse_message__response_result() {
508 let val = json!({"jsonrpc": "2.0", "id": 1, "result": {"tools": []}});
509 let msg = parse_message(&val).unwrap();
510 match msg {
511 JsonRpcMessage::Response(r) => {
512 assert_eq!(r.id, JsonRpcId::Number(1));
513 assert!(r.result.is_some());
514 assert!(r.error.is_none());
515 }
516 _ => panic!("expected Response"),
517 }
518 }
519
520 #[test]
521 fn parse_message__response_error() {
522 let val = json!({"jsonrpc": "2.0", "id": 1, "error": {"code": -32601, "message": "Method not found"}});
523 let msg = parse_message(&val).unwrap();
524 match msg {
525 JsonRpcMessage::Response(r) => {
526 assert_eq!(r.id, JsonRpcId::Number(1));
527 assert!(r.result.is_none());
528 let err = r.error.unwrap();
529 assert_eq!(err.code, -32601);
530 assert_eq!(err.message, "Method not found");
531 }
532 _ => panic!("expected Response"),
533 }
534 }
535
536 #[test]
537 fn parse_message__rejects_wrong_version() {
538 let val = json!({"jsonrpc": "1.0", "id": 1, "method": "test"});
539 assert!(parse_message(&val).is_none());
540 }
541
542 #[test]
543 fn parse_message__rejects_missing_jsonrpc() {
544 let val = json!({"id": 1, "method": "test"});
545 assert!(parse_message(&val).is_none());
546 }
547
548 #[test]
549 fn parse_message__rejects_no_method_no_id() {
550 let val = json!({"jsonrpc": "2.0"});
551 assert!(parse_message(&val).is_none());
552 }
553
554 #[test]
555 fn parse_message__rejects_non_object() {
556 let val = json!("hello");
557 assert!(parse_message(&val).is_none());
558 }
559
560 #[test]
561 fn parse_message__rejects_oauth_register() {
562 let val = json!({
563 "client_name": "My App",
564 "redirect_uris": ["https://example.com/callback"],
565 "grant_types": ["authorization_code"]
566 });
567 assert!(parse_message(&val).is_none());
568 }
569
570 #[test]
573 fn parse_body__single_request() {
574 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
575 let parsed = parse_body(body).unwrap();
576 assert!(!parsed.is_batch);
577 assert_eq!(parsed.messages.len(), 1);
578 assert_eq!(parsed.method_str(), "tools/list");
579 assert_eq!(parsed.mcp_method(), McpMethod::ToolsList);
580 }
581
582 #[test]
583 fn parse_body__batch_requests() {
584 let body = br#"[
585 {"jsonrpc":"2.0","id":1,"method":"tools/list"},
586 {"jsonrpc":"2.0","id":2,"method":"resources/list"}
587 ]"#;
588 let parsed = parse_body(body).unwrap();
589 assert!(parsed.is_batch);
590 assert_eq!(parsed.messages.len(), 2);
591 assert_eq!(parsed.method_str(), "tools/list");
592 }
593
594 #[test]
595 fn parse_body__notification_only() {
596 let body = br#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#;
597 let parsed = parse_body(body).unwrap();
598 assert!(parsed.is_notification_only());
599 assert_eq!(parsed.mcp_method(), McpMethod::Initialized);
600 }
601
602 #[test]
603 fn parse_body__mixed_batch() {
604 let body = br#"[
605 {"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":1}},
606 {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather"}}
607 ]"#;
608 let parsed = parse_body(body).unwrap();
609 assert!(parsed.is_batch);
610 assert!(!parsed.is_notification_only());
611 assert_eq!(parsed.first_request_id(), Some(&JsonRpcId::Number(2)));
612 }
613
614 #[test]
615 fn parse_body__rejects_empty_batch() {
616 let body = b"[]";
617 assert!(parse_body(body).is_none());
618 }
619
620 #[test]
621 fn parse_body__rejects_invalid_json() {
622 assert!(parse_body(b"not json").is_none());
623 }
624
625 #[test]
626 fn parse_body__rejects_non_jsonrpc() {
627 let body = br#"{"grant_type":"client_credentials","client_id":"abc"}"#;
628 assert!(parse_body(body).is_none());
629 }
630
631 #[test]
632 fn parse_body__rejects_batch_of_non_jsonrpc() {
633 let body = br#"[{"foo":"bar"},{"baz":1}]"#;
634 assert!(parse_body(body).is_none());
635 }
636
637 #[test]
640 fn mcp_method__known_methods() {
641 assert_eq!(McpMethod::parse("initialize"), McpMethod::Initialize);
642 assert_eq!(McpMethod::parse("tools/call"), McpMethod::ToolsCall);
643 assert_eq!(McpMethod::parse("tools/list"), McpMethod::ToolsList);
644 assert_eq!(McpMethod::parse("resources/read"), McpMethod::ResourcesRead);
645 assert_eq!(McpMethod::parse("resources/list"), McpMethod::ResourcesList);
646 assert_eq!(
647 McpMethod::parse("resources/templates/list"),
648 McpMethod::ResourcesTemplatesList
649 );
650 assert_eq!(McpMethod::parse("prompts/list"), McpMethod::PromptsList);
651 assert_eq!(McpMethod::parse("prompts/get"), McpMethod::PromptsGet);
652 assert_eq!(McpMethod::parse("ping"), McpMethod::Ping);
653 assert_eq!(
654 McpMethod::parse("logging/setLevel"),
655 McpMethod::LoggingSetLevel
656 );
657 assert_eq!(
658 McpMethod::parse("completion/complete"),
659 McpMethod::CompletionComplete
660 );
661 assert_eq!(
662 McpMethod::parse("notifications/tools/list_changed"),
663 McpMethod::NotificationsToolsListChanged
664 );
665 assert_eq!(
666 McpMethod::parse("notifications/cancelled"),
667 McpMethod::NotificationsCancelled
668 );
669 assert_eq!(
670 McpMethod::parse("notifications/progress"),
671 McpMethod::NotificationsProgress
672 );
673 }
674
675 #[test]
676 fn mcp_method__notifications() {
677 assert_eq!(
678 McpMethod::parse("notifications/initialized"),
679 McpMethod::Initialized
680 );
681 assert_eq!(
683 McpMethod::parse("notifications/cancelled"),
684 McpMethod::NotificationsCancelled
685 );
686 assert_eq!(
687 McpMethod::parse("notifications/progress"),
688 McpMethod::NotificationsProgress
689 );
690 assert_eq!(
691 McpMethod::parse("notifications/tools/list_changed"),
692 McpMethod::NotificationsToolsListChanged
693 );
694 assert_eq!(
696 McpMethod::parse("notifications/resources/updated"),
697 McpMethod::Notification("notifications/resources/updated".into())
698 );
699 }
700
701 #[test]
702 fn mcp_method__unknown() {
703 assert_eq!(
704 McpMethod::parse("custom/method"),
705 McpMethod::Unknown("custom/method".into())
706 );
707 }
708
709 #[test]
710 fn mcp_method__as_str_roundtrip() {
711 let methods = [
712 "initialize",
713 "notifications/initialized",
714 "ping",
715 "tools/list",
716 "tools/call",
717 "resources/list",
718 "resources/read",
719 "resources/templates/list",
720 "prompts/list",
721 "prompts/get",
722 "logging/setLevel",
723 "completion/complete",
724 "notifications/tools/list_changed",
725 "notifications/cancelled",
726 "notifications/progress",
727 ];
728 for m in methods {
729 assert_eq!(McpMethod::parse(m).as_str(), m);
730 }
731 }
732
733 #[test]
736 fn jsonrpc_id__display() {
737 assert_eq!(JsonRpcId::Number(42).to_string(), "42");
738 assert_eq!(JsonRpcId::String("abc".into()).to_string(), "abc");
739 }
740
741 #[test]
744 fn parsed_body__first_params_from_request() {
745 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo"}}"#;
746 let parsed = parse_body(body).unwrap();
747 let params = parsed.first_params().unwrap();
748 assert_eq!(params["name"], "echo");
749 }
750
751 #[test]
752 fn parsed_body__first_params_none_for_response() {
753 let body = br#"{"jsonrpc":"2.0","id":1,"result":{}}"#;
754 let parsed = parse_body(body).unwrap();
755 assert!(parsed.first_params().is_none());
756 }
757
758 #[test]
759 fn parsed_body__method_str_defaults_to_unknown() {
760 let body = br#"{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}"#;
761 let parsed = parse_body(body).unwrap();
762 assert_eq!(parsed.method_str(), "unknown");
763 }
764
765 #[test]
768 fn parsed_body__detail_tools_call() {
769 let body =
770 br#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_weather"}}"#;
771 let parsed = parse_body(body).unwrap();
772 assert_eq!(parsed.detail().as_deref(), Some("get_weather"));
773 }
774
775 #[test]
776 fn parsed_body__detail_resources_read() {
777 let body = br#"{"jsonrpc":"2.0","id":1,"method":"resources/read","params":{"uri":"ui://widget/clock.html"}}"#;
778 let parsed = parse_body(body).unwrap();
779 assert_eq!(parsed.detail().as_deref(), Some("ui://widget/clock.html"));
780 }
781
782 #[test]
783 fn parsed_body__detail_none_for_tools_list() {
784 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
785 let parsed = parse_body(body).unwrap();
786 assert!(parsed.detail().is_none());
787 }
788
789 #[test]
790 fn parsed_body__detail_notifications_cancelled() {
791 let body = br#"{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":"req-42","reason":"timeout"}}"#;
792 let parsed = parse_body(body).unwrap();
793 assert_eq!(parsed.detail().as_deref(), Some("req-42"));
794 }
795
796 #[test]
797 fn parsed_body__detail_cancelled_numeric_id() {
798 let body =
799 br#"{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":7}}"#;
800 let parsed = parse_body(body).unwrap();
801 assert_eq!(parsed.detail().as_deref(), Some("7"));
802 }
803
804 #[test]
805 fn parsed_body__detail_notifications_progress() {
806 let body = br#"{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"tok-1","progress":50,"total":100}}"#;
807 let parsed = parse_body(body).unwrap();
808 assert_eq!(parsed.detail().as_deref(), Some("tok-1"));
809 }
810
811 #[test]
812 fn parsed_body__detail_progress_numeric_token() {
813 let body = br#"{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":99,"progress":10}}"#;
814 let parsed = parse_body(body).unwrap();
815 assert_eq!(parsed.detail().as_deref(), Some("99"));
816 }
817
818 #[test]
821 fn error_code__labels() {
822 assert_eq!(error_code::label(error_code::PARSE_ERROR), "Parse error");
823 assert_eq!(
824 error_code::label(error_code::METHOD_NOT_FOUND),
825 "Method not found"
826 );
827 assert_eq!(
828 error_code::label(error_code::INVALID_PARAMS),
829 "Invalid params"
830 );
831 assert_eq!(
832 error_code::label(error_code::INTERNAL_ERROR),
833 "Internal error"
834 );
835 assert_eq!(error_code::label(-32000), "Server error");
836 assert_eq!(error_code::label(-32099), "Server error");
837 assert_eq!(error_code::label(42), "Unknown error");
838 }
839
840 #[test]
843 fn error_response__numeric_id() {
844 let body = error_response(&json!(1), error_code::METHOD_NOT_FOUND, "Method not found");
845 let parsed: Value = serde_json::from_slice(&body).unwrap();
846 assert_eq!(parsed["jsonrpc"], "2.0");
847 assert_eq!(parsed["id"], 1);
848 assert_eq!(parsed["error"]["code"], -32601);
849 assert_eq!(parsed["error"]["message"], "Method not found");
850 }
851
852 #[test]
853 fn error_response__null_id() {
854 let body = error_response(&Value::Null, error_code::PARSE_ERROR, "Parse error");
855 let parsed: Value = serde_json::from_slice(&body).unwrap();
856 assert_eq!(parsed["id"], Value::Null);
857 assert_eq!(parsed["error"]["code"], -32700);
858 }
859
860 #[test]
863 fn extract_error_code__from_error_response() {
864 let val = json!({"jsonrpc": "2.0", "id": 1, "error": {"code": -32601, "message": "Method not found"}});
865 let (code, msg) = extract_error_code(&val).unwrap();
866 assert_eq!(code, -32601);
867 assert_eq!(msg, "Method not found");
868 }
869
870 #[test]
871 fn extract_error_code__none_for_success() {
872 let val = json!({"jsonrpc": "2.0", "id": 1, "result": {"tools": []}});
873 assert!(extract_error_code(&val).is_none());
874 }
875
876 #[test]
879 fn needs_response_buffering__schema_methods_buffered() {
880 assert!(McpMethod::Initialize.needs_response_buffering());
883 assert!(McpMethod::ToolsList.needs_response_buffering());
884 assert!(McpMethod::ResourcesList.needs_response_buffering());
885 assert!(McpMethod::ResourcesTemplatesList.needs_response_buffering());
886 assert!(McpMethod::PromptsList.needs_response_buffering());
887 }
888
889 #[test]
890 fn needs_response_buffering__non_schema_methods_streamed() {
891 assert!(!McpMethod::Ping.needs_response_buffering());
892 assert!(!McpMethod::PromptsGet.needs_response_buffering());
893 assert!(!McpMethod::CompletionComplete.needs_response_buffering());
894 assert!(!McpMethod::LoggingSetLevel.needs_response_buffering());
895 }
896}