1use std::io::{self, BufRead, Write};
7
8use crate::mcp::server::{
9 McpServer, SUPPORTED_PROTOCOL_VERSIONS, request_protocol_version, shape_modern_result,
10};
11
12const MAX_LINE_BYTES: usize = 16 * 1024 * 1024;
17
18const ERR_NOT_INITIALIZED: i32 = -32002;
20
21fn read_limited_line<R: BufRead>(
29 reader: &mut R,
30 max: usize,
31 buf: &mut Vec<u8>,
32) -> io::Result<usize> {
33 buf.clear();
34 let mut total = 0usize;
35 loop {
36 let available = reader.fill_buf()?;
37 if available.is_empty() {
38 return Ok(total); }
40 let newline = available.iter().position(|&b| b == b'\n');
41 let take = newline.map_or(available.len(), |i| i + 1);
42 if total + take > max {
43 return Err(io::Error::new(
44 io::ErrorKind::InvalidData,
45 format!("JSON-RPC line exceeds {max} bytes"),
46 ));
47 }
48 buf.extend_from_slice(&available[..take]);
49 reader.consume(take);
50 total += take;
51 if newline.is_some() {
52 return Ok(total);
53 }
54 }
55}
56
57pub struct JsonRpcDispatcher<'a> {
59 server: &'a McpServer,
60}
61
62impl<'a> JsonRpcDispatcher<'a> {
63 pub fn new(server: &'a McpServer) -> Self {
65 Self { server }
66 }
67
68 pub fn dispatch_authenticated(
73 &self,
74 request: &str,
75 auth_header: Option<&str>,
76 ) -> Option<String> {
77 let provided = auth_header.unwrap_or("");
78 if !self.server.check_auth(provided) {
79 let id = serde_json::from_str::<serde_json::Value>(request.trim())
82 .ok()
83 .and_then(|v| v.get("id").cloned())
84 .unwrap_or(serde_json::Value::Null);
85 return Some(self.error_response(id, -32001, "Unauthorized"));
86 }
87 self.dispatch(request)
88 }
89
90 pub fn dispatch(&self, request: &str) -> Option<String> {
95 let trimmed = request.trim();
96 if trimmed.starts_with('[') {
97 let reqs: Vec<serde_json::Value> = match serde_json::from_str(trimmed) {
98 Ok(v) => v,
99 Err(e) => {
100 return Some(self.error_response(
101 serde_json::Value::Null,
102 -32700,
103 &format!("Parse error: {e}"),
104 ));
105 }
106 };
107 if reqs.iter().any(|r| request_protocol_version(r).is_some()) {
111 return Some(self.error_response(
112 serde_json::Value::Null,
113 -32600,
114 "Invalid Request: JSON-RPC batches are not supported by protocol revisions after 2024-11-05",
115 ));
116 }
117 let responses: Vec<String> = reqs
118 .iter()
119 .filter_map(|req| self.dispatch_single(req))
120 .collect();
121 if responses.is_empty() {
122 None
123 } else {
124 Some(format!("[{}]", responses.join(",")))
125 }
126 } else {
127 let req: serde_json::Value = match serde_json::from_str(trimmed) {
128 Ok(v) => v,
129 Err(e) => {
130 return Some(self.error_response(
131 serde_json::Value::Null,
132 -32700,
133 &format!("Parse error: {e}"),
134 ));
135 }
136 };
137 self.dispatch_single(&req)
138 }
139 }
140
141 fn dispatch_single(&self, req: &serde_json::Value) -> Option<String> {
143 if !req.is_object() {
146 return Some(self.error_response(
147 serde_json::Value::Null,
148 -32600,
149 "Invalid Request: expected a JSON object",
150 ));
151 }
152 req.get("id")?;
155 let id = req.get("id").cloned().unwrap_or(serde_json::Value::Null);
157 let method = req.get("method").and_then(|v| v.as_str()).unwrap_or("");
158
159 let modern = if method == "initialize" {
163 None
164 } else {
165 request_protocol_version(req)
166 };
167 if let Some(version) = modern {
168 if let Some(err) = self.check_modern_version(&id, version) {
169 return Some(err);
170 }
171 match method {
174 "server/discover" => {
175 let mut result = self.server.discover_response();
176 shape_modern_result(version, method, &mut result);
177 return Some(self.success_response(id, result));
178 }
179 "subscriptions/listen" => return Some(self.subscription_close(&id)),
180 "ping" => {
182 return Some(self.error_response(
183 id,
184 -32601,
185 "Method not found: ping (removed in protocol 2026-07-28)",
186 ));
187 }
188 _ => {}
189 }
190 }
191
192 let result = match method {
193 "initialize" => {
194 let requested = req
195 .get("params")
196 .and_then(|p| p.get("protocolVersion"))
197 .and_then(|v| v.as_str());
198 Ok(self.server.initialize_response(requested))
199 }
200 "ping" => Ok(serde_json::json!({})),
201 "tools/list" => Ok(serde_json::json!({
202 "tools": self.server.tools()
203 })),
204 "resources/list" => Ok(serde_json::json!({
205 "resources": self.server.resources()
206 })),
207 "resources/templates/list" => Ok(serde_json::json!({
208 "resourceTemplates": []
209 })),
210 "prompts/list" => Ok(serde_json::json!({
211 "prompts": self.server.prompts()
212 })),
213 "prompts/get" => self.handle_prompt_get(req),
214 "tools/call" => return Some(self.handle_tool_call(&id, req, modern)),
215 "resources/read" => self.handle_resource_read(req),
216 _ => Err((-32601, format!("Method not found: {method}"))),
217 };
218
219 match result {
220 Ok(mut value) => {
221 if let Some(version) = modern {
222 shape_modern_result(version, method, &mut value);
223 }
224 Some(self.success_response(id, value))
225 }
226 Err((code, message)) => Some(self.error_response(id, code, &message)),
227 }
228 }
229
230 fn check_modern_version(&self, id: &serde_json::Value, requested: &str) -> Option<String> {
235 if SUPPORTED_PROTOCOL_VERSIONS.contains(&requested) {
236 return None;
237 }
238 let mut error = self.error_response(id.clone(), -32022, "Unsupported protocol version");
239 if let Ok(mut v) = serde_json::from_str::<serde_json::Value>(&error) {
241 v["error"]["data"] = serde_json::json!({
242 "supported": SUPPORTED_PROTOCOL_VERSIONS,
243 "requested": requested,
244 });
245 error = serde_json::to_string(&v).unwrap_or(error);
246 }
247 Some(error)
248 }
249
250 fn subscription_close(&self, id: &serde_json::Value) -> String {
254 let (ack, close) = self.server.subscription_ack_and_close(id);
255 format!(
256 "{}\n{}",
257 serde_json::to_string(&ack).unwrap_or_default(),
258 self.success_response(id.clone(), close)
259 )
260 }
261
262 pub async fn dispatch_async(&self, request: &str) -> Option<String> {
270 let trimmed = request.trim();
271 if trimmed.starts_with('[') {
272 let reqs: Vec<serde_json::Value> = match serde_json::from_str(trimmed) {
273 Ok(v) => v,
274 Err(e) => {
275 return Some(self.error_response(
276 serde_json::Value::Null,
277 -32700,
278 &format!("Parse error: {e}"),
279 ));
280 }
281 };
282 let mut responses: Vec<String> = Vec::with_capacity(reqs.len());
283 for req in &reqs {
284 if let Some(r) = self.dispatch_single_async(req).await {
285 responses.push(r);
286 }
287 }
288 if responses.is_empty() {
289 None
290 } else {
291 Some(format!("[{}]", responses.join(",")))
292 }
293 } else {
294 let req: serde_json::Value = match serde_json::from_str(trimmed) {
295 Ok(v) => v,
296 Err(e) => {
297 return Some(self.error_response(
298 serde_json::Value::Null,
299 -32700,
300 &format!("Parse error: {e}"),
301 ));
302 }
303 };
304 self.dispatch_single_async(&req).await
305 }
306 }
307
308 pub async fn dispatch_authenticated_async(
310 &self,
311 request: &str,
312 auth_header: Option<&str>,
313 ) -> Option<String> {
314 if !self.server.check_auth(auth_header.unwrap_or("")) {
315 let id = serde_json::from_str::<serde_json::Value>(request.trim())
316 .ok()
317 .and_then(|v| v.get("id").cloned())
318 .unwrap_or(serde_json::Value::Null);
319 return Some(self.error_response(id, -32001, "Unauthorized"));
320 }
321 self.dispatch_async(request).await
322 }
323
324 async fn dispatch_single_async(&self, req: &serde_json::Value) -> Option<String> {
325 if req.get("method").and_then(|v| v.as_str()) == Some("tools/call") {
328 req.get("id")?;
329 let id = req.get("id").cloned().unwrap_or(serde_json::Value::Null);
330 let modern = request_protocol_version(req);
331 if let Some(version) = modern
332 && let Some(err) = self.check_modern_version(&id, version)
333 {
334 return Some(err);
335 }
336 return Some(self.handle_tool_call_async(&id, req, modern).await);
337 }
338 self.dispatch_single(req)
339 }
340
341 fn check_initialized(&self, parsed: &serde_json::Value, initialized: bool) -> Option<String> {
352 if initialized || !parsed.is_object() {
353 return None;
354 }
355 if request_protocol_version(parsed).is_some() {
356 return None; }
358 if parsed.get("method").and_then(|v| v.as_str()) == Some("initialize") {
359 return None;
360 }
361 parsed.get("id")?;
363 let id = parsed.get("id").cloned().unwrap_or(serde_json::Value::Null);
364 Some(self.error_response(
365 id,
366 ERR_NOT_INITIALIZED,
367 "Server not initialized: send an initialize request first",
368 ))
369 }
370
371 pub fn run_stdio(&self) -> io::Result<()> {
385 if let Some(name) = self.server.async_only_tools().first() {
386 return Err(io::Error::new(
387 io::ErrorKind::InvalidInput,
388 format!(
389 "tool '{name}' has only an async handler; use run_stdio_async instead of run_stdio"
390 ),
391 ));
392 }
393 let stdin = io::stdin();
394 let mut reader = io::BufReader::new(stdin.lock());
395 let mut stdout = io::stdout().lock();
396 let mut initialized = false;
397 let mut raw = Vec::new();
398
399 loop {
400 let n = read_limited_line(&mut reader, MAX_LINE_BYTES, &mut raw)?;
401 if n == 0 {
402 break; }
404 let line = std::str::from_utf8(&raw).map_err(|_| {
405 io::Error::new(
406 io::ErrorKind::InvalidData,
407 "JSON-RPC line is not valid UTF-8",
408 )
409 })?;
410 let trimmed = line.trim();
411 if trimmed.is_empty() {
412 continue;
413 }
414 if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(trimmed) {
415 if let Some(err) = self.check_initialized(&parsed, initialized) {
416 writeln!(stdout, "{err}")?;
417 stdout.flush()?;
418 continue;
419 }
420 if parsed.get("method").and_then(|m| m.as_str()) == Some("initialize") {
421 initialized = true;
422 }
423 }
424 if let Some(response) = self.dispatch(trimmed) {
425 writeln!(stdout, "{response}")?;
426 stdout.flush()?;
427 }
428 }
429 Ok(())
430 }
431
432 pub async fn run_stdio_async(&self) -> io::Result<()> {
439 let stdin = io::stdin();
440 let mut reader = io::BufReader::new(stdin.lock());
441 let mut stdout = io::stdout().lock();
442 let mut initialized = false;
443 let mut raw = Vec::new();
444
445 loop {
446 let n = read_limited_line(&mut reader, MAX_LINE_BYTES, &mut raw)?;
447 if n == 0 {
448 break; }
450 let line = std::str::from_utf8(&raw).map_err(|_| {
451 io::Error::new(
452 io::ErrorKind::InvalidData,
453 "JSON-RPC line is not valid UTF-8",
454 )
455 })?;
456 let trimmed = line.trim();
457 if trimmed.is_empty() {
458 continue;
459 }
460 if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(trimmed) {
461 if let Some(err) = self.check_initialized(&parsed, initialized) {
462 writeln!(stdout, "{err}")?;
463 stdout.flush()?;
464 continue;
465 }
466 if parsed.get("method").and_then(|m| m.as_str()) == Some("initialize") {
467 initialized = true;
468 }
469 }
470 if let Some(response) = self.dispatch_async(trimmed).await {
471 writeln!(stdout, "{response}")?;
472 stdout.flush()?;
473 }
474 }
475 Ok(())
476 }
477
478 async fn handle_tool_call_async(
480 &self,
481 id: &serde_json::Value,
482 req: &serde_json::Value,
483 modern: Option<&str>,
484 ) -> String {
485 let tool_name = req
486 .get("params")
487 .and_then(|p| p.get("name"))
488 .and_then(|n| n.as_str())
489 .unwrap_or("");
490 let params = req
491 .get("params")
492 .and_then(|p| p.get("arguments"))
493 .cloned()
494 .unwrap_or(serde_json::json!(null));
495
496 if !self.server.has_tool(tool_name) {
497 return self.error_response(id.clone(), -32602, &format!("Unknown tool: {tool_name}"));
498 }
499 if let Err(e) = self.server.validate_tool_args(tool_name, ¶ms) {
500 return self.error_response(id.clone(), -32602, &e);
501 }
502
503 let mut result = match self.server.call_tool_async(tool_name, params).await {
504 Ok(result) => serde_json::json!({
505 "content": [{ "type": "text", "text": result.to_string() }],
506 "isError": false
507 }),
508 Err(e) => serde_json::json!({
509 "content": [{ "type": "text", "text": e.to_string() }],
510 "isError": true
511 }),
512 };
513 if let Some(version) = modern {
514 shape_modern_result(version, "tools/call", &mut result);
515 }
516 self.success_response(id.clone(), result)
517 }
518
519 fn handle_tool_call(
526 &self,
527 id: &serde_json::Value,
528 req: &serde_json::Value,
529 modern: Option<&str>,
530 ) -> String {
531 let tool_name = req
532 .get("params")
533 .and_then(|p| p.get("name"))
534 .and_then(|n| n.as_str())
535 .unwrap_or("");
536
537 let params = req
538 .get("params")
539 .and_then(|p| p.get("arguments"))
540 .cloned()
541 .unwrap_or(serde_json::json!(null));
542
543 if !self.server.has_tool(tool_name) {
544 return self.error_response(id.clone(), -32602, &format!("Unknown tool: {tool_name}"));
545 }
546 if let Err(e) = self.server.validate_tool_args(tool_name, ¶ms) {
547 return self.error_response(id.clone(), -32602, &e);
548 }
549
550 let mut result = match self.server.call_tool(tool_name, params) {
551 Ok(result) => serde_json::json!({
552 "content": [{ "type": "text", "text": result.to_string() }],
553 "isError": false
554 }),
555 Err(e) => serde_json::json!({
556 "content": [{ "type": "text", "text": e.to_string() }],
557 "isError": true
558 }),
559 };
560 if let Some(version) = modern {
561 shape_modern_result(version, "tools/call", &mut result);
562 }
563 self.success_response(id.clone(), result)
564 }
565
566 fn handle_prompt_get(
569 &self,
570 req: &serde_json::Value,
571 ) -> std::result::Result<serde_json::Value, (i32, String)> {
572 let name = req
573 .get("params")
574 .and_then(|p| p.get("name"))
575 .and_then(|n| n.as_str())
576 .unwrap_or("");
577 let args = req
578 .get("params")
579 .and_then(|p| p.get("arguments"))
580 .cloned()
581 .unwrap_or(serde_json::json!({}));
582 self.server
583 .get_prompt(name, args)
584 .map_err(|e| (-32602, e.to_string()))
585 }
586
587 fn handle_resource_read(
588 &self,
589 req: &serde_json::Value,
590 ) -> std::result::Result<serde_json::Value, (i32, String)> {
591 let uri = req
592 .get("params")
593 .and_then(|p| p.get("uri"))
594 .and_then(|u| u.as_str())
595 .unwrap_or("");
596 self.server
597 .read_resource(uri, serde_json::json!({}))
598 .map(|content| {
599 serde_json::json!({
600 "contents": [{
601 "uri": uri,
602 "text": content.to_string()
603 }]
604 })
605 })
606 .map_err(|e| (-32602, e.to_string()))
607 }
608
609 fn success_response(&self, id: serde_json::Value, result: serde_json::Value) -> String {
610 serde_json::to_string(&serde_json::json!({
611 "jsonrpc": "2.0",
612 "id": id,
613 "result": result,
614 }))
615 .unwrap_or_default()
616 }
617
618 fn error_response(&self, id: serde_json::Value, code: i32, message: &str) -> String {
619 serde_json::to_string(&serde_json::json!({
620 "jsonrpc": "2.0",
621 "id": id,
622 "error": {
623 "code": code,
624 "message": message,
625 }
626 }))
627 .unwrap_or_default()
628 }
629}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634 use crate::mcp::schema::ToolDescription;
635
636 fn async_only_server() -> McpServer {
639 let mut server = McpServer::new("async-server", "0.1.0");
640 server.register_tool(ToolDescription {
641 name: "search".into(),
642 description: "async search".into(),
643 input_schema: serde_json::json!({"type": "object"}),
644 });
645 server.set_async_handler("search", |p: serde_json::Value| async move { Ok(p) });
646 server
647 }
648
649 #[tokio::test]
650 async fn async_only_tool_runs_over_dispatch_async() {
651 let server = async_only_server();
652 let dispatcher = JsonRpcDispatcher::new(&server);
653 let req = r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search","arguments":{"q":"x"}}}"#;
654 let resp = dispatcher.dispatch_async(req).await.expect("response");
655 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
656 assert_eq!(parsed["result"]["isError"], false, "{parsed}");
657 assert!(
658 parsed["result"]["content"][0]["text"]
659 .as_str()
660 .unwrap()
661 .contains("\"q\""),
662 "{parsed}"
663 );
664 }
665
666 #[tokio::test]
667 async fn dispatch_async_still_serves_metadata_methods() {
668 let server = async_only_server();
669 let dispatcher = JsonRpcDispatcher::new(&server);
670 let resp = dispatcher
671 .dispatch_async(r#"{"jsonrpc":"2.0","id":7,"method":"tools/list"}"#)
672 .await
673 .expect("response");
674 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
675 assert_eq!(parsed["id"], 7);
676 assert_eq!(parsed["result"]["tools"][0]["name"], "search");
677 }
678
679 #[tokio::test]
680 async fn missing_required_argument_is_rejected_not_defaulted() {
681 let mut server = McpServer::new("s", "1.0");
682 server.register_tool(ToolDescription {
683 name: "search".into(),
684 description: "d".into(),
685 input_schema: serde_json::json!({
686 "type": "object",
687 "properties": {"query": {"type": "string"}},
688 "required": ["query"]
689 }),
690 });
691 server.set_async_handler("search", |p: serde_json::Value| async move { Ok(p) });
692 let dispatcher = JsonRpcDispatcher::new(&server);
693
694 let resp = dispatcher
696 .dispatch_async(
697 r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search"}}"#,
698 )
699 .await
700 .expect("response");
701 assert!(resp.contains("-32602"), "{resp}");
702 assert!(resp.contains("query"), "{resp}");
703
704 let ok = dispatcher
706 .dispatch_async(
707 r#"{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search","arguments":{"query":"x"}}}"#,
708 )
709 .await
710 .expect("response");
711 let parsed: serde_json::Value = serde_json::from_str(&ok).unwrap();
712 assert_eq!(parsed["result"]["isError"], false, "{ok}");
713 }
714
715 #[test]
716 fn non_object_request_gets_invalid_request_not_silence() {
717 let server = async_only_server();
718 let dispatcher = JsonRpcDispatcher::new(&server);
719 let resp = dispatcher
720 .dispatch("[1, 2]")
721 .expect("must answer, not hang");
722 assert!(resp.contains("-32600"), "{resp}");
723 }
724
725 #[test]
726 fn sync_call_of_async_only_tool_says_so() {
727 let server = async_only_server();
728 let err = server
729 .call_tool("search", serde_json::json!({}))
730 .unwrap_err()
731 .to_string();
732 assert!(err.contains("async"), "misleading error: {err}");
733 }
734
735 fn test_server() -> McpServer {
736 let mut server = McpServer::new("test-server", "0.1.0");
737 server.register_tool(ToolDescription {
738 name: "echo".into(),
739 description: "Echo input".into(),
740 input_schema: serde_json::json!({"type": "object"}),
741 });
742 server.set_handler("echo", Ok);
743 server
744 }
745
746 #[test]
747 fn dispatch_initialize() {
748 let server = test_server();
749 let dispatcher = JsonRpcDispatcher::new(&server);
750 let resp = dispatcher
751 .dispatch(r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#)
752 .unwrap();
753 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
754 assert_eq!(parsed["result"]["serverInfo"]["name"], "test-server");
755 }
756
757 #[test]
758 fn dispatch_tools_list() {
759 let server = test_server();
760 let dispatcher = JsonRpcDispatcher::new(&server);
761 let resp = dispatcher
762 .dispatch(r#"{"jsonrpc":"2.0","id":2,"method":"tools/list"}"#)
763 .unwrap();
764 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
765 assert_eq!(parsed["result"]["tools"].as_array().unwrap().len(), 1);
766 }
767
768 #[test]
769 fn dispatch_tools_call() {
770 let server = test_server();
771 let dispatcher = JsonRpcDispatcher::new(&server);
772 let req = r#"{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"msg":"hello"}}}"#;
773 let resp = dispatcher.dispatch(req).unwrap();
774 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
775 let text = parsed["result"]["content"][0]["text"].as_str().unwrap();
776 assert!(text.contains("hello"));
777 }
778
779 #[test]
780 fn dispatch_unknown_method() {
781 let server = test_server();
782 let dispatcher = JsonRpcDispatcher::new(&server);
783 let resp = dispatcher
784 .dispatch(r#"{"jsonrpc":"2.0","id":4,"method":"nonexistent"}"#)
785 .unwrap();
786 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
787 assert_eq!(parsed["error"]["code"], -32601);
788 }
789
790 #[test]
791 fn dispatch_invalid_json() {
792 let server = test_server();
793 let dispatcher = JsonRpcDispatcher::new(&server);
794 let resp = dispatcher.dispatch("not json").unwrap();
795 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
796 assert_eq!(parsed["error"]["code"], -32700);
797 }
798
799 #[test]
800 fn dispatch_unknown_tool_is_invalid_params() {
801 let server = test_server();
803 let dispatcher = JsonRpcDispatcher::new(&server);
804 let req = r#"{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"missing","arguments":{}}}"#;
805 let resp = dispatcher.dispatch(req).unwrap();
806 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
807 assert_eq!(parsed["error"]["code"], -32602);
808 }
809
810 #[test]
811 fn dispatch_ping_returns_empty_result() {
812 let server = test_server();
813 let dispatcher = JsonRpcDispatcher::new(&server);
814 let resp = dispatcher
815 .dispatch(r#"{"jsonrpc":"2.0","id":9,"method":"ping"}"#)
816 .unwrap();
817 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
818 assert_eq!(parsed["id"], 9);
819 assert!(parsed["result"].is_object());
820 assert_eq!(parsed["result"].as_object().unwrap().len(), 0);
821 }
822
823 #[test]
824 fn dispatch_preserves_string_id() {
825 let server = test_server();
826 let dispatcher = JsonRpcDispatcher::new(&server);
827 let resp = dispatcher
828 .dispatch(r#"{"jsonrpc":"2.0","id":"req-abc","method":"tools/list"}"#)
829 .unwrap();
830 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
831 assert_eq!(parsed["id"], "req-abc");
832 }
833
834 #[test]
835 fn initialize_echoes_client_protocol_version() {
836 let server = test_server();
837 let dispatcher = JsonRpcDispatcher::new(&server);
838 let resp = dispatcher
839 .dispatch(
840 r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05"}}"#,
841 )
842 .unwrap();
843 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
844 assert_eq!(parsed["result"]["protocolVersion"], "2024-11-05");
845 }
846
847 #[test]
848 fn tool_execution_error_reported_in_band() {
849 let mut server = McpServer::new("t", "1.0");
852 server.register_tool(ToolDescription {
853 name: "boom".into(),
854 description: "always fails".into(),
855 input_schema: serde_json::json!({"type": "object"}),
856 });
857 server.set_handler("boom", |_| {
858 Err(crate::error::KernelError::Config("kaboom".into()))
859 });
860 let dispatcher = JsonRpcDispatcher::new(&server);
861 let req = r#"{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"boom","arguments":{}}}"#;
862 let resp = dispatcher.dispatch(req).unwrap();
863 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
864 assert!(
865 parsed.get("error").is_none(),
866 "should not be a protocol error"
867 );
868 assert_eq!(parsed["result"]["isError"], true);
869 assert!(
870 parsed["result"]["content"][0]["text"]
871 .as_str()
872 .unwrap()
873 .contains("kaboom")
874 );
875 }
876
877 #[test]
878 fn dispatch_prompts_list_and_get() {
879 let mut server = McpServer::new("t", "1.0");
880 server.register_prompt(crate::mcp::schema::PromptDescription {
881 name: "greet".into(),
882 description: Some("Greet".into()),
883 arguments: Vec::new(),
884 });
885 server.set_prompt_handler("greet", |_| {
886 Ok(serde_json::json!({
887 "messages": [{ "role": "user", "content": { "type": "text", "text": "hi" } }]
888 }))
889 });
890 let dispatcher = JsonRpcDispatcher::new(&server);
891
892 let list = dispatcher
893 .dispatch(r#"{"jsonrpc":"2.0","id":1,"method":"prompts/list"}"#)
894 .unwrap();
895 let list: serde_json::Value = serde_json::from_str(&list).unwrap();
896 assert_eq!(list["result"]["prompts"][0]["name"], "greet");
897
898 let got = dispatcher
899 .dispatch(r#"{"jsonrpc":"2.0","id":2,"method":"prompts/get","params":{"name":"greet","arguments":{}}}"#)
900 .unwrap();
901 let got: serde_json::Value = serde_json::from_str(&got).unwrap();
902 assert_eq!(got["result"]["messages"][0]["content"]["text"], "hi");
903 }
904
905 #[test]
906 fn dispatch_resource_templates_list_is_empty() {
907 let server = test_server();
908 let dispatcher = JsonRpcDispatcher::new(&server);
909 let resp = dispatcher
910 .dispatch(r#"{"jsonrpc":"2.0","id":1,"method":"resources/templates/list"}"#)
911 .unwrap();
912 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
913 assert!(parsed["result"]["resourceTemplates"].is_array());
914 }
915
916 #[test]
917 fn notification_without_id_gets_no_response() {
918 let server = test_server();
919 let dispatcher = JsonRpcDispatcher::new(&server);
920 assert!(
922 dispatcher
923 .dispatch(r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#)
924 .is_none()
925 );
926 }
927
928 #[test]
929 fn dispatch_batch_request() {
930 let server = test_server();
931 let dispatcher = JsonRpcDispatcher::new(&server);
932 let batch = r#"[
933 {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}},
934 {"jsonrpc":"2.0","id":2,"method":"tools/list"}
935 ]"#;
936 let resp = dispatcher.dispatch(batch).unwrap();
937 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
938 let arr = parsed.as_array().unwrap();
939 assert_eq!(arr.len(), 2);
940 assert_eq!(arr[0]["result"]["serverInfo"]["name"], "test-server");
942 assert_eq!(arr[1]["result"]["tools"].as_array().unwrap().len(), 1);
944 }
945
946 #[test]
947 fn dispatch_batch_with_error() {
948 let server = test_server();
949 let dispatcher = JsonRpcDispatcher::new(&server);
950 let batch = r#"[
951 {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}},
952 {"jsonrpc":"2.0","id":2,"method":"nonexistent"}
953 ]"#;
954 let resp = dispatcher.dispatch(batch).unwrap();
955 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
956 let arr = parsed.as_array().unwrap();
957 assert_eq!(arr[0]["result"]["serverInfo"]["name"], "test-server");
958 assert_eq!(arr[1]["error"]["code"], -32601);
959 }
960
961 #[test]
962 fn dispatch_authenticated_passes_with_no_auth_configured() {
963 let server = test_server();
964 let dispatcher = JsonRpcDispatcher::new(&server);
965 let req = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
966 let resp = dispatcher.dispatch_authenticated(req, None).unwrap();
967 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
968 assert!(parsed["result"]["serverInfo"].is_object());
969 }
970
971 #[test]
972 fn dispatch_authenticated_rejects_missing_token() {
973 let server = McpServer::new("secured", "1.0").with_bearer_auth("secret-token");
974 let dispatcher = JsonRpcDispatcher::new(&server);
975 let req = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
976 let resp = dispatcher.dispatch_authenticated(req, None).unwrap();
977 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
978 assert_eq!(parsed["error"]["code"], -32001);
979 }
980
981 #[test]
982 fn dispatch_authenticated_rejects_wrong_token() {
983 let server = McpServer::new("secured", "1.0").with_bearer_auth("correct-token");
984 let dispatcher = JsonRpcDispatcher::new(&server);
985 let req = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
986 let resp = dispatcher
987 .dispatch_authenticated(req, Some("Bearer wrong-token"))
988 .unwrap();
989 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
990 assert_eq!(parsed["error"]["code"], -32001);
991 }
992
993 #[test]
994 fn dispatch_authenticated_passes_with_correct_token() {
995 let server = McpServer::new("secured", "1.0").with_bearer_auth("correct-token");
996 let dispatcher = JsonRpcDispatcher::new(&server);
997 let req = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
998 let resp = dispatcher
999 .dispatch_authenticated(req, Some("Bearer correct-token"))
1000 .unwrap();
1001 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
1002 assert!(parsed["result"]["serverInfo"].is_object());
1003 }
1004
1005 #[test]
1006 fn read_limited_line_bounds_memory_before_newline() {
1007 let mut reader = io::Cursor::new(vec![b'x'; 40]);
1010 let mut buf = Vec::new();
1011 let err = read_limited_line(&mut reader, 10, &mut buf).unwrap_err();
1012 assert_eq!(err.kind(), io::ErrorKind::InvalidData, "{err}");
1013 assert!(buf.len() <= 10, "buffer grew past the cap: {}", buf.len());
1014 }
1015
1016 #[test]
1017 fn read_limited_line_reads_normal_lines() {
1018 let mut reader = io::Cursor::new(b"{\"a\":1}\n{\"b\":2}\ntrailing".to_vec());
1019 let mut buf = Vec::new();
1020 assert_eq!(read_limited_line(&mut reader, 1024, &mut buf).unwrap(), 8);
1021 assert_eq!(buf, b"{\"a\":1}\n");
1022 assert_eq!(read_limited_line(&mut reader, 1024, &mut buf).unwrap(), 8);
1023 assert_eq!(buf, b"{\"b\":2}\n");
1024 assert_eq!(read_limited_line(&mut reader, 1024, &mut buf).unwrap(), 8);
1026 assert_eq!(buf, b"trailing");
1027 assert_eq!(read_limited_line(&mut reader, 1024, &mut buf).unwrap(), 0);
1028 }
1029
1030 #[test]
1031 fn pre_initialize_requests_are_rejected() {
1032 let server = test_server();
1033 let dispatcher = JsonRpcDispatcher::new(&server);
1034 let parsed: serde_json::Value =
1035 serde_json::from_str(r#"{"jsonrpc":"2.0","id":5,"method":"tools/list"}"#).unwrap();
1036 let err = dispatcher
1037 .check_initialized(&parsed, false)
1038 .expect("gate must reject");
1039 assert!(err.contains("-32002"), "{err}");
1040
1041 let init: serde_json::Value =
1043 serde_json::from_str(r#"{"jsonrpc":"2.0","id":1,"method":"initialize"}"#).unwrap();
1044 assert!(dispatcher.check_initialized(&init, false).is_none());
1045 assert!(dispatcher.check_initialized(&parsed, true).is_none());
1047
1048 let notif: serde_json::Value =
1050 serde_json::from_str(r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#)
1051 .unwrap();
1052 assert!(dispatcher.check_initialized(¬if, false).is_none());
1053
1054 let modern: serde_json::Value = serde_json::from_str(
1057 r#"{"jsonrpc":"2.0","id":9,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}"#,
1058 )
1059 .unwrap();
1060 assert!(dispatcher.check_initialized(&modern, false).is_none());
1061 }
1062
1063 fn modern_request(method: &str, id: i64, version: &str) -> String {
1065 format!(
1066 r#"{{"jsonrpc":"2.0","id":{id},"method":"{method}","params":{{"_meta":{{"io.modelcontextprotocol/protocolVersion":"{version}"}}}}}}"#
1067 )
1068 }
1069
1070 #[test]
1071 fn modern_server_discover_answers_versions_and_identity() {
1072 let server = test_server();
1073 let dispatcher = JsonRpcDispatcher::new(&server);
1074 let resp = dispatcher
1075 .dispatch(&modern_request("server/discover", 1, "2026-07-28"))
1076 .unwrap();
1077 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
1078 assert_eq!(
1079 parsed["result"]["supportedVersions"][0],
1080 crate::mcp::server::LATEST_PROTOCOL_VERSION
1081 );
1082 assert_eq!(
1083 parsed["result"]["_meta"]["io.modelcontextprotocol/serverInfo"]["name"],
1084 "test-server"
1085 );
1086 assert!(parsed["result"]["ttlMs"].is_u64(), "{parsed}");
1088 assert_eq!(parsed["result"]["resultType"], "complete");
1089 }
1090
1091 #[test]
1092 fn modern_unsupported_version_lists_supported() {
1093 let server = test_server();
1094 let dispatcher = JsonRpcDispatcher::new(&server);
1095 let resp = dispatcher
1096 .dispatch(&modern_request("tools/list", 2, "1999-01-01"))
1097 .unwrap();
1098 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
1099 assert_eq!(parsed["error"]["code"], -32022, "{parsed}");
1100 assert_eq!(parsed["error"]["data"]["requested"], "1999-01-01");
1101 assert!(
1102 parsed["error"]["data"]["supported"]
1103 .as_array()
1104 .unwrap()
1105 .contains(&serde_json::json!("2025-06-18"))
1106 );
1107 }
1108
1109 #[test]
1110 fn modern_ping_is_method_not_found() {
1111 let server = test_server();
1112 let dispatcher = JsonRpcDispatcher::new(&server);
1113 let resp = dispatcher
1114 .dispatch(&modern_request("ping", 3, "2026-07-28"))
1115 .unwrap();
1116 assert!(resp.contains("-32601"), "{resp}");
1117 let resp = dispatcher
1119 .dispatch(r#"{"jsonrpc":"2.0","id":4,"method":"ping"}"#)
1120 .unwrap();
1121 assert!(resp.contains("result"), "{resp}");
1122 }
1123
1124 #[test]
1125 fn modern_results_carry_result_type_and_cache_fields() {
1126 let mut server = test_server();
1127 server.set_handler("echo", Ok);
1128 let dispatcher = JsonRpcDispatcher::new(&server);
1129
1130 let list = dispatcher
1131 .dispatch(&modern_request("tools/list", 1, "2026-07-28"))
1132 .unwrap();
1133 let parsed: serde_json::Value = serde_json::from_str(&list).unwrap();
1134 assert_eq!(parsed["result"]["resultType"], "complete");
1135 assert!(parsed["result"]["ttlMs"].is_u64());
1136 assert_eq!(parsed["result"]["cacheScope"], "private");
1137
1138 let call = format!(
1139 r#"{{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{{"name":"echo","arguments":{{}},"_meta":{{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}}}}"#
1140 );
1141 let call = dispatcher.dispatch(&call).unwrap();
1142 let parsed: serde_json::Value = serde_json::from_str(&call).unwrap();
1143 assert_eq!(parsed["result"]["resultType"], "complete", "{parsed}");
1144 assert!(
1145 parsed["result"].get("ttlMs").is_none(),
1146 "tools/call is not cacheable"
1147 );
1148
1149 let legacy_shaped = dispatcher
1152 .dispatch(&modern_request("tools/list", 3, "2025-06-18"))
1153 .unwrap();
1154 let parsed: serde_json::Value = serde_json::from_str(&legacy_shaped).unwrap();
1155 assert!(parsed["result"].get("resultType").is_none(), "{parsed}");
1156 }
1157
1158 #[test]
1159 fn modern_subscriptions_listen_acks_then_closes() {
1160 let server = test_server();
1161 let dispatcher = JsonRpcDispatcher::new(&server);
1162 let resp = dispatcher
1163 .dispatch(&modern_request("subscriptions/listen", 7, "2026-07-28"))
1164 .unwrap();
1165 let lines: Vec<&str> = resp.lines().collect();
1168 assert_eq!(lines.len(), 2, "{resp}");
1169 let ack: serde_json::Value = serde_json::from_str(lines[0]).unwrap();
1170 assert_eq!(ack["method"], "notifications/subscriptions/acknowledged");
1171 assert_eq!(ack["params"]["notifications"], serde_json::json!({}));
1173 assert_eq!(
1174 ack["params"]["_meta"]["io.modelcontextprotocol/subscriptionId"],
1175 7
1176 );
1177 let close: serde_json::Value = serde_json::from_str(lines[1]).unwrap();
1178 assert_eq!(close["id"], 7);
1179 assert_eq!(close["result"]["resultType"], "complete");
1180 assert_eq!(
1181 close["result"]["_meta"]["io.modelcontextprotocol/subscriptionId"],
1182 7
1183 );
1184 }
1185
1186 #[test]
1187 fn modern_request_inside_batch_rejects_the_batch() {
1188 let server = test_server();
1189 let dispatcher = JsonRpcDispatcher::new(&server);
1190 let batch = format!("[{}]", modern_request("tools/list", 1, "2026-07-28"));
1191 let resp = dispatcher.dispatch(&batch).unwrap();
1192 assert!(resp.contains("-32600"), "{resp}");
1193 }
1194}