1use std::sync::Arc;
8
9use serde::Serialize;
10use serde_json::Value;
11use tracing::debug;
12
13use crate::error::{
14 INTERNAL_ERROR, INVALID_PARAMS, INVALID_REQUEST, METHOD_NOT_FOUND, PARSE_ERROR,
15 UNSUPPORTED_PROTOCOL_VERSION,
16};
17use crate::mcp::auth::{AuthError, AuthHook};
18use crate::mcp::host::{MethodHandler, ToolDispatcher};
19use crate::mcp::modern::{
20 DiscoverResult, ModernMeta, ModernRequestMeta, PROTOCOL_VERSION_2026_07_28,
21};
22use crate::mcp::protocol::{JsonRpcRequest, JsonRpcResponse, JSONRPC_VERSION, PROTOCOL_VERSION};
23use crate::mcp::schema::{
24 InitializeRequest, InitializeResponse, ServerCapabilities, ServerInfo, ToolCall, ToolResponse,
25};
26use crate::mcp::tool::{ToolContext, ToolRegistry};
27
28fn default_supported_versions() -> Vec<String> {
31 vec![
32 PROTOCOL_VERSION_2026_07_28.to_owned(),
33 PROTOCOL_VERSION.to_owned(),
34 ]
35}
36
37pub struct McpServer<S: Send + Sync + ?Sized> {
46 name: String,
47 version: String,
48 state: Arc<S>,
49 tools: ToolRegistry<S>,
50 capabilities: ServerCapabilities,
51 instructions: Option<String>,
52 supported_versions: Vec<String>,
53 auth_hook: Option<Arc<dyn AuthHook<S>>>,
54 allowed_origins: Vec<String>,
55 tool_dispatcher: Option<Arc<dyn ToolDispatcher<S>>>,
56 method_handler: Option<Arc<dyn MethodHandler<S>>>,
57}
58
59impl<S: Send + Sync + ?Sized + 'static> McpServer<S> {
60 pub fn new(
66 name: impl Into<String>,
67 version: impl Into<String>,
68 tools: ToolRegistry<S>,
69 state: Arc<S>,
70 ) -> Self {
71 Self {
72 name: name.into(),
73 version: version.into(),
74 state,
75 tools,
76 capabilities: ServerCapabilities::tools_only(),
77 instructions: None,
78 supported_versions: default_supported_versions(),
79 auth_hook: None,
80 allowed_origins: Vec::new(),
81 tool_dispatcher: None,
82 method_handler: None,
83 }
84 }
85
86 #[must_use]
88 pub fn with_capabilities(mut self, capabilities: ServerCapabilities) -> Self {
89 self.capabilities = capabilities;
90 self
91 }
92
93 #[must_use]
95 pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
96 self.instructions = Some(instructions.into());
97 self
98 }
99
100 #[must_use]
103 pub fn with_supported_versions(mut self, versions: Vec<String>) -> Self {
104 self.supported_versions = versions;
105 self
106 }
107
108 #[must_use]
112 pub fn with_auth_hook(mut self, auth_hook: Arc<dyn AuthHook<S>>) -> Self {
113 self.auth_hook = Some(auth_hook);
114 self
115 }
116
117 #[must_use]
121 pub fn with_allowed_origins(mut self, origins: Vec<String>) -> Self {
122 self.allowed_origins = origins;
123 self
124 }
125
126 pub fn allowed_origins(&self) -> &[String] {
128 &self.allowed_origins
129 }
130
131 #[must_use]
135 pub fn with_tool_dispatcher(mut self, dispatcher: Arc<dyn ToolDispatcher<S>>) -> Self {
136 self.tool_dispatcher = Some(dispatcher);
137 self
138 }
139
140 #[must_use]
144 pub fn with_method_handler(mut self, handler: Arc<dyn MethodHandler<S>>) -> Self {
145 self.method_handler = Some(handler);
146 self
147 }
148
149 pub async fn authenticate(&self, request: &JsonRpcRequest) -> Result<ToolContext, AuthError> {
155 match &self.auth_hook {
156 Some(hook) => hook.authenticate(request, &self.state).await,
157 None => Ok(ToolContext::default()),
158 }
159 }
160
161 pub async fn handle_raw(&self, raw: &str) -> Option<JsonRpcResponse> {
166 let request: JsonRpcRequest = match serde_json::from_str(raw) {
167 Ok(req) => req,
168 Err(e) => {
169 return Some(JsonRpcResponse::error(
170 None,
171 PARSE_ERROR,
172 format!("Parse error: {e}"),
173 ));
174 }
175 };
176 self.handle_request(request).await
177 }
178
179 pub async fn handle_request(&self, request: JsonRpcRequest) -> Option<JsonRpcResponse> {
184 self.handle_request_with_context(request, &ToolContext::default())
185 .await
186 }
187
188 pub async fn handle_request_with_context(
196 &self,
197 request: JsonRpcRequest,
198 ctx: &ToolContext,
199 ) -> Option<JsonRpcResponse> {
200 if request.jsonrpc != JSONRPC_VERSION {
202 return Some(JsonRpcResponse::error(
203 request.id,
204 INVALID_REQUEST,
205 format!("Unsupported JSON-RPC version: {}", request.jsonrpc),
206 ));
207 }
208
209 if request.id.is_none() {
211 debug!(method = %request.method, "Received notification, no response");
212 return None;
213 }
214
215 let response = match ModernRequestMeta::from_params(request.params.as_ref()) {
217 ModernMeta::Malformed(reason) => {
218 JsonRpcResponse::error(request.id, INVALID_PARAMS, reason)
219 }
220 ModernMeta::Modern(meta) => self.process_modern(request, *meta, ctx).await,
221 ModernMeta::Legacy => self.process_legacy(request, ctx).await,
222 };
223
224 Some(response)
225 }
226
227 async fn process_legacy(&self, request: JsonRpcRequest, ctx: &ToolContext) -> JsonRpcResponse {
229 match request.method.as_str() {
230 "initialize" => self.handle_initialize(request.id, request.params.as_ref()),
231 "tools/list" => self.handle_tools_list(request.id, ctx).await,
232 "tools/call" => {
233 self.handle_tools_call(request.id, request.params, ctx)
234 .await
235 }
236 "server/discover" => self.handle_server_discover(request.id),
237 "ping" => JsonRpcResponse::success(request.id, Value::Object(serde_json::Map::new())),
238 other => {
239 self.handle_unknown_method(other, request.id, request.params, ctx)
240 .await
241 }
242 }
243 }
244
245 async fn process_modern(
253 &self,
254 request: JsonRpcRequest,
255 meta: ModernRequestMeta,
256 ctx: &ToolContext,
257 ) -> JsonRpcResponse {
258 if !self.supports_version(&meta.protocol_version) {
259 return self.unsupported_version_error(request.id, &meta.protocol_version);
260 }
261
262 let response = match request.method.as_str() {
263 "server/discover" => self.handle_server_discover(request.id),
264 "tools/list" => self.handle_tools_list(request.id, ctx).await,
265 "tools/call" => {
266 self.handle_tools_call(request.id, request.params, ctx)
267 .await
268 }
269 other => {
270 self.handle_unknown_method(other, request.id, request.params, ctx)
271 .await
272 }
273 };
274
275 Self::frame_modern_result(response)
276 }
277
278 fn supports_version(&self, version: &str) -> bool {
280 self.supported_versions.iter().any(|v| v == version)
281 }
282
283 fn handle_initialize(&self, id: Option<Value>, params: Option<&Value>) -> JsonRpcResponse {
290 let init = params.and_then(|p| serde_json::from_value::<InitializeRequest>(p.clone()).ok());
291
292 if let Some(req) = &init {
293 debug!(
294 client = %req.client_info.name,
295 version = %req.client_info.version,
296 protocol = %req.protocol_version,
297 "MCP client connected"
298 );
299 }
300
301 let negotiated_version = match &init {
302 Some(req) if self.supports_version(&req.protocol_version) => {
303 req.protocol_version.clone()
304 }
305 _ => PROTOCOL_VERSION.to_owned(),
306 };
307
308 let result = InitializeResponse::new(
309 negotiated_version,
310 ServerInfo::new(self.name.clone(), self.version.clone()),
311 self.capabilities.clone(),
312 self.instructions.clone(),
313 );
314
315 Self::success_or_error(id, &result)
316 }
317
318 fn handle_server_discover(&self, id: Option<Value>) -> JsonRpcResponse {
322 let discover = DiscoverResult::new(
323 self.supported_versions.clone(),
324 self.capabilities.clone(),
325 ServerInfo::new(self.name.clone(), self.version.clone()),
326 self.instructions.clone(),
327 );
328 Self::success_or_error(id, &discover)
329 }
330
331 fn unsupported_version_error(&self, id: Option<Value>, requested: &str) -> JsonRpcResponse {
334 let data = serde_json::json!({
335 "supported": self.supported_versions,
336 "requested": requested,
337 });
338 JsonRpcResponse::error_with_data(
339 id,
340 UNSUPPORTED_PROTOCOL_VERSION,
341 "Unsupported protocol version",
342 data,
343 )
344 }
345
346 fn frame_modern_result(mut response: JsonRpcResponse) -> JsonRpcResponse {
350 if let Some(obj) = response.result.as_mut().and_then(Value::as_object_mut) {
351 obj.entry("resultType")
352 .or_insert_with(|| Value::String("complete".to_owned()));
353 }
354 response
355 }
356
357 fn success_or_error<T: Serialize>(id: Option<Value>, value: &T) -> JsonRpcResponse {
359 match serde_json::to_value(value) {
360 Ok(val) => JsonRpcResponse::success(id, val),
361 Err(e) => {
362 JsonRpcResponse::error(id, INTERNAL_ERROR, format!("Serialization error: {e}"))
363 }
364 }
365 }
366
367 async fn handle_unknown_method(
371 &self,
372 method: &str,
373 id: Option<Value>,
374 params: Option<Value>,
375 ctx: &ToolContext,
376 ) -> JsonRpcResponse {
377 if let Some(handler) = &self.method_handler {
378 if let Some(response) = handler
379 .handle(method, id.clone(), params, &self.state, ctx)
380 .await
381 {
382 return response;
383 }
384 }
385 debug!(method, "Unknown MCP method");
386 JsonRpcResponse::error(id, METHOD_NOT_FOUND, format!("Method not found: {method}"))
387 }
388
389 async fn handle_tools_list(&self, id: Option<Value>, ctx: &ToolContext) -> JsonRpcResponse {
394 let definitions = match &self.tool_dispatcher {
395 Some(dispatcher) => dispatcher.list_tools(&self.state, ctx).await,
396 None => self.tools.list_definitions(),
397 };
398 match serde_json::to_value(definitions) {
399 Ok(tools) => {
400 let mut result = serde_json::Map::new();
401 result.insert("tools".to_owned(), tools);
402 JsonRpcResponse::success(id, Value::Object(result))
403 }
404 Err(e) => {
405 JsonRpcResponse::error(id, INTERNAL_ERROR, format!("Serialization error: {e}"))
406 }
407 }
408 }
409
410 async fn handle_tools_call(
412 &self,
413 id: Option<Value>,
414 params: Option<Value>,
415 ctx: &ToolContext,
416 ) -> JsonRpcResponse {
417 let call: ToolCall = match params {
418 Some(p) => match serde_json::from_value(p) {
419 Ok(cp) => cp,
420 Err(e) => {
421 return JsonRpcResponse::error(
422 id,
423 INVALID_PARAMS,
424 format!("Invalid params: {e}"),
425 );
426 }
427 },
428 None => {
429 return JsonRpcResponse::error(
430 id,
431 INVALID_PARAMS,
432 "Missing params for tools/call".to_owned(),
433 );
434 }
435 };
436
437 let arguments = call
438 .arguments
439 .unwrap_or_else(|| Value::Object(serde_json::Map::new()));
440
441 let result = match &self.tool_dispatcher {
444 Some(dispatcher) => {
445 dispatcher
446 .call_tool(&call.name, &self.state, ctx, arguments)
447 .await
448 }
449 None => {
450 self.tools
451 .execute(&call.name, &self.state, ctx, arguments)
452 .await
453 }
454 };
455
456 Self::tool_response_result(id, &result)
457 }
458
459 fn tool_response_result(id: Option<Value>, result: &ToolResponse) -> JsonRpcResponse {
462 match serde_json::to_value(result) {
463 Ok(val) => JsonRpcResponse::success(id, val),
464 Err(e) => JsonRpcResponse::error(
465 id,
466 INTERNAL_ERROR,
467 format!("Result serialization error: {e}"),
468 ),
469 }
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476 use crate::mcp::schema::{Tool, ToolResponse};
477 use crate::mcp::tool::McpTool;
478 use serde_json::json;
479
480 struct TestState;
481
482 struct PingTool;
483
484 #[async_trait::async_trait]
485 impl McpTool<TestState> for PingTool {
486 fn definition(&self) -> Tool {
487 Tool {
488 name: "ping_tool".to_owned(),
489 description: "Returns pong".to_owned(),
490 input_schema: json!({"type": "object"}),
491 annotations: None,
492 }
493 }
494
495 async fn execute(
496 &self,
497 _state: &Arc<TestState>,
498 _ctx: &ToolContext,
499 _arguments: Value,
500 ) -> ToolResponse {
501 ToolResponse::text("pong".to_owned())
502 }
503 }
504
505 fn make_server() -> McpServer<TestState> {
506 let mut registry = ToolRegistry::new();
507 registry.register(Box::new(PingTool));
508 let state = Arc::new(TestState);
509 McpServer::new("test-server", "0.1.0", registry, state)
510 }
511
512 #[tokio::test]
513 async fn handle_initialize() {
514 let server = make_server();
515 let raw = r#"{
516 "jsonrpc": "2.0",
517 "id": 1,
518 "method": "initialize",
519 "params": {
520 "protocolVersion": "2024-11-05",
521 "capabilities": {},
522 "clientInfo": { "name": "test-client" }
523 }
524 }"#;
525 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); assert_eq!(result["protocolVersion"], PROTOCOL_VERSION);
528 assert_eq!(result["serverInfo"]["name"], "test-server");
529 assert_eq!(result["serverInfo"]["version"], "0.1.0");
530 }
531
532 #[tokio::test]
533 async fn handle_initialize_without_params() {
534 let server = make_server();
535 let raw = r#"{"jsonrpc": "2.0", "id": 1, "method": "initialize"}"#;
536 let resp = server.handle_raw(raw).await.expect("response"); assert!(resp.result.is_some());
538 assert!(resp.error.is_none());
539 }
540
541 #[tokio::test]
542 async fn handle_tools_list() {
543 let server = make_server();
544 let raw = r#"{"jsonrpc": "2.0", "id": 2, "method": "tools/list"}"#;
545 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); let tools = result["tools"].as_array().expect("tools array"); assert_eq!(tools.len(), 1);
549 assert_eq!(tools[0]["name"], "ping_tool");
550 }
551
552 #[tokio::test]
553 async fn handle_tools_call() {
554 let server = make_server();
555 let raw = r#"{
556 "jsonrpc": "2.0",
557 "id": 3,
558 "method": "tools/call",
559 "params": { "name": "ping_tool", "arguments": {} }
560 }"#;
561 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); assert_eq!(result["content"][0]["text"], "pong");
564 }
565
566 #[tokio::test]
567 async fn handle_tools_call_unknown_tool() {
568 let server = make_server();
569 let raw = r#"{
570 "jsonrpc": "2.0",
571 "id": 4,
572 "method": "tools/call",
573 "params": { "name": "nonexistent" }
574 }"#;
575 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); assert_eq!(result["isError"], true);
578 assert!(result["content"][0]["text"]
579 .as_str()
580 .expect("text") .contains("Unknown tool"));
582 }
583
584 #[tokio::test]
585 async fn handle_tools_call_missing_params() {
586 let server = make_server();
587 let raw = r#"{"jsonrpc": "2.0", "id": 5, "method": "tools/call"}"#;
588 let resp = server.handle_raw(raw).await.expect("response"); let err = resp.error.expect("error"); assert_eq!(err.code, INVALID_PARAMS);
591 }
592
593 #[tokio::test]
594 async fn handle_ping() {
595 let server = make_server();
596 let raw = r#"{"jsonrpc": "2.0", "id": 6, "method": "ping"}"#;
597 let resp = server.handle_raw(raw).await.expect("response"); assert!(resp.result.is_some());
599 assert!(resp.error.is_none());
600 }
601
602 #[tokio::test]
603 async fn handle_unknown_method() {
604 let server = make_server();
605 let raw = r#"{"jsonrpc": "2.0", "id": 7, "method": "bogus/method"}"#;
606 let resp = server.handle_raw(raw).await.expect("response"); let err = resp.error.expect("error"); assert_eq!(err.code, METHOD_NOT_FOUND);
609 assert!(err.message.contains("bogus/method"));
610 }
611
612 #[tokio::test]
613 async fn handle_invalid_json() {
614 let server = make_server();
615 let resp = server
616 .handle_raw("not json at all")
617 .await
618 .expect("response"); let err = resp.error.expect("error"); assert_eq!(err.code, PARSE_ERROR);
621 }
622
623 #[tokio::test]
624 async fn handle_wrong_jsonrpc_version() {
625 let server = make_server();
626 let raw = r#"{"jsonrpc": "1.0", "id": 8, "method": "ping"}"#;
627 let resp = server.handle_raw(raw).await.expect("response"); let err = resp.error.expect("error"); assert_eq!(err.code, INVALID_REQUEST);
630 }
631
632 #[tokio::test]
633 async fn notification_returns_none() {
634 let server = make_server();
635 let raw = r#"{"jsonrpc": "2.0", "method": "notifications/cancelled"}"#;
636 let resp = server.handle_raw(raw).await;
637 assert!(resp.is_none());
638 }
639
640 #[tokio::test]
641 async fn response_id_matches_request_id() {
642 let server = make_server();
643 let raw = r#"{"jsonrpc": "2.0", "id": 999, "method": "ping"}"#;
644 let resp = server.handle_raw(raw).await.expect("response"); assert_eq!(resp.id, Some(Value::from(999)));
646 }
647
648 #[tokio::test]
649 async fn tools_call_with_no_arguments_defaults_to_empty_object() {
650 let server = make_server();
651 let raw = r#"{
652 "jsonrpc": "2.0",
653 "id": 10,
654 "method": "tools/call",
655 "params": { "name": "ping_tool" }
656 }"#;
657 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); assert_eq!(result["content"][0]["text"], "pong");
660 }
661
662 #[tokio::test]
663 async fn tools_call_with_invalid_params_structure() {
664 let server = make_server();
665 let raw = r#"{
666 "jsonrpc": "2.0",
667 "id": 11,
668 "method": "tools/call",
669 "params": "not an object"
670 }"#;
671 let resp = server.handle_raw(raw).await.expect("response"); let err = resp.error.expect("error"); assert_eq!(err.code, INVALID_PARAMS);
674 }
675
676 #[tokio::test]
677 async fn server_discover_advertises_versions_and_tools() {
678 let server = make_server();
679 let raw = r#"{"jsonrpc": "2.0", "id": 20, "method": "server/discover"}"#;
680 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); assert_eq!(result["resultType"], "complete");
683 let versions: Vec<&str> = result["supportedVersions"]
684 .as_array()
685 .expect("versions") .iter()
687 .filter_map(Value::as_str)
688 .collect();
689 assert!(versions.contains(&"2026-07-28"));
690 assert!(versions.contains(&"2025-11-25"));
691 assert!(result["capabilities"]["tools"].is_object());
692 assert_eq!(result["serverInfo"]["name"], "test-server");
693 }
694
695 #[tokio::test]
696 async fn initialize_echoes_supported_client_version() {
697 let server = make_server();
698 let raw = r#"{
699 "jsonrpc": "2.0",
700 "id": 21,
701 "method": "initialize",
702 "params": {
703 "protocolVersion": "2026-07-28",
704 "capabilities": {},
705 "clientInfo": { "name": "c", "version": "1" }
706 }
707 }"#;
708 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); assert_eq!(result["protocolVersion"], "2026-07-28");
711 }
712
713 #[tokio::test]
714 async fn modern_tools_list_frames_result_type() {
715 let server = make_server();
716 let raw = r#"{
717 "jsonrpc": "2.0",
718 "id": 22,
719 "method": "tools/list",
720 "params": {
721 "_meta": {
722 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
723 "io.modelcontextprotocol/clientInfo": { "name": "c", "version": "1" },
724 "io.modelcontextprotocol/clientCapabilities": {}
725 }
726 }
727 }"#;
728 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); assert_eq!(result["resultType"], "complete");
731 assert_eq!(result["tools"][0]["name"], "ping_tool");
732 }
733
734 #[tokio::test]
735 async fn modern_unsupported_version_returns_minus_32004() {
736 let server = make_server();
737 let raw = r#"{
738 "jsonrpc": "2.0",
739 "id": 23,
740 "method": "tools/list",
741 "params": {
742 "_meta": {
743 "io.modelcontextprotocol/protocolVersion": "1999-01-01",
744 "io.modelcontextprotocol/clientInfo": { "name": "c", "version": "1" },
745 "io.modelcontextprotocol/clientCapabilities": {}
746 }
747 }
748 }"#;
749 let resp = server.handle_raw(raw).await.expect("response"); let err = resp.error.expect("error"); assert_eq!(err.code, UNSUPPORTED_PROTOCOL_VERSION);
752 let data = err.data.expect("data"); assert_eq!(data["requested"], "1999-01-01");
754 assert!(data["supported"]
755 .as_array()
756 .expect("supported") .iter()
758 .any(|v| v == "2026-07-28"));
759 }
760
761 #[tokio::test]
762 async fn modern_malformed_meta_returns_invalid_params() {
763 let server = make_server();
764 let raw = r#"{
766 "jsonrpc": "2.0",
767 "id": 24,
768 "method": "tools/list",
769 "params": {
770 "_meta": {
771 "io.modelcontextprotocol/protocolVersion": "2026-07-28",
772 "io.modelcontextprotocol/clientCapabilities": {}
773 }
774 }
775 }"#;
776 let resp = server.handle_raw(raw).await.expect("response"); let err = resp.error.expect("error"); assert_eq!(err.code, INVALID_PARAMS);
779 }
780
781 use crate::mcp::host::{MethodHandler, ToolDispatcher};
784
785 struct ScopedDispatcher;
788
789 #[async_trait::async_trait]
790 impl ToolDispatcher<TestState> for ScopedDispatcher {
791 async fn list_tools(&self, _state: &Arc<TestState>, ctx: &ToolContext) -> Vec<Tool> {
792 if ctx.tenant_id.is_some() {
793 vec![Tool {
794 name: "scoped_tool".to_owned(),
795 description: "tenant-scoped".to_owned(),
796 input_schema: json!({"type": "object"}),
797 annotations: None,
798 }]
799 } else {
800 Vec::new()
801 }
802 }
803
804 async fn call_tool(
805 &self,
806 name: &str,
807 _state: &Arc<TestState>,
808 _ctx: &ToolContext,
809 _arguments: Value,
810 ) -> ToolResponse {
811 match name {
812 "scoped_tool" => ToolResponse::text("dispatched".to_owned()),
813 other => ToolResponse::error(format!("quota exceeded for {other}")),
814 }
815 }
816 }
817
818 struct ResourcesMethodHandler;
820
821 #[async_trait::async_trait]
822 impl MethodHandler<TestState> for ResourcesMethodHandler {
823 async fn handle(
824 &self,
825 method: &str,
826 id: Option<Value>,
827 _params: Option<Value>,
828 _state: &Arc<TestState>,
829 _ctx: &ToolContext,
830 ) -> Option<JsonRpcResponse> {
831 if method == "resources/list" {
832 Some(JsonRpcResponse::success(id, json!({ "resources": [] })))
833 } else {
834 None
835 }
836 }
837 }
838
839 #[tokio::test]
840 async fn dispatcher_list_empty_without_tenant() {
841 let server = make_server().with_tool_dispatcher(Arc::new(ScopedDispatcher));
842 let raw = r#"{"jsonrpc": "2.0", "id": 30, "method": "tools/list"}"#;
844 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); let tools = result["tools"].as_array().expect("tools array"); assert!(tools.is_empty(), "no tenant → dispatcher yields no tools");
848 }
849
850 #[tokio::test]
851 async fn dispatcher_list_scoped_with_tenant() {
852 let server = make_server().with_tool_dispatcher(Arc::new(ScopedDispatcher));
853 let request: JsonRpcRequest =
854 serde_json::from_str(r#"{"jsonrpc": "2.0", "id": 31, "method": "tools/list"}"#)
855 .expect("request"); let ctx = ToolContext::new().with_tenant("tenant-1");
857 let resp = server
858 .handle_request_with_context(request, &ctx)
859 .await
860 .expect("response"); let result = resp.result.expect("result"); let tools = result["tools"].as_array().expect("tools array"); assert_eq!(tools.len(), 1);
864 assert_eq!(tools[0]["name"], "scoped_tool");
865 }
866
867 #[tokio::test]
868 async fn method_handler_serves_non_tool_method() {
869 let server = make_server().with_method_handler(Arc::new(ResourcesMethodHandler));
870 let raw = r#"{"jsonrpc": "2.0", "id": 32, "method": "resources/list"}"#;
871 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); assert!(result["resources"].is_array());
874 assert!(resp.error.is_none());
875 }
876
877 #[tokio::test]
878 async fn method_handler_declines_falls_through_to_method_not_found() {
879 let server = make_server().with_method_handler(Arc::new(ResourcesMethodHandler));
880 let raw = r#"{"jsonrpc": "2.0", "id": 33, "method": "prompts/list"}"#;
881 let resp = server.handle_raw(raw).await.expect("response"); let err = resp.error.expect("error"); assert_eq!(err.code, METHOD_NOT_FOUND);
884 assert!(err.message.contains("prompts/list"));
885 }
886
887 #[tokio::test]
888 async fn dispatcher_call_routes_host_side() {
889 let server = make_server().with_tool_dispatcher(Arc::new(ScopedDispatcher));
892 let raw = r#"{
893 "jsonrpc": "2.0",
894 "id": 34,
895 "method": "tools/call",
896 "params": { "name": "scoped_tool", "arguments": {} }
897 }"#;
898 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); assert_eq!(result["content"][0]["text"], "dispatched");
901 }
902
903 #[tokio::test]
904 async fn dispatcher_call_reports_host_error() {
905 let server = make_server().with_tool_dispatcher(Arc::new(ScopedDispatcher));
908 let raw = r#"{
909 "jsonrpc": "2.0",
910 "id": 35,
911 "method": "tools/call",
912 "params": { "name": "ping_tool", "arguments": {} }
913 }"#;
914 let resp = server.handle_raw(raw).await.expect("response"); let result = resp.result.expect("result"); assert_eq!(result["isError"], true);
917 assert!(result["content"][0]["text"]
918 .as_str()
919 .expect("text") .contains("quota exceeded"));
921 }
922}