1use std::collections::HashMap;
4use std::sync::Arc;
5
6use asupersync::{Budget, Cx, Outcome};
7use fastmcp_core::logging::{debug, targets, trace};
8use fastmcp_core::{
9 McpContext, McpError, McpErrorCode, McpResult, OutcomeExt, SessionState, block_on,
10};
11use fastmcp_protocol::{
12 CallToolParams, CallToolResult, CancelTaskParams, CancelTaskResult, Content, GetPromptParams,
13 GetPromptResult, GetTaskParams, GetTaskResult, InitializeParams, InitializeResult,
14 JsonRpcRequest, ListPromptsParams, ListPromptsResult, ListResourceTemplatesParams,
15 ListResourceTemplatesResult, ListResourcesParams, ListResourcesResult, ListTasksParams,
16 ListTasksResult, ListToolsParams, ListToolsResult, PROTOCOL_VERSION, ProgressToken, Prompt,
17 ReadResourceParams, ReadResourceResult, Resource, ResourceTemplate, SubmitTaskParams,
18 SubmitTaskResult, Tool, validate, validate_strict,
19};
20
21use crate::handler::{BidirectionalSenders, UriParams, create_context_with_progress_and_senders};
22use crate::tasks::SharedTaskManager;
23
24use crate::Session;
25use crate::handler::{
26 BoxedPromptHandler, BoxedResourceHandler, BoxedToolHandler, PromptHandler, ResourceHandler,
27 ToolHandler,
28};
29
30pub type NotificationSender = Arc<dyn Fn(JsonRpcRequest) + Send + Sync>;
35
36#[derive(Debug, Clone, Default)]
38pub struct TagFilters<'a> {
39 pub include: Option<&'a [String]>,
41 pub exclude: Option<&'a [String]>,
43}
44
45impl<'a> TagFilters<'a> {
46 pub fn new(include: Option<&'a Vec<String>>, exclude: Option<&'a Vec<String>>) -> Self {
48 Self {
49 include: include.map(|v| v.as_slice()),
50 exclude: exclude.map(|v| v.as_slice()),
51 }
52 }
53
54 pub fn matches(&self, component_tags: &[String]) -> bool {
60 let component_tags_lower: Vec<String> =
62 component_tags.iter().map(|t| t.to_lowercase()).collect();
63
64 if let Some(include) = self.include {
66 if !include.is_empty() {
68 for tag in include {
69 let tag_lower = tag.to_lowercase();
70 if !component_tags_lower.contains(&tag_lower) {
71 return false;
72 }
73 }
74 }
75 }
76
77 if let Some(exclude) = self.exclude {
79 for tag in exclude {
80 let tag_lower = tag.to_lowercase();
81 if component_tags_lower.contains(&tag_lower) {
82 return false;
83 }
84 }
85 }
86
87 true
88 }
89}
90
91pub struct Router {
93 tools: HashMap<String, BoxedToolHandler>,
94 resources: HashMap<String, BoxedResourceHandler>,
95 prompts: HashMap<String, BoxedPromptHandler>,
96 resource_templates: HashMap<String, ResourceTemplateEntry>,
97 sorted_template_keys: Vec<String>,
100 strict_input_validation: bool,
102}
103
104impl Router {
105 #[must_use]
107 pub fn new() -> Self {
108 Self {
109 tools: HashMap::new(),
110 resources: HashMap::new(),
111 prompts: HashMap::new(),
112 resource_templates: HashMap::new(),
113 sorted_template_keys: Vec::new(),
114 strict_input_validation: false,
115 }
116 }
117
118 pub fn set_strict_input_validation(&mut self, strict: bool) {
126 self.strict_input_validation = strict;
127 }
128
129 #[must_use]
131 pub fn strict_input_validation(&self) -> bool {
132 self.strict_input_validation
133 }
134
135 fn rebuild_sorted_template_keys(&mut self) {
138 self.sorted_template_keys = self.resource_templates.keys().cloned().collect();
139 self.sorted_template_keys.sort_by(|a, b| {
140 let entry_a = &self.resource_templates[a];
141 let entry_b = &self.resource_templates[b];
142 let (a_literals, a_literal_segments, a_segments) = entry_a.matcher.specificity();
143 let (b_literals, b_literal_segments, b_segments) = entry_b.matcher.specificity();
144 b_literals
145 .cmp(&a_literals)
146 .then(b_literal_segments.cmp(&a_literal_segments))
147 .then(b_segments.cmp(&a_segments))
148 .then_with(|| a.cmp(b))
149 });
150 }
151
152 pub fn add_tool<H: ToolHandler + 'static>(&mut self, handler: H) {
158 let def = handler.definition();
159 self.tools.insert(def.name.clone(), Box::new(handler));
160 }
161
162 pub fn add_tool_with_behavior<H: ToolHandler + 'static>(
167 &mut self,
168 handler: H,
169 behavior: crate::DuplicateBehavior,
170 ) -> Result<(), McpError> {
171 let def = handler.definition();
172 let name = &def.name;
173
174 if self.tools.contains_key(name) {
175 match behavior {
176 crate::DuplicateBehavior::Error => {
177 return Err(McpError::invalid_request(format!(
178 "Tool '{}' already exists",
179 name
180 )));
181 }
182 crate::DuplicateBehavior::Warn => {
183 log::warn!(target: "fastmcp::router", "Tool '{}' already exists, keeping original", name);
184 return Ok(());
185 }
186 crate::DuplicateBehavior::Replace => {
187 log::debug!(target: "fastmcp::router", "Replacing tool '{}'", name);
188 }
190 crate::DuplicateBehavior::Ignore => {
191 return Ok(());
192 }
193 }
194 }
195
196 self.tools.insert(def.name.clone(), Box::new(handler));
197 Ok(())
198 }
199
200 pub fn add_resource<H: ResourceHandler + 'static>(&mut self, handler: H) {
206 let template = handler.template();
207 let def = handler.definition();
208 let boxed: BoxedResourceHandler = Box::new(handler);
209
210 if let Some(template) = template {
211 let entry = ResourceTemplateEntry {
212 matcher: UriTemplate::new(&template.uri_template),
213 template: template.clone(),
214 handler: Some(boxed),
215 };
216 self.resource_templates
217 .insert(template.uri_template.clone(), entry);
218 self.rebuild_sorted_template_keys();
219 } else {
220 self.resources.insert(def.uri.clone(), boxed);
221 }
222 }
223
224 pub fn add_resource_with_behavior<H: ResourceHandler + 'static>(
229 &mut self,
230 handler: H,
231 behavior: crate::DuplicateBehavior,
232 ) -> Result<(), McpError> {
233 let template = handler.template();
234 let def = handler.definition();
235
236 let key = if template.is_some() {
238 template.as_ref().unwrap().uri_template.clone()
239 } else {
240 def.uri.clone()
241 };
242
243 let exists = if template.is_some() {
244 self.resource_templates.contains_key(&key)
245 } else {
246 self.resources.contains_key(&key)
247 };
248
249 if exists {
250 match behavior {
251 crate::DuplicateBehavior::Error => {
252 return Err(McpError::invalid_request(format!(
253 "Resource '{}' already exists",
254 key
255 )));
256 }
257 crate::DuplicateBehavior::Warn => {
258 log::warn!(target: "fastmcp::router", "Resource '{}' already exists, keeping original", key);
259 return Ok(());
260 }
261 crate::DuplicateBehavior::Replace => {
262 log::debug!(target: "fastmcp::router", "Replacing resource '{}'", key);
263 }
265 crate::DuplicateBehavior::Ignore => {
266 return Ok(());
267 }
268 }
269 }
270
271 let boxed: BoxedResourceHandler = Box::new(handler);
273
274 if let Some(template) = template {
275 let entry = ResourceTemplateEntry {
276 matcher: UriTemplate::new(&template.uri_template),
277 template: template.clone(),
278 handler: Some(boxed),
279 };
280 self.resource_templates
281 .insert(template.uri_template.clone(), entry);
282 self.rebuild_sorted_template_keys();
283 } else {
284 self.resources.insert(def.uri.clone(), boxed);
285 }
286
287 Ok(())
288 }
289
290 pub fn add_resource_template(&mut self, template: ResourceTemplate) {
292 let matcher = UriTemplate::new(&template.uri_template);
293 let entry = ResourceTemplateEntry {
294 matcher,
295 template: template.clone(),
296 handler: None,
297 };
298 let needs_rebuild = match self.resource_templates.get_mut(&template.uri_template) {
299 Some(existing) => {
300 existing.template = template;
301 existing.matcher = entry.matcher;
302 false }
304 None => {
305 self.resource_templates
306 .insert(template.uri_template.clone(), entry);
307 true }
309 };
310 if needs_rebuild {
311 self.rebuild_sorted_template_keys();
312 }
313 }
314
315 pub fn add_prompt<H: PromptHandler + 'static>(&mut self, handler: H) {
322 let def = handler.definition();
323 self.prompts.insert(def.name.clone(), Box::new(handler));
324 }
325
326 pub fn add_prompt_with_behavior<H: PromptHandler + 'static>(
331 &mut self,
332 handler: H,
333 behavior: crate::DuplicateBehavior,
334 ) -> Result<(), McpError> {
335 let def = handler.definition();
336 let name = &def.name;
337
338 if self.prompts.contains_key(name) {
339 match behavior {
340 crate::DuplicateBehavior::Error => {
341 return Err(McpError::invalid_request(format!(
342 "Prompt '{}' already exists",
343 name
344 )));
345 }
346 crate::DuplicateBehavior::Warn => {
347 log::warn!(target: "fastmcp::router", "Prompt '{}' already exists, keeping original", name);
348 return Ok(());
349 }
350 crate::DuplicateBehavior::Replace => {
351 log::debug!(target: "fastmcp::router", "Replacing prompt '{}'", name);
352 }
354 crate::DuplicateBehavior::Ignore => {
355 return Ok(());
356 }
357 }
358 }
359
360 self.prompts.insert(def.name.clone(), Box::new(handler));
361 Ok(())
362 }
363
364 #[must_use]
366 pub fn tools(&self) -> Vec<Tool> {
367 self.tools.values().map(|h| h.definition()).collect()
368 }
369
370 #[must_use]
375 pub fn tools_filtered(
376 &self,
377 session_state: Option<&SessionState>,
378 tag_filters: Option<&TagFilters<'_>>,
379 ) -> Vec<Tool> {
380 self.tools
381 .values()
382 .filter(|h| {
383 let def = h.definition();
384 if let Some(state) = session_state {
386 if !state.is_tool_enabled(&def.name) {
387 return false;
388 }
389 }
390 if let Some(filters) = tag_filters {
392 if !filters.matches(&def.tags) {
393 return false;
394 }
395 }
396 true
397 })
398 .map(|h| h.definition())
399 .collect()
400 }
401
402 #[must_use]
404 pub fn resources(&self) -> Vec<Resource> {
405 self.resources.values().map(|h| h.definition()).collect()
406 }
407
408 #[must_use]
413 pub fn resources_filtered(
414 &self,
415 session_state: Option<&SessionState>,
416 tag_filters: Option<&TagFilters<'_>>,
417 ) -> Vec<Resource> {
418 self.resources
419 .values()
420 .filter(|h| {
421 let def = h.definition();
422 if let Some(state) = session_state {
424 if !state.is_resource_enabled(&def.uri) {
425 return false;
426 }
427 }
428 if let Some(filters) = tag_filters {
430 if !filters.matches(&def.tags) {
431 return false;
432 }
433 }
434 true
435 })
436 .map(|h| h.definition())
437 .collect()
438 }
439
440 #[must_use]
442 pub fn resource_templates(&self) -> Vec<ResourceTemplate> {
443 let mut templates: Vec<ResourceTemplate> = self
444 .resource_templates
445 .values()
446 .map(|entry| entry.template.clone())
447 .collect();
448 templates.sort_by(|a, b| a.uri_template.cmp(&b.uri_template));
449 templates
450 }
451
452 #[must_use]
457 pub fn resource_templates_filtered(
458 &self,
459 session_state: Option<&SessionState>,
460 tag_filters: Option<&TagFilters<'_>>,
461 ) -> Vec<ResourceTemplate> {
462 let mut templates: Vec<ResourceTemplate> = self
463 .resource_templates
464 .values()
465 .filter(|entry| {
466 if let Some(state) = session_state {
468 if !state.is_resource_enabled(&entry.template.uri_template) {
469 return false;
470 }
471 }
472 if let Some(filters) = tag_filters {
474 if !filters.matches(&entry.template.tags) {
475 return false;
476 }
477 }
478 true
479 })
480 .map(|entry| entry.template.clone())
481 .collect();
482 templates.sort_by(|a, b| a.uri_template.cmp(&b.uri_template));
483 templates
484 }
485
486 #[must_use]
488 pub fn prompts(&self) -> Vec<Prompt> {
489 self.prompts.values().map(|h| h.definition()).collect()
490 }
491
492 #[must_use]
497 pub fn prompts_filtered(
498 &self,
499 session_state: Option<&SessionState>,
500 tag_filters: Option<&TagFilters<'_>>,
501 ) -> Vec<Prompt> {
502 self.prompts
503 .values()
504 .filter(|h| {
505 let def = h.definition();
506 if let Some(state) = session_state {
508 if !state.is_prompt_enabled(&def.name) {
509 return false;
510 }
511 }
512 if let Some(filters) = tag_filters {
514 if !filters.matches(&def.tags) {
515 return false;
516 }
517 }
518 true
519 })
520 .map(|h| h.definition())
521 .collect()
522 }
523
524 #[must_use]
526 pub fn tools_count(&self) -> usize {
527 self.tools.len()
528 }
529
530 #[must_use]
532 pub fn resources_count(&self) -> usize {
533 self.resources.len()
534 }
535
536 #[must_use]
538 pub fn resource_templates_count(&self) -> usize {
539 self.resource_templates.len()
540 }
541
542 #[must_use]
544 pub fn prompts_count(&self) -> usize {
545 self.prompts.len()
546 }
547
548 #[must_use]
550 pub fn get_tool(&self, name: &str) -> Option<&BoxedToolHandler> {
551 self.tools.get(name)
552 }
553
554 #[must_use]
556 pub fn get_resource(&self, uri: &str) -> Option<&BoxedResourceHandler> {
557 self.resources.get(uri)
558 }
559
560 #[must_use]
562 pub fn get_resource_template(&self, uri_template: &str) -> Option<&ResourceTemplate> {
563 self.resource_templates
564 .get(uri_template)
565 .map(|entry| &entry.template)
566 }
567
568 #[must_use]
570 pub fn resource_exists(&self, uri: &str) -> bool {
571 self.resolve_resource(uri).is_some()
572 }
573
574 fn resolve_resource(&self, uri: &str) -> Option<ResolvedResource<'_>> {
575 if let Some(handler) = self.resources.get(uri) {
576 return Some(ResolvedResource {
577 handler,
578 params: UriParams::new(),
579 });
580 }
581
582 for key in &self.sorted_template_keys {
584 let entry = &self.resource_templates[key];
585 let Some(handler) = entry.handler.as_ref() else {
586 continue;
587 };
588 if let Some(params) = entry.matcher.matches(uri) {
589 return Some(ResolvedResource { handler, params });
590 }
591 }
592
593 None
594 }
595
596 #[must_use]
598 pub fn get_prompt(&self, name: &str) -> Option<&BoxedPromptHandler> {
599 self.prompts.get(name)
600 }
601
602 pub fn handle_initialize(
608 &self,
609 _cx: &Cx,
610 session: &mut Session,
611 params: InitializeParams,
612 instructions: Option<&str>,
613 ) -> McpResult<InitializeResult> {
614 debug!(
615 target: targets::SESSION,
616 "Initializing session with client: {:?}",
617 params.client_info.name
618 );
619
620 session.initialize(
622 params.client_info,
623 params.capabilities,
624 PROTOCOL_VERSION.to_string(),
625 );
626
627 Ok(InitializeResult {
628 protocol_version: PROTOCOL_VERSION.to_string(),
629 capabilities: session.server_capabilities().clone(),
630 server_info: session.server_info().clone(),
631 instructions: instructions.map(String::from),
632 })
633 }
634
635 pub fn handle_tools_list(
640 &self,
641 _cx: &Cx,
642 params: ListToolsParams,
643 session_state: Option<&SessionState>,
644 ) -> McpResult<ListToolsResult> {
645 let tag_filters =
646 TagFilters::new(params.include_tags.as_ref(), params.exclude_tags.as_ref());
647 let tag_filters = if params.include_tags.is_some() || params.exclude_tags.is_some() {
648 Some(&tag_filters)
649 } else {
650 None
651 };
652 Ok(ListToolsResult {
653 tools: self.tools_filtered(session_state, tag_filters),
654 next_cursor: None,
655 })
656 }
657
658 pub fn handle_tools_call(
670 &self,
671 cx: &Cx,
672 request_id: u64,
673 params: CallToolParams,
674 budget: &Budget,
675 session_state: SessionState,
676 notification_sender: Option<&NotificationSender>,
677 bidirectional_senders: Option<&BidirectionalSenders>,
678 ) -> McpResult<CallToolResult> {
679 debug!(target: targets::HANDLER, "Calling tool: {}", params.name);
680 trace!(target: targets::HANDLER, "Tool arguments: {:?}", params.arguments);
681
682 if cx.is_cancel_requested() {
684 return Err(McpError::request_cancelled());
685 }
686
687 if budget.is_exhausted() {
689 return Err(McpError::new(
690 McpErrorCode::RequestCancelled,
691 "Request budget exhausted",
692 ));
693 }
694
695 if !session_state.is_tool_enabled(¶ms.name) {
697 return Err(McpError::new(
698 McpErrorCode::MethodNotFound,
699 format!("Tool '{}' is disabled for this session", params.name),
700 ));
701 }
702
703 let handler = self
705 .tools
706 .get(¶ms.name)
707 .ok_or_else(|| McpError::method_not_found(&format!("tool: {}", params.name)))?;
708
709 let arguments = params.arguments.unwrap_or_else(|| serde_json::json!({}));
712 let tool_def = handler.definition();
713
714 let validation_result = if self.strict_input_validation {
716 validate_strict(&tool_def.input_schema, &arguments)
717 } else {
718 validate(&tool_def.input_schema, &arguments)
719 };
720
721 if let Err(validation_errors) = validation_result {
722 let error_messages: Vec<String> = validation_errors
723 .iter()
724 .map(|e| format!("{}: {}", e.path, e.message))
725 .collect();
726 return Err(McpError::invalid_params(format!(
727 "Input validation failed: {}",
728 error_messages.join("; ")
729 )));
730 }
731
732 let progress_token: Option<ProgressToken> =
734 params.meta.as_ref().and_then(|m| m.progress_token.clone());
735
736 let ctx = match (progress_token, notification_sender) {
738 (Some(token), Some(sender)) => {
739 let sender = sender.clone();
740 create_context_with_progress_and_senders(
741 cx.clone(),
742 request_id,
743 Some(token),
744 Some(session_state),
745 move |req| {
746 sender(req);
747 },
748 bidirectional_senders,
749 )
750 }
751 _ => {
752 let mut ctx = McpContext::with_state(cx.clone(), request_id, session_state);
753 if let Some(senders) = bidirectional_senders {
755 if let Some(ref sampling) = senders.sampling {
756 ctx = ctx.with_sampling(sampling.clone());
757 }
758 if let Some(ref elicitation) = senders.elicitation {
759 ctx = ctx.with_elicitation(elicitation.clone());
760 }
761 }
762 ctx
763 }
764 };
765
766 let outcome = block_on(handler.call_async(&ctx, arguments));
768 match outcome {
769 Outcome::Ok(content) => Ok(CallToolResult {
770 content,
771 is_error: false,
772 }),
773 Outcome::Err(e) => {
774 if matches!(e.code, McpErrorCode::RequestCancelled) {
776 return Err(e);
777 }
778
779 Ok(CallToolResult {
781 content: vec![Content::Text { text: e.message }],
782 is_error: true,
783 })
784 }
785 Outcome::Cancelled(_) => {
786 Err(McpError::request_cancelled())
788 }
789 Outcome::Panicked(payload) => {
790 Err(McpError::internal_error(format!(
792 "Handler panic: {}",
793 payload.message()
794 )))
795 }
796 }
797 }
798
799 pub fn handle_resources_list(
804 &self,
805 _cx: &Cx,
806 params: ListResourcesParams,
807 session_state: Option<&SessionState>,
808 ) -> McpResult<ListResourcesResult> {
809 let tag_filters =
810 TagFilters::new(params.include_tags.as_ref(), params.exclude_tags.as_ref());
811 let tag_filters = if params.include_tags.is_some() || params.exclude_tags.is_some() {
812 Some(&tag_filters)
813 } else {
814 None
815 };
816 Ok(ListResourcesResult {
817 resources: self.resources_filtered(session_state, tag_filters),
818 next_cursor: None,
819 })
820 }
821
822 pub fn handle_resource_templates_list(
827 &self,
828 _cx: &Cx,
829 params: ListResourceTemplatesParams,
830 session_state: Option<&SessionState>,
831 ) -> McpResult<ListResourceTemplatesResult> {
832 let tag_filters =
833 TagFilters::new(params.include_tags.as_ref(), params.exclude_tags.as_ref());
834 let tag_filters = if params.include_tags.is_some() || params.exclude_tags.is_some() {
835 Some(&tag_filters)
836 } else {
837 None
838 };
839 Ok(ListResourceTemplatesResult {
840 resource_templates: self.resource_templates_filtered(session_state, tag_filters),
841 })
842 }
843
844 pub fn handle_resources_read(
856 &self,
857 cx: &Cx,
858 request_id: u64,
859 params: &ReadResourceParams,
860 budget: &Budget,
861 session_state: SessionState,
862 notification_sender: Option<&NotificationSender>,
863 bidirectional_senders: Option<&BidirectionalSenders>,
864 ) -> McpResult<ReadResourceResult> {
865 debug!(target: targets::HANDLER, "Reading resource: {}", params.uri);
866
867 if cx.is_cancel_requested() {
869 return Err(McpError::request_cancelled());
870 }
871
872 if budget.is_exhausted() {
874 return Err(McpError::new(
875 McpErrorCode::RequestCancelled,
876 "Request budget exhausted",
877 ));
878 }
879
880 if !session_state.is_resource_enabled(¶ms.uri) {
882 return Err(McpError::new(
883 McpErrorCode::ResourceNotFound,
884 format!("Resource '{}' is disabled for this session", params.uri),
885 ));
886 }
887
888 let resolved = self
889 .resolve_resource(¶ms.uri)
890 .ok_or_else(|| McpError::resource_not_found(¶ms.uri))?;
891
892 let progress_token: Option<ProgressToken> =
894 params.meta.as_ref().and_then(|m| m.progress_token.clone());
895
896 let ctx = match (progress_token, notification_sender) {
898 (Some(token), Some(sender)) => {
899 let sender = sender.clone();
900 create_context_with_progress_and_senders(
901 cx.clone(),
902 request_id,
903 Some(token),
904 Some(session_state),
905 move |req| {
906 sender(req);
907 },
908 bidirectional_senders,
909 )
910 }
911 _ => {
912 let mut ctx = McpContext::with_state(cx.clone(), request_id, session_state);
913 if let Some(senders) = bidirectional_senders {
915 if let Some(ref sampling) = senders.sampling {
916 ctx = ctx.with_sampling(sampling.clone());
917 }
918 if let Some(ref elicitation) = senders.elicitation {
919 ctx = ctx.with_elicitation(elicitation.clone());
920 }
921 }
922 ctx
923 }
924 };
925
926 let outcome = block_on(resolved.handler.read_async_with_uri(
928 &ctx,
929 ¶ms.uri,
930 &resolved.params,
931 ));
932
933 let contents = outcome.into_mcp_result()?;
935
936 Ok(ReadResourceResult { contents })
937 }
938
939 pub fn handle_prompts_list(
944 &self,
945 _cx: &Cx,
946 params: ListPromptsParams,
947 session_state: Option<&SessionState>,
948 ) -> McpResult<ListPromptsResult> {
949 let tag_filters =
950 TagFilters::new(params.include_tags.as_ref(), params.exclude_tags.as_ref());
951 let tag_filters = if params.include_tags.is_some() || params.exclude_tags.is_some() {
952 Some(&tag_filters)
953 } else {
954 None
955 };
956 Ok(ListPromptsResult {
957 prompts: self.prompts_filtered(session_state, tag_filters),
958 next_cursor: None,
959 })
960 }
961
962 pub fn handle_prompts_get(
974 &self,
975 cx: &Cx,
976 request_id: u64,
977 params: GetPromptParams,
978 budget: &Budget,
979 session_state: SessionState,
980 notification_sender: Option<&NotificationSender>,
981 bidirectional_senders: Option<&BidirectionalSenders>,
982 ) -> McpResult<GetPromptResult> {
983 debug!(target: targets::HANDLER, "Getting prompt: {}", params.name);
984 trace!(target: targets::HANDLER, "Prompt arguments: {:?}", params.arguments);
985
986 if cx.is_cancel_requested() {
988 return Err(McpError::request_cancelled());
989 }
990
991 if budget.is_exhausted() {
993 return Err(McpError::new(
994 McpErrorCode::RequestCancelled,
995 "Request budget exhausted",
996 ));
997 }
998
999 if !session_state.is_prompt_enabled(¶ms.name) {
1001 return Err(McpError::new(
1002 McpErrorCode::PromptNotFound,
1003 format!("Prompt '{}' is disabled for this session", params.name),
1004 ));
1005 }
1006
1007 let handler = self.prompts.get(¶ms.name).ok_or_else(|| {
1009 McpError::new(
1010 fastmcp_core::McpErrorCode::PromptNotFound,
1011 format!("Prompt not found: {}", params.name),
1012 )
1013 })?;
1014
1015 let progress_token: Option<ProgressToken> =
1017 params.meta.as_ref().and_then(|m| m.progress_token.clone());
1018
1019 let ctx = match (progress_token, notification_sender) {
1021 (Some(token), Some(sender)) => {
1022 let sender = sender.clone();
1023 create_context_with_progress_and_senders(
1024 cx.clone(),
1025 request_id,
1026 Some(token),
1027 Some(session_state),
1028 move |req| {
1029 sender(req);
1030 },
1031 bidirectional_senders,
1032 )
1033 }
1034 _ => {
1035 let mut ctx = McpContext::with_state(cx.clone(), request_id, session_state);
1036 if let Some(senders) = bidirectional_senders {
1038 if let Some(ref sampling) = senders.sampling {
1039 ctx = ctx.with_sampling(sampling.clone());
1040 }
1041 if let Some(ref elicitation) = senders.elicitation {
1042 ctx = ctx.with_elicitation(elicitation.clone());
1043 }
1044 }
1045 ctx
1046 }
1047 };
1048
1049 let arguments = params.arguments.unwrap_or_default();
1051 let outcome = block_on(handler.get_async(&ctx, arguments));
1052
1053 let messages = outcome.into_mcp_result()?;
1055
1056 Ok(GetPromptResult {
1057 description: handler.definition().description,
1058 messages,
1059 })
1060 }
1061
1062 pub fn handle_tasks_list(
1070 &self,
1071 _cx: &Cx,
1072 params: ListTasksParams,
1073 task_manager: Option<&SharedTaskManager>,
1074 ) -> McpResult<ListTasksResult> {
1075 let task_manager = task_manager.ok_or_else(|| {
1076 McpError::new(
1077 McpErrorCode::MethodNotFound,
1078 "Background tasks not enabled on this server",
1079 )
1080 })?;
1081
1082 debug!(target: targets::HANDLER, "Listing tasks (status filter: {:?})", params.status);
1083
1084 let tasks = task_manager.list_tasks(params.status);
1085 Ok(ListTasksResult {
1086 tasks,
1087 next_cursor: None, })
1089 }
1090
1091 pub fn handle_tasks_get(
1095 &self,
1096 _cx: &Cx,
1097 params: GetTaskParams,
1098 task_manager: Option<&SharedTaskManager>,
1099 ) -> McpResult<GetTaskResult> {
1100 let task_manager = task_manager.ok_or_else(|| {
1101 McpError::new(
1102 McpErrorCode::MethodNotFound,
1103 "Background tasks not enabled on this server",
1104 )
1105 })?;
1106
1107 debug!(target: targets::HANDLER, "Getting task: {}", params.id);
1108
1109 let task = task_manager
1110 .get_info(¶ms.id)
1111 .ok_or_else(|| McpError::invalid_params(format!("Task not found: {}", params.id)))?;
1112
1113 let result = task_manager.get_result(¶ms.id);
1114
1115 Ok(GetTaskResult { task, result })
1116 }
1117
1118 pub fn handle_tasks_cancel(
1122 &self,
1123 _cx: &Cx,
1124 params: CancelTaskParams,
1125 task_manager: Option<&SharedTaskManager>,
1126 ) -> McpResult<CancelTaskResult> {
1127 let task_manager = task_manager.ok_or_else(|| {
1128 McpError::new(
1129 McpErrorCode::MethodNotFound,
1130 "Background tasks not enabled on this server",
1131 )
1132 })?;
1133
1134 debug!(target: targets::HANDLER, "Cancelling task: {}", params.id);
1135
1136 let task = task_manager.cancel(¶ms.id, params.reason)?;
1137
1138 Ok(CancelTaskResult {
1139 cancelled: true,
1140 task,
1141 })
1142 }
1143
1144 pub fn handle_tasks_submit(
1148 &self,
1149 cx: &Cx,
1150 params: SubmitTaskParams,
1151 task_manager: Option<&SharedTaskManager>,
1152 ) -> McpResult<SubmitTaskResult> {
1153 let task_manager = task_manager.ok_or_else(|| {
1154 McpError::new(
1155 McpErrorCode::MethodNotFound,
1156 "Background tasks not enabled on this server",
1157 )
1158 })?;
1159
1160 debug!(target: targets::HANDLER, "Submitting task: {}", params.task_type);
1161
1162 let task_id = task_manager.submit(cx, ¶ms.task_type, params.params)?;
1163 let task = task_manager
1164 .get_info(&task_id)
1165 .ok_or_else(|| McpError::internal_error("Task created but not found"))?;
1166
1167 Ok(SubmitTaskResult { task })
1168 }
1169}
1170
1171impl Default for Router {
1172 fn default() -> Self {
1173 Self::new()
1174 }
1175}
1176
1177#[derive(Debug, Default)]
1183pub struct MountResult {
1184 pub tools: usize,
1186 pub resources: usize,
1188 pub resource_templates: usize,
1190 pub prompts: usize,
1192 pub warnings: Vec<String>,
1194}
1195
1196impl MountResult {
1197 #[must_use]
1199 pub fn has_components(&self) -> bool {
1200 self.tools > 0 || self.resources > 0 || self.resource_templates > 0 || self.prompts > 0
1201 }
1202
1203 #[must_use]
1205 pub fn is_success(&self) -> bool {
1206 true
1207 }
1208}
1209
1210impl Router {
1211 fn apply_prefix(name: &str, prefix: Option<&str>) -> String {
1213 match prefix {
1214 Some(p) if !p.is_empty() => format!("{}/{}", p, name),
1215 _ => name.to_string(),
1216 }
1217 }
1218
1219 fn validate_prefix(prefix: &str) -> Result<(), String> {
1224 if prefix.is_empty() {
1225 return Ok(());
1226 }
1227 if prefix.contains('/') {
1228 return Err(format!("Prefix cannot contain slashes: '{}'", prefix));
1229 }
1230 for ch in prefix.chars() {
1232 if !ch.is_alphanumeric() && ch != '_' && ch != '-' {
1233 return Err(format!(
1234 "Prefix contains invalid character '{}': '{}'",
1235 ch, prefix
1236 ));
1237 }
1238 }
1239 Ok(())
1240 }
1241
1242 pub fn mount(&mut self, other: Router, prefix: Option<&str>) -> MountResult {
1258 let mut result = MountResult::default();
1259
1260 if let Some(p) = prefix {
1262 if let Err(e) = Self::validate_prefix(p) {
1263 result.warnings.push(e);
1264 }
1266 }
1267
1268 let tool_result = self.mount_tools_from(other.tools, prefix);
1270 result.tools = tool_result.tools;
1271 result.warnings.extend(tool_result.warnings);
1272
1273 let resource_result = self.mount_resources_from(other.resources, prefix);
1275 result.resources = resource_result.resources;
1276 result.warnings.extend(resource_result.warnings);
1277
1278 let template_result = self.mount_resource_templates_from(other.resource_templates, prefix);
1280 result.resource_templates = template_result.resource_templates;
1281 result.warnings.extend(template_result.warnings);
1282
1283 let prompt_result = self.mount_prompts_from(other.prompts, prefix);
1285 result.prompts = prompt_result.prompts;
1286 result.warnings.extend(prompt_result.warnings);
1287
1288 if result.has_components() {
1290 debug!(
1291 target: targets::HANDLER,
1292 "Mounted {} tools, {} resources, {} templates, {} prompts (prefix: {:?})",
1293 result.tools,
1294 result.resources,
1295 result.resource_templates,
1296 result.prompts,
1297 prefix
1298 );
1299 }
1300
1301 result
1302 }
1303
1304 pub fn mount_tools(&mut self, other: Router, prefix: Option<&str>) -> MountResult {
1306 self.mount_tools_from(other.tools, prefix)
1307 }
1308
1309 fn mount_tools_from(
1311 &mut self,
1312 tools: HashMap<String, BoxedToolHandler>,
1313 prefix: Option<&str>,
1314 ) -> MountResult {
1315 use crate::handler::MountedToolHandler;
1316
1317 let mut result = MountResult::default();
1318
1319 for (name, handler) in tools {
1320 let mounted_name = Self::apply_prefix(&name, prefix);
1321 trace!(
1322 target: targets::HANDLER,
1323 "Mounting tool '{}' as '{}'",
1324 name,
1325 mounted_name
1326 );
1327
1328 if self.tools.contains_key(&mounted_name) {
1330 result.warnings.push(format!(
1331 "Tool '{}' already exists, will be overwritten",
1332 mounted_name
1333 ));
1334 }
1335
1336 let mounted = MountedToolHandler::new(handler, mounted_name.clone());
1338 self.tools.insert(mounted_name, Box::new(mounted));
1339 result.tools += 1;
1340 }
1341
1342 result
1343 }
1344
1345 pub fn mount_resources(&mut self, other: Router, prefix: Option<&str>) -> MountResult {
1347 let mut result = self.mount_resources_from(other.resources, prefix);
1348 let template_result = self.mount_resource_templates_from(other.resource_templates, prefix);
1349 result.resource_templates = template_result.resource_templates;
1350 result.warnings.extend(template_result.warnings);
1351 result
1352 }
1353
1354 fn mount_resources_from(
1356 &mut self,
1357 resources: HashMap<String, BoxedResourceHandler>,
1358 prefix: Option<&str>,
1359 ) -> MountResult {
1360 use crate::handler::MountedResourceHandler;
1361
1362 let mut result = MountResult::default();
1363
1364 for (uri, handler) in resources {
1365 let mounted_uri = Self::apply_prefix(&uri, prefix);
1366 trace!(
1367 target: targets::HANDLER,
1368 "Mounting resource '{}' as '{}'",
1369 uri,
1370 mounted_uri
1371 );
1372
1373 if self.resources.contains_key(&mounted_uri) {
1375 result.warnings.push(format!(
1376 "Resource '{}' already exists, will be overwritten",
1377 mounted_uri
1378 ));
1379 }
1380
1381 let mounted = MountedResourceHandler::new(handler, mounted_uri.clone());
1383 self.resources.insert(mounted_uri, Box::new(mounted));
1384 result.resources += 1;
1385 }
1386
1387 result
1388 }
1389
1390 fn mount_resource_templates_from(
1392 &mut self,
1393 templates: HashMap<String, ResourceTemplateEntry>,
1394 prefix: Option<&str>,
1395 ) -> MountResult {
1396 use crate::handler::MountedResourceHandler;
1397
1398 let mut result = MountResult::default();
1399
1400 for (uri_template, entry) in templates {
1401 let mounted_uri_template = Self::apply_prefix(&uri_template, prefix);
1402 trace!(
1403 target: targets::HANDLER,
1404 "Mounting resource template '{}' as '{}'",
1405 uri_template,
1406 mounted_uri_template
1407 );
1408
1409 if self.resource_templates.contains_key(&mounted_uri_template) {
1411 result.warnings.push(format!(
1412 "Resource template '{}' already exists, will be overwritten",
1413 mounted_uri_template
1414 ));
1415 }
1416
1417 let mut mounted_template = entry.template.clone();
1419 mounted_template.uri_template = mounted_uri_template.clone();
1420
1421 let mounted_handler = entry.handler.map(|h| {
1423 let wrapped: BoxedResourceHandler =
1424 Box::new(MountedResourceHandler::with_template(
1425 h,
1426 mounted_uri_template.clone(),
1427 mounted_template.clone(),
1428 ));
1429 wrapped
1430 });
1431
1432 let mounted_entry = ResourceTemplateEntry {
1434 matcher: UriTemplate::new(&mounted_uri_template),
1435 template: mounted_template,
1436 handler: mounted_handler,
1437 };
1438
1439 self.resource_templates
1440 .insert(mounted_uri_template, mounted_entry);
1441 result.resource_templates += 1;
1442 }
1443
1444 if result.resource_templates > 0 {
1446 self.rebuild_sorted_template_keys();
1447 }
1448
1449 result
1450 }
1451
1452 pub fn mount_prompts(&mut self, other: Router, prefix: Option<&str>) -> MountResult {
1454 self.mount_prompts_from(other.prompts, prefix)
1455 }
1456
1457 fn mount_prompts_from(
1459 &mut self,
1460 prompts: HashMap<String, BoxedPromptHandler>,
1461 prefix: Option<&str>,
1462 ) -> MountResult {
1463 use crate::handler::MountedPromptHandler;
1464
1465 let mut result = MountResult::default();
1466
1467 for (name, handler) in prompts {
1468 let mounted_name = Self::apply_prefix(&name, prefix);
1469 trace!(
1470 target: targets::HANDLER,
1471 "Mounting prompt '{}' as '{}'",
1472 name,
1473 mounted_name
1474 );
1475
1476 if self.prompts.contains_key(&mounted_name) {
1478 result.warnings.push(format!(
1479 "Prompt '{}' already exists, will be overwritten",
1480 mounted_name
1481 ));
1482 }
1483
1484 let mounted = MountedPromptHandler::new(handler, mounted_name.clone());
1486 self.prompts.insert(mounted_name, Box::new(mounted));
1487 result.prompts += 1;
1488 }
1489
1490 result
1491 }
1492
1493 #[must_use]
1497 #[allow(dead_code)]
1498 pub(crate) fn into_parts(
1499 self,
1500 ) -> (
1501 HashMap<String, BoxedToolHandler>,
1502 HashMap<String, BoxedResourceHandler>,
1503 HashMap<String, ResourceTemplateEntry>,
1504 HashMap<String, BoxedPromptHandler>,
1505 ) {
1506 (
1507 self.tools,
1508 self.resources,
1509 self.resource_templates,
1510 self.prompts,
1511 )
1512 }
1513}
1514
1515struct ResolvedResource<'a> {
1516 handler: &'a BoxedResourceHandler,
1517 params: UriParams,
1518}
1519
1520pub(crate) struct ResourceTemplateEntry {
1522 pub(crate) matcher: UriTemplate,
1523 pub(crate) template: ResourceTemplate,
1524 pub(crate) handler: Option<BoxedResourceHandler>,
1525}
1526
1527#[derive(Debug, Clone)]
1529pub(crate) struct UriTemplate {
1530 pattern: String,
1531 segments: Vec<UriSegment>,
1532}
1533
1534#[derive(Debug, Clone, PartialEq, Eq)]
1535enum UriTemplateError {
1536 UnclosedParam,
1537 UnmatchedClose,
1538 EmptyParam,
1539 DuplicateParam(String),
1540}
1541
1542#[derive(Debug, Clone)]
1543enum UriSegment {
1544 Literal(String),
1545 Param(String),
1546}
1547
1548impl UriTemplate {
1549 fn new(pattern: &str) -> Self {
1554 Self::try_new(pattern).unwrap_or_else(|err| {
1555 fastmcp_core::logging::warn!(
1556 target: targets::HANDLER,
1557 "Invalid URI template '{}': {:?}, using non-matching fallback",
1558 pattern,
1559 err
1560 );
1561 Self {
1563 pattern: pattern.to_string(),
1564 segments: vec![UriSegment::Literal("\0INVALID\0".to_string())],
1565 }
1566 })
1567 }
1568
1569 fn try_new(pattern: &str) -> Result<Self, UriTemplateError> {
1571 Self::parse(pattern)
1572 }
1573
1574 fn parse(pattern: &str) -> Result<Self, UriTemplateError> {
1575 let mut segments = Vec::new();
1576 let mut literal = String::new();
1577 let mut chars = pattern.chars().peekable();
1578 let mut seen = std::collections::HashSet::new();
1579
1580 while let Some(ch) = chars.next() {
1581 match ch {
1582 '{' => {
1583 if matches!(chars.peek(), Some('{')) {
1584 let _ = chars.next();
1585 literal.push('{');
1586 continue;
1587 }
1588
1589 if !literal.is_empty() {
1590 segments.push(UriSegment::Literal(std::mem::take(&mut literal)));
1591 }
1592
1593 let mut name = String::new();
1594 let mut closed = false;
1595 for next in chars.by_ref() {
1596 if next == '}' {
1597 closed = true;
1598 break;
1599 }
1600 name.push(next);
1601 }
1602
1603 if !closed {
1604 return Err(UriTemplateError::UnclosedParam);
1605 }
1606
1607 if name.is_empty() {
1608 return Err(UriTemplateError::EmptyParam);
1609 }
1610 if !seen.insert(name.clone()) {
1611 return Err(UriTemplateError::DuplicateParam(name));
1612 }
1613 segments.push(UriSegment::Param(name));
1614 }
1615 '}' => {
1616 if matches!(chars.peek(), Some('}')) {
1617 let _ = chars.next();
1618 literal.push('}');
1619 continue;
1620 }
1621 return Err(UriTemplateError::UnmatchedClose);
1622 }
1623 _ => literal.push(ch),
1624 }
1625 }
1626
1627 if !literal.is_empty() {
1628 segments.push(UriSegment::Literal(literal));
1629 }
1630
1631 Ok(Self {
1632 pattern: pattern.to_string(),
1633 segments,
1634 })
1635 }
1636
1637 fn specificity(&self) -> (usize, usize, usize) {
1638 let mut literal_len = 0usize;
1639 let mut literal_segments = 0usize;
1640 for segment in &self.segments {
1641 if let UriSegment::Literal(lit) = segment {
1642 literal_len += lit.len();
1643 literal_segments += 1;
1644 }
1645 }
1646 (literal_len, literal_segments, self.segments.len())
1647 }
1648
1649 fn matches(&self, uri: &str) -> Option<UriParams> {
1650 let mut params = UriParams::new();
1651 let mut remainder = uri;
1652 let mut iter = self.segments.iter().peekable();
1653
1654 while let Some(segment) = iter.next() {
1655 match segment {
1656 UriSegment::Literal(lit) => {
1657 remainder = remainder.strip_prefix(lit)?;
1658 }
1659 UriSegment::Param(name) => {
1660 let next_literal = iter.peek().and_then(|next| match next {
1661 UriSegment::Literal(lit) => Some(lit.as_str()),
1662 UriSegment::Param(_) => None,
1663 });
1664
1665 if next_literal.is_none() && iter.peek().is_some() {
1666 return None;
1667 }
1668
1669 if let Some(literal) = next_literal {
1670 let idx = remainder.find(literal)?;
1671 let value = &remainder[..idx];
1672 if value.is_empty() {
1673 return None;
1674 }
1675 let value = percent_decode(value)?;
1676 params.insert(name.clone(), value);
1677 remainder = &remainder[idx..];
1678 } else {
1679 if remainder.is_empty() {
1683 return None;
1684 }
1685
1686 let allow_slash_in_last_param = self
1687 .segments
1688 .iter()
1689 .filter(|seg| matches!(seg, UriSegment::Param(_)))
1690 .count()
1691 == 1;
1692
1693 let end_idx = if allow_slash_in_last_param {
1694 remainder.len()
1695 } else {
1696 remainder.find('/').unwrap_or(remainder.len())
1697 };
1698
1699 let value = &remainder[..end_idx];
1700 if value.is_empty() {
1701 return None;
1702 }
1703 let value = percent_decode(value)?;
1704 params.insert(name.clone(), value);
1705 remainder = &remainder[end_idx..];
1706 }
1707 }
1708 }
1709 }
1710
1711 if remainder.is_empty() {
1712 Some(params)
1713 } else {
1714 None
1715 }
1716 }
1717}
1718
1719fn percent_decode(input: &str) -> Option<String> {
1720 if !input.as_bytes().contains(&b'%') {
1721 return Some(input.to_string());
1722 }
1723 let bytes = input.as_bytes();
1724 let mut out = Vec::with_capacity(bytes.len());
1725 let mut i = 0usize;
1726 while i < bytes.len() {
1727 match bytes[i] {
1728 b'%' => {
1729 if i + 2 >= bytes.len() {
1730 return None;
1731 }
1732 let hi = bytes[i + 1];
1733 let lo = bytes[i + 2];
1734 let value = (from_hex(hi)? << 4) | from_hex(lo)?;
1735 out.push(value);
1736 i += 3;
1737 }
1738 b => {
1739 out.push(b);
1740 i += 1;
1741 }
1742 }
1743 }
1744 String::from_utf8(out).ok()
1745}
1746
1747fn from_hex(byte: u8) -> Option<u8> {
1748 match byte {
1749 b'0'..=b'9' => Some(byte - b'0'),
1750 b'a'..=b'f' => Some(byte - b'a' + 10),
1751 b'A'..=b'F' => Some(byte - b'A' + 10),
1752 _ => None,
1753 }
1754}
1755
1756use fastmcp_core::{
1761 MAX_RESOURCE_READ_DEPTH, ResourceContentItem, ResourceReadResult, ResourceReader,
1762};
1763use std::pin::Pin;
1764
1765pub struct RouterResourceReader {
1770 router: Arc<Router>,
1772 session_state: SessionState,
1774}
1775
1776impl RouterResourceReader {
1777 #[must_use]
1779 pub fn new(router: Arc<Router>, session_state: SessionState) -> Self {
1780 Self {
1781 router,
1782 session_state,
1783 }
1784 }
1785}
1786
1787impl ResourceReader for RouterResourceReader {
1788 fn read_resource(
1789 &self,
1790 cx: &Cx,
1791 uri: &str,
1792 depth: u32,
1793 ) -> Pin<
1794 Box<
1795 dyn std::future::Future<Output = fastmcp_core::McpResult<ResourceReadResult>>
1796 + Send
1797 + '_,
1798 >,
1799 > {
1800 if depth > MAX_RESOURCE_READ_DEPTH {
1802 return Box::pin(async move {
1803 Err(McpError::new(
1804 McpErrorCode::InternalError,
1805 format!(
1806 "Maximum resource read depth ({}) exceeded",
1807 MAX_RESOURCE_READ_DEPTH
1808 ),
1809 ))
1810 });
1811 }
1812
1813 let cx = cx.clone();
1815 let uri = uri.to_string();
1816 let router = self.router.clone();
1817 let session_state = self.session_state.clone();
1818
1819 Box::pin(async move {
1820 debug!(target: targets::HANDLER, "Cross-component resource read: {} (depth: {})", uri, depth);
1821
1822 let resolved = router.resolve_resource(&uri).ok_or_else(|| {
1824 McpError::new(
1825 McpErrorCode::ResourceNotFound,
1826 format!("Resource not found: {}", uri),
1827 )
1828 })?;
1829
1830 let nested_router = router.clone();
1833 let nested_state = session_state.clone();
1834 let child_ctx = McpContext::with_state(cx.clone(), 0, session_state)
1835 .with_resource_read_depth(depth)
1836 .with_resource_reader(Arc::new(RouterResourceReader::new(
1837 nested_router,
1838 nested_state,
1839 )));
1840
1841 let outcome = block_on(resolved.handler.read_async_with_uri(
1843 &child_ctx,
1844 &uri,
1845 &resolved.params,
1846 ));
1847
1848 let contents = outcome.into_mcp_result()?;
1850
1851 let items: Vec<ResourceContentItem> = contents
1853 .into_iter()
1854 .map(|c| ResourceContentItem {
1855 uri: c.uri,
1856 mime_type: c.mime_type,
1857 text: c.text,
1858 blob: c.blob,
1859 })
1860 .collect();
1861
1862 Ok(ResourceReadResult::new(items))
1863 })
1864 }
1865}
1866
1867use fastmcp_core::{MAX_TOOL_CALL_DEPTH, ToolCallResult, ToolCaller, ToolContentItem};
1872
1873pub struct RouterToolCaller {
1878 router: Arc<Router>,
1880 session_state: SessionState,
1882}
1883
1884impl RouterToolCaller {
1885 #[must_use]
1887 pub fn new(router: Arc<Router>, session_state: SessionState) -> Self {
1888 Self {
1889 router,
1890 session_state,
1891 }
1892 }
1893}
1894
1895impl ToolCaller for RouterToolCaller {
1896 fn call_tool(
1897 &self,
1898 cx: &Cx,
1899 name: &str,
1900 args: serde_json::Value,
1901 depth: u32,
1902 ) -> Pin<
1903 Box<dyn std::future::Future<Output = fastmcp_core::McpResult<ToolCallResult>> + Send + '_>,
1904 > {
1905 if depth > MAX_TOOL_CALL_DEPTH {
1907 return Box::pin(async move {
1908 Err(McpError::new(
1909 McpErrorCode::InternalError,
1910 format!("Maximum tool call depth ({}) exceeded", MAX_TOOL_CALL_DEPTH),
1911 ))
1912 });
1913 }
1914
1915 let cx = cx.clone();
1917 let name = name.to_string();
1918 let router = self.router.clone();
1919 let session_state = self.session_state.clone();
1920
1921 Box::pin(async move {
1922 debug!(target: targets::HANDLER, "Cross-component tool call: {} (depth: {})", name, depth);
1923
1924 let handler = router
1926 .tools
1927 .get(&name)
1928 .ok_or_else(|| McpError::method_not_found(&format!("tool: {}", name)))?;
1929
1930 let tool_def = handler.definition();
1932
1933 let validation_result = if router.strict_input_validation {
1935 validate_strict(&tool_def.input_schema, &args)
1936 } else {
1937 validate(&tool_def.input_schema, &args)
1938 };
1939
1940 if let Err(validation_errors) = validation_result {
1941 let error_messages: Vec<String> = validation_errors
1942 .iter()
1943 .map(|e| format!("{}: {}", e.path, e.message))
1944 .collect();
1945 return Err(McpError::invalid_params(format!(
1946 "Input validation failed: {}",
1947 error_messages.join("; ")
1948 )));
1949 }
1950
1951 let nested_router = router.clone();
1954 let nested_state = session_state.clone();
1955 let child_ctx = McpContext::with_state(cx.clone(), 0, session_state)
1956 .with_tool_call_depth(depth)
1957 .with_tool_caller(Arc::new(RouterToolCaller::new(
1958 nested_router.clone(),
1959 nested_state.clone(),
1960 )))
1961 .with_resource_reader(Arc::new(RouterResourceReader::new(
1962 nested_router,
1963 nested_state,
1964 )));
1965
1966 let outcome = block_on(handler.call_async(&child_ctx, args));
1968
1969 match outcome {
1971 Outcome::Ok(content) => {
1972 let items: Vec<ToolContentItem> = content
1974 .into_iter()
1975 .map(|c| match c {
1976 Content::Text { text } => ToolContentItem::Text { text },
1977 Content::Image { data, mime_type } => {
1978 ToolContentItem::Image { data, mime_type }
1979 }
1980 Content::Resource { resource } => ToolContentItem::Resource {
1981 uri: resource.uri,
1982 mime_type: resource.mime_type,
1983 text: resource.text,
1984 },
1985 })
1986 .collect();
1987
1988 Ok(ToolCallResult::success(items))
1989 }
1990 Outcome::Err(e) => {
1991 Ok(ToolCallResult::error(e.message))
1993 }
1994 Outcome::Cancelled(_) => Err(McpError::request_cancelled()),
1995 Outcome::Panicked(payload) => Err(McpError::internal_error(format!(
1996 "Handler panic: {}",
1997 payload.message()
1998 ))),
1999 }
2000 })
2001 }
2002}
2003
2004#[cfg(test)]
2005mod uri_template_tests {
2006 use super::{UriTemplate, UriTemplateError};
2007
2008 #[test]
2009 fn uri_template_matches_simple_param() {
2010 let matcher = UriTemplate::new("file://{path}");
2011 let params = matcher.matches("file://foo").expect("match");
2012 assert_eq!(params.get("path").map(String::as_str), Some("foo"));
2013 }
2014
2015 #[test]
2016 fn uri_template_allows_slash_in_trailing_param() {
2017 let matcher = UriTemplate::new("file://{path}");
2018 let params = matcher.matches("file://foo/bar").expect("match");
2019 assert_eq!(params.get("path").map(String::as_str), Some("foo/bar"));
2020 }
2021
2022 #[test]
2023 fn uri_template_matches_multiple_params() {
2024 let matcher = UriTemplate::new("db://{table}/{id}");
2025 let params = matcher.matches("db://users/42").expect("match");
2026 assert_eq!(params.get("table").map(String::as_str), Some("users"));
2027 assert_eq!(params.get("id").map(String::as_str), Some("42"));
2028 }
2029
2030 #[test]
2031 fn uri_template_rejects_extra_segments() {
2032 let matcher = UriTemplate::new("db://{table}/{id}");
2033 assert!(matcher.matches("db://users/42/extra").is_none());
2034 }
2035
2036 #[test]
2037 fn uri_template_rejects_extra_segments_with_literal_path() {
2038 let matcher = UriTemplate::new("db://{table}/items/{id}");
2039 let params = matcher.matches("db://users/items/42").expect("match");
2040 assert_eq!(params.get("table").map(String::as_str), Some("users"));
2041 assert_eq!(params.get("id").map(String::as_str), Some("42"));
2042 assert!(matcher.matches("db://users/items/42/extra").is_none());
2043 }
2044
2045 #[test]
2046 fn uri_template_decodes_percent_encoded_values() {
2047 let matcher = UriTemplate::new("file://{path}");
2048 let params = matcher.matches("file://foo%2Fbar").expect("match");
2049 assert_eq!(params.get("path").map(String::as_str), Some("foo/bar"));
2050 }
2051
2052 #[test]
2053 fn uri_template_supports_escaped_braces() {
2054 let matcher = UriTemplate::new("file://{{literal}}/{id}");
2055 let params = matcher.matches("file://{literal}/123").expect("match");
2056 assert_eq!(params.get("id").map(String::as_str), Some("123"));
2057 }
2058
2059 #[test]
2060 fn uri_template_rejects_empty_param() {
2061 let err = UriTemplate::parse("file://{}/x").unwrap_err();
2062 assert_eq!(err, UriTemplateError::EmptyParam);
2063 }
2064
2065 #[test]
2066 fn uri_template_rejects_unmatched_close() {
2067 let err = UriTemplate::parse("file://}x").unwrap_err();
2068 assert_eq!(err, UriTemplateError::UnmatchedClose);
2069 }
2070
2071 #[test]
2072 fn uri_template_rejects_duplicate_params() {
2073 let err = UriTemplate::parse("db://{id}/{id}").unwrap_err();
2074 assert_eq!(err, UriTemplateError::DuplicateParam("id".to_string()));
2075 }
2076}