1use std::io::{self, BufRead, Write};
7
8use crate::mcp::server::McpServer;
9
10const MAX_LINE_BYTES: usize = 16 * 1024 * 1024;
14
15pub struct JsonRpcDispatcher<'a> {
17 server: &'a McpServer,
18}
19
20impl<'a> JsonRpcDispatcher<'a> {
21 pub fn new(server: &'a McpServer) -> Self {
23 Self { server }
24 }
25
26 pub fn dispatch_authenticated(
31 &self,
32 request: &str,
33 auth_header: Option<&str>,
34 ) -> Option<String> {
35 let provided = auth_header.unwrap_or("");
36 if !self.server.check_auth(provided) {
37 let id = serde_json::from_str::<serde_json::Value>(request.trim())
40 .ok()
41 .and_then(|v| v.get("id").cloned())
42 .unwrap_or(serde_json::Value::Null);
43 return Some(self.error_response(id, -32001, "Unauthorized"));
44 }
45 self.dispatch(request)
46 }
47
48 pub fn dispatch(&self, request: &str) -> Option<String> {
50 let trimmed = request.trim();
51 if trimmed.starts_with('[') {
52 let reqs: Vec<serde_json::Value> = match serde_json::from_str(trimmed) {
54 Ok(v) => v,
55 Err(e) => {
56 return Some(self.error_response(
57 serde_json::Value::Null,
58 -32700,
59 &format!("Parse error: {e}"),
60 ));
61 }
62 };
63 let responses: Vec<String> = reqs
64 .iter()
65 .filter_map(|req| self.dispatch_single(req))
66 .collect();
67 if responses.is_empty() {
68 None
69 } else {
70 Some(format!("[{}]", responses.join(",")))
71 }
72 } else {
73 let req: serde_json::Value = match serde_json::from_str(trimmed) {
74 Ok(v) => v,
75 Err(e) => {
76 return Some(self.error_response(
77 serde_json::Value::Null,
78 -32700,
79 &format!("Parse error: {e}"),
80 ));
81 }
82 };
83 self.dispatch_single(&req)
84 }
85 }
86
87 fn dispatch_single(&self, req: &serde_json::Value) -> Option<String> {
89 if !req.is_object() {
92 return Some(self.error_response(
93 serde_json::Value::Null,
94 -32600,
95 "Invalid Request: expected a JSON object",
96 ));
97 }
98 req.get("id")?;
101 let id = req.get("id").cloned().unwrap_or(serde_json::Value::Null);
103 let method = req.get("method").and_then(|v| v.as_str()).unwrap_or("");
104
105 let result = match method {
106 "initialize" => {
107 let requested = req
108 .get("params")
109 .and_then(|p| p.get("protocolVersion"))
110 .and_then(|v| v.as_str());
111 Ok(self.server.initialize_response(requested))
112 }
113 "ping" => Ok(serde_json::json!({})),
114 "tools/list" => Ok(serde_json::json!({
115 "tools": self.server.tools()
116 })),
117 "resources/list" => Ok(serde_json::json!({
118 "resources": self.server.resources()
119 })),
120 "resources/templates/list" => Ok(serde_json::json!({
121 "resourceTemplates": []
122 })),
123 "prompts/list" => Ok(serde_json::json!({
124 "prompts": self.server.prompts()
125 })),
126 "prompts/get" => self.handle_prompt_get(req),
127 "tools/call" => return Some(self.handle_tool_call(&id, req)),
128 "resources/read" => self.handle_resource_read(req),
129 _ => Err((-32601, format!("Method not found: {method}"))),
130 };
131
132 match result {
133 Ok(value) => Some(self.success_response(id, value)),
134 Err((code, message)) => Some(self.error_response(id, code, &message)),
135 }
136 }
137
138 pub async fn dispatch_async(&self, request: &str) -> Option<String> {
146 let trimmed = request.trim();
147 if trimmed.starts_with('[') {
148 let reqs: Vec<serde_json::Value> = match serde_json::from_str(trimmed) {
149 Ok(v) => v,
150 Err(e) => {
151 return Some(self.error_response(
152 serde_json::Value::Null,
153 -32700,
154 &format!("Parse error: {e}"),
155 ));
156 }
157 };
158 let mut responses: Vec<String> = Vec::with_capacity(reqs.len());
159 for req in &reqs {
160 if let Some(r) = self.dispatch_single_async(req).await {
161 responses.push(r);
162 }
163 }
164 if responses.is_empty() {
165 None
166 } else {
167 Some(format!("[{}]", responses.join(",")))
168 }
169 } else {
170 let req: serde_json::Value = match serde_json::from_str(trimmed) {
171 Ok(v) => v,
172 Err(e) => {
173 return Some(self.error_response(
174 serde_json::Value::Null,
175 -32700,
176 &format!("Parse error: {e}"),
177 ));
178 }
179 };
180 self.dispatch_single_async(&req).await
181 }
182 }
183
184 pub async fn dispatch_authenticated_async(
186 &self,
187 request: &str,
188 auth_header: Option<&str>,
189 ) -> Option<String> {
190 if !self.server.check_auth(auth_header.unwrap_or("")) {
191 let id = serde_json::from_str::<serde_json::Value>(request.trim())
192 .ok()
193 .and_then(|v| v.get("id").cloned())
194 .unwrap_or(serde_json::Value::Null);
195 return Some(self.error_response(id, -32001, "Unauthorized"));
196 }
197 self.dispatch_async(request).await
198 }
199
200 async fn dispatch_single_async(&self, req: &serde_json::Value) -> Option<String> {
201 if req.get("method").and_then(|v| v.as_str()) == Some("tools/call") {
204 req.get("id")?;
205 let id = req.get("id").cloned().unwrap_or(serde_json::Value::Null);
206 return Some(self.handle_tool_call_async(&id, req).await);
207 }
208 self.dispatch_single(req)
209 }
210
211 pub fn run_stdio(&self) -> io::Result<()> {
222 if let Some(name) = self.server.async_only_tools().first() {
223 return Err(io::Error::new(
224 io::ErrorKind::InvalidInput,
225 format!(
226 "tool '{name}' has only an async handler; use run_stdio_async instead of run_stdio"
227 ),
228 ));
229 }
230 let stdin = io::stdin();
231 let mut stdout = io::stdout().lock();
232
233 for line in stdin.lock().lines() {
234 let line = line?;
235 if line.len() > MAX_LINE_BYTES {
236 return Err(io::Error::new(
237 io::ErrorKind::InvalidData,
238 format!("JSON-RPC line exceeds {MAX_LINE_BYTES} bytes"),
239 ));
240 }
241 let trimmed = line.trim();
242 if trimmed.is_empty() {
243 continue;
244 }
245 if let Some(response) = self.dispatch(trimmed) {
246 writeln!(stdout, "{response}")?;
247 stdout.flush()?;
248 }
249 }
250 Ok(())
251 }
252
253 pub async fn run_stdio_async(&self) -> io::Result<()> {
259 let stdin = io::stdin();
260 let mut stdout = io::stdout().lock();
261
262 for line in stdin.lock().lines() {
263 let line = line?;
264 if line.len() > MAX_LINE_BYTES {
265 return Err(io::Error::new(
266 io::ErrorKind::InvalidData,
267 format!("JSON-RPC line exceeds {MAX_LINE_BYTES} bytes"),
268 ));
269 }
270 let trimmed = line.trim();
271 if trimmed.is_empty() {
272 continue;
273 }
274 if let Some(response) = self.dispatch_async(trimmed).await {
275 writeln!(stdout, "{response}")?;
276 stdout.flush()?;
277 }
278 }
279 Ok(())
280 }
281
282 async fn handle_tool_call_async(
284 &self,
285 id: &serde_json::Value,
286 req: &serde_json::Value,
287 ) -> String {
288 let tool_name = req
289 .get("params")
290 .and_then(|p| p.get("name"))
291 .and_then(|n| n.as_str())
292 .unwrap_or("");
293 let params = req
294 .get("params")
295 .and_then(|p| p.get("arguments"))
296 .cloned()
297 .unwrap_or(serde_json::json!(null));
298
299 if !self.server.has_tool(tool_name) {
300 return self.error_response(id.clone(), -32602, &format!("Unknown tool: {tool_name}"));
301 }
302 if let Err(e) = self.server.validate_tool_args(tool_name, ¶ms) {
303 return self.error_response(id.clone(), -32602, &e);
304 }
305
306 match self.server.call_tool_async(tool_name, params).await {
307 Ok(result) => self.success_response(
308 id.clone(),
309 serde_json::json!({
310 "content": [{ "type": "text", "text": result.to_string() }],
311 "isError": false
312 }),
313 ),
314 Err(e) => self.success_response(
315 id.clone(),
316 serde_json::json!({
317 "content": [{ "type": "text", "text": e.to_string() }],
318 "isError": true
319 }),
320 ),
321 }
322 }
323
324 fn handle_tool_call(&self, id: &serde_json::Value, req: &serde_json::Value) -> String {
331 let tool_name = req
332 .get("params")
333 .and_then(|p| p.get("name"))
334 .and_then(|n| n.as_str())
335 .unwrap_or("");
336
337 let params = req
338 .get("params")
339 .and_then(|p| p.get("arguments"))
340 .cloned()
341 .unwrap_or(serde_json::json!(null));
342
343 if !self.server.has_tool(tool_name) {
344 return self.error_response(id.clone(), -32602, &format!("Unknown tool: {tool_name}"));
345 }
346 if let Err(e) = self.server.validate_tool_args(tool_name, ¶ms) {
347 return self.error_response(id.clone(), -32602, &e);
348 }
349
350 match self.server.call_tool(tool_name, params) {
351 Ok(result) => self.success_response(
352 id.clone(),
353 serde_json::json!({
354 "content": [{ "type": "text", "text": result.to_string() }],
355 "isError": false
356 }),
357 ),
358 Err(e) => self.success_response(
359 id.clone(),
360 serde_json::json!({
361 "content": [{ "type": "text", "text": e.to_string() }],
362 "isError": true
363 }),
364 ),
365 }
366 }
367
368 fn handle_prompt_get(
371 &self,
372 req: &serde_json::Value,
373 ) -> std::result::Result<serde_json::Value, (i32, String)> {
374 let name = req
375 .get("params")
376 .and_then(|p| p.get("name"))
377 .and_then(|n| n.as_str())
378 .unwrap_or("");
379 let args = req
380 .get("params")
381 .and_then(|p| p.get("arguments"))
382 .cloned()
383 .unwrap_or(serde_json::json!({}));
384 self.server
385 .get_prompt(name, args)
386 .map_err(|e| (-32602, e.to_string()))
387 }
388
389 fn handle_resource_read(
390 &self,
391 req: &serde_json::Value,
392 ) -> std::result::Result<serde_json::Value, (i32, String)> {
393 let uri = req
394 .get("params")
395 .and_then(|p| p.get("uri"))
396 .and_then(|u| u.as_str())
397 .unwrap_or("");
398 self.server
399 .read_resource(uri, serde_json::json!({}))
400 .map(|content| {
401 serde_json::json!({
402 "contents": [{
403 "uri": uri,
404 "text": content.to_string()
405 }]
406 })
407 })
408 .map_err(|e| (-32603, e.to_string()))
409 }
410
411 fn success_response(&self, id: serde_json::Value, result: serde_json::Value) -> String {
412 serde_json::to_string(&serde_json::json!({
413 "jsonrpc": "2.0",
414 "id": id,
415 "result": result,
416 }))
417 .unwrap_or_default()
418 }
419
420 fn error_response(&self, id: serde_json::Value, code: i32, message: &str) -> String {
421 serde_json::to_string(&serde_json::json!({
422 "jsonrpc": "2.0",
423 "id": id,
424 "error": {
425 "code": code,
426 "message": message,
427 }
428 }))
429 .unwrap_or_default()
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436 use crate::mcp::schema::ToolDescription;
437
438 fn async_only_server() -> McpServer {
441 let mut server = McpServer::new("async-server", "0.1.0");
442 server.register_tool(ToolDescription {
443 name: "search".into(),
444 description: "async search".into(),
445 input_schema: serde_json::json!({"type": "object"}),
446 });
447 server.set_async_handler("search", |p: serde_json::Value| async move { Ok(p) });
448 server
449 }
450
451 #[tokio::test]
452 async fn async_only_tool_runs_over_dispatch_async() {
453 let server = async_only_server();
454 let dispatcher = JsonRpcDispatcher::new(&server);
455 let req = r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search","arguments":{"q":"x"}}}"#;
456 let resp = dispatcher.dispatch_async(req).await.expect("response");
457 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
458 assert_eq!(parsed["result"]["isError"], false, "{parsed}");
459 assert!(
460 parsed["result"]["content"][0]["text"]
461 .as_str()
462 .unwrap()
463 .contains("\"q\""),
464 "{parsed}"
465 );
466 }
467
468 #[tokio::test]
469 async fn dispatch_async_still_serves_metadata_methods() {
470 let server = async_only_server();
471 let dispatcher = JsonRpcDispatcher::new(&server);
472 let resp = dispatcher
473 .dispatch_async(r#"{"jsonrpc":"2.0","id":7,"method":"tools/list"}"#)
474 .await
475 .expect("response");
476 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
477 assert_eq!(parsed["id"], 7);
478 assert_eq!(parsed["result"]["tools"][0]["name"], "search");
479 }
480
481 #[tokio::test]
482 async fn missing_required_argument_is_rejected_not_defaulted() {
483 let mut server = McpServer::new("s", "1.0");
484 server.register_tool(ToolDescription {
485 name: "search".into(),
486 description: "d".into(),
487 input_schema: serde_json::json!({
488 "type": "object",
489 "properties": {"query": {"type": "string"}},
490 "required": ["query"]
491 }),
492 });
493 server.set_async_handler("search", |p: serde_json::Value| async move { Ok(p) });
494 let dispatcher = JsonRpcDispatcher::new(&server);
495
496 let resp = dispatcher
498 .dispatch_async(
499 r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search"}}"#,
500 )
501 .await
502 .expect("response");
503 assert!(resp.contains("-32602"), "{resp}");
504 assert!(resp.contains("query"), "{resp}");
505
506 let ok = dispatcher
508 .dispatch_async(
509 r#"{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search","arguments":{"query":"x"}}}"#,
510 )
511 .await
512 .expect("response");
513 let parsed: serde_json::Value = serde_json::from_str(&ok).unwrap();
514 assert_eq!(parsed["result"]["isError"], false, "{ok}");
515 }
516
517 #[test]
518 fn non_object_request_gets_invalid_request_not_silence() {
519 let server = async_only_server();
520 let dispatcher = JsonRpcDispatcher::new(&server);
521 let resp = dispatcher
522 .dispatch("[1, 2]")
523 .expect("must answer, not hang");
524 assert!(resp.contains("-32600"), "{resp}");
525 }
526
527 #[test]
528 fn sync_call_of_async_only_tool_says_so() {
529 let server = async_only_server();
530 let err = server
531 .call_tool("search", serde_json::json!({}))
532 .unwrap_err()
533 .to_string();
534 assert!(err.contains("async"), "misleading error: {err}");
535 }
536
537 fn test_server() -> McpServer {
538 let mut server = McpServer::new("test-server", "0.1.0");
539 server.register_tool(ToolDescription {
540 name: "echo".into(),
541 description: "Echo input".into(),
542 input_schema: serde_json::json!({"type": "object"}),
543 });
544 server.set_handler("echo", Ok);
545 server
546 }
547
548 #[test]
549 fn dispatch_initialize() {
550 let server = test_server();
551 let dispatcher = JsonRpcDispatcher::new(&server);
552 let resp = dispatcher
553 .dispatch(r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#)
554 .unwrap();
555 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
556 assert_eq!(parsed["result"]["serverInfo"]["name"], "test-server");
557 }
558
559 #[test]
560 fn dispatch_tools_list() {
561 let server = test_server();
562 let dispatcher = JsonRpcDispatcher::new(&server);
563 let resp = dispatcher
564 .dispatch(r#"{"jsonrpc":"2.0","id":2,"method":"tools/list"}"#)
565 .unwrap();
566 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
567 assert_eq!(parsed["result"]["tools"].as_array().unwrap().len(), 1);
568 }
569
570 #[test]
571 fn dispatch_tools_call() {
572 let server = test_server();
573 let dispatcher = JsonRpcDispatcher::new(&server);
574 let req = r#"{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"msg":"hello"}}}"#;
575 let resp = dispatcher.dispatch(req).unwrap();
576 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
577 let text = parsed["result"]["content"][0]["text"].as_str().unwrap();
578 assert!(text.contains("hello"));
579 }
580
581 #[test]
582 fn dispatch_unknown_method() {
583 let server = test_server();
584 let dispatcher = JsonRpcDispatcher::new(&server);
585 let resp = dispatcher
586 .dispatch(r#"{"jsonrpc":"2.0","id":4,"method":"nonexistent"}"#)
587 .unwrap();
588 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
589 assert_eq!(parsed["error"]["code"], -32601);
590 }
591
592 #[test]
593 fn dispatch_invalid_json() {
594 let server = test_server();
595 let dispatcher = JsonRpcDispatcher::new(&server);
596 let resp = dispatcher.dispatch("not json").unwrap();
597 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
598 assert_eq!(parsed["error"]["code"], -32700);
599 }
600
601 #[test]
602 fn dispatch_unknown_tool_is_invalid_params() {
603 let server = test_server();
605 let dispatcher = JsonRpcDispatcher::new(&server);
606 let req = r#"{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"missing","arguments":{}}}"#;
607 let resp = dispatcher.dispatch(req).unwrap();
608 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
609 assert_eq!(parsed["error"]["code"], -32602);
610 }
611
612 #[test]
613 fn dispatch_ping_returns_empty_result() {
614 let server = test_server();
615 let dispatcher = JsonRpcDispatcher::new(&server);
616 let resp = dispatcher
617 .dispatch(r#"{"jsonrpc":"2.0","id":9,"method":"ping"}"#)
618 .unwrap();
619 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
620 assert_eq!(parsed["id"], 9);
621 assert!(parsed["result"].is_object());
622 assert_eq!(parsed["result"].as_object().unwrap().len(), 0);
623 }
624
625 #[test]
626 fn dispatch_preserves_string_id() {
627 let server = test_server();
628 let dispatcher = JsonRpcDispatcher::new(&server);
629 let resp = dispatcher
630 .dispatch(r#"{"jsonrpc":"2.0","id":"req-abc","method":"tools/list"}"#)
631 .unwrap();
632 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
633 assert_eq!(parsed["id"], "req-abc");
634 }
635
636 #[test]
637 fn initialize_echoes_client_protocol_version() {
638 let server = test_server();
639 let dispatcher = JsonRpcDispatcher::new(&server);
640 let resp = dispatcher
641 .dispatch(
642 r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05"}}"#,
643 )
644 .unwrap();
645 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
646 assert_eq!(parsed["result"]["protocolVersion"], "2024-11-05");
647 }
648
649 #[test]
650 fn tool_execution_error_reported_in_band() {
651 let mut server = McpServer::new("t", "1.0");
654 server.register_tool(ToolDescription {
655 name: "boom".into(),
656 description: "always fails".into(),
657 input_schema: serde_json::json!({"type": "object"}),
658 });
659 server.set_handler("boom", |_| {
660 Err(crate::error::KernelError::Config("kaboom".into()))
661 });
662 let dispatcher = JsonRpcDispatcher::new(&server);
663 let req = r#"{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"boom","arguments":{}}}"#;
664 let resp = dispatcher.dispatch(req).unwrap();
665 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
666 assert!(
667 parsed.get("error").is_none(),
668 "should not be a protocol error"
669 );
670 assert_eq!(parsed["result"]["isError"], true);
671 assert!(
672 parsed["result"]["content"][0]["text"]
673 .as_str()
674 .unwrap()
675 .contains("kaboom")
676 );
677 }
678
679 #[test]
680 fn dispatch_prompts_list_and_get() {
681 let mut server = McpServer::new("t", "1.0");
682 server.register_prompt(crate::mcp::schema::PromptDescription {
683 name: "greet".into(),
684 description: Some("Greet".into()),
685 arguments: Vec::new(),
686 });
687 server.set_prompt_handler("greet", |_| {
688 Ok(serde_json::json!({
689 "messages": [{ "role": "user", "content": { "type": "text", "text": "hi" } }]
690 }))
691 });
692 let dispatcher = JsonRpcDispatcher::new(&server);
693
694 let list = dispatcher
695 .dispatch(r#"{"jsonrpc":"2.0","id":1,"method":"prompts/list"}"#)
696 .unwrap();
697 let list: serde_json::Value = serde_json::from_str(&list).unwrap();
698 assert_eq!(list["result"]["prompts"][0]["name"], "greet");
699
700 let got = dispatcher
701 .dispatch(r#"{"jsonrpc":"2.0","id":2,"method":"prompts/get","params":{"name":"greet","arguments":{}}}"#)
702 .unwrap();
703 let got: serde_json::Value = serde_json::from_str(&got).unwrap();
704 assert_eq!(got["result"]["messages"][0]["content"]["text"], "hi");
705 }
706
707 #[test]
708 fn dispatch_resource_templates_list_is_empty() {
709 let server = test_server();
710 let dispatcher = JsonRpcDispatcher::new(&server);
711 let resp = dispatcher
712 .dispatch(r#"{"jsonrpc":"2.0","id":1,"method":"resources/templates/list"}"#)
713 .unwrap();
714 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
715 assert!(parsed["result"]["resourceTemplates"].is_array());
716 }
717
718 #[test]
719 fn notification_without_id_gets_no_response() {
720 let server = test_server();
721 let dispatcher = JsonRpcDispatcher::new(&server);
722 assert!(
724 dispatcher
725 .dispatch(r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#)
726 .is_none()
727 );
728 }
729
730 #[test]
731 fn dispatch_batch_request() {
732 let server = test_server();
733 let dispatcher = JsonRpcDispatcher::new(&server);
734 let batch = r#"[
735 {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}},
736 {"jsonrpc":"2.0","id":2,"method":"tools/list"}
737 ]"#;
738 let resp = dispatcher.dispatch(batch).unwrap();
739 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
740 let arr = parsed.as_array().unwrap();
741 assert_eq!(arr.len(), 2);
742 assert_eq!(arr[0]["result"]["serverInfo"]["name"], "test-server");
744 assert_eq!(arr[1]["result"]["tools"].as_array().unwrap().len(), 1);
746 }
747
748 #[test]
749 fn dispatch_batch_with_error() {
750 let server = test_server();
751 let dispatcher = JsonRpcDispatcher::new(&server);
752 let batch = r#"[
753 {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}},
754 {"jsonrpc":"2.0","id":2,"method":"nonexistent"}
755 ]"#;
756 let resp = dispatcher.dispatch(batch).unwrap();
757 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
758 let arr = parsed.as_array().unwrap();
759 assert_eq!(arr[0]["result"]["serverInfo"]["name"], "test-server");
760 assert_eq!(arr[1]["error"]["code"], -32601);
761 }
762
763 #[test]
764 fn dispatch_authenticated_passes_with_no_auth_configured() {
765 let server = test_server();
766 let dispatcher = JsonRpcDispatcher::new(&server);
767 let req = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
768 let resp = dispatcher.dispatch_authenticated(req, None).unwrap();
769 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
770 assert!(parsed["result"]["serverInfo"].is_object());
771 }
772
773 #[test]
774 fn dispatch_authenticated_rejects_missing_token() {
775 let server = McpServer::new("secured", "1.0").with_bearer_auth("secret-token");
776 let dispatcher = JsonRpcDispatcher::new(&server);
777 let req = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
778 let resp = dispatcher.dispatch_authenticated(req, None).unwrap();
779 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
780 assert_eq!(parsed["error"]["code"], -32001);
781 }
782
783 #[test]
784 fn dispatch_authenticated_rejects_wrong_token() {
785 let server = McpServer::new("secured", "1.0").with_bearer_auth("correct-token");
786 let dispatcher = JsonRpcDispatcher::new(&server);
787 let req = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
788 let resp = dispatcher
789 .dispatch_authenticated(req, Some("Bearer wrong-token"))
790 .unwrap();
791 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
792 assert_eq!(parsed["error"]["code"], -32001);
793 }
794
795 #[test]
796 fn dispatch_authenticated_passes_with_correct_token() {
797 let server = McpServer::new("secured", "1.0").with_bearer_auth("correct-token");
798 let dispatcher = JsonRpcDispatcher::new(&server);
799 let req = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
800 let resp = dispatcher
801 .dispatch_authenticated(req, Some("Bearer correct-token"))
802 .unwrap();
803 let parsed: serde_json::Value = serde_json::from_str(&resp).unwrap();
804 assert!(parsed["result"]["serverInfo"].is_object());
805 }
806}