Skip to main content

fastmcp_server/
router.rs

1//! Request router for MCP servers.
2
3use 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
30/// Type alias for a notification sender callback.
31///
32/// This callback is used to send notifications (like progress updates) back to the client
33/// during request handling. The callback receives a JSON-RPC request (notification format).
34pub type NotificationSender = Arc<dyn Fn(JsonRpcRequest) + Send + Sync>;
35
36/// Tag filtering parameters for list operations.
37#[derive(Debug, Clone, Default)]
38pub struct TagFilters<'a> {
39    /// Only include components with ALL of these tags (AND logic).
40    pub include: Option<&'a [String]>,
41    /// Exclude components with ANY of these tags (OR logic).
42    pub exclude: Option<&'a [String]>,
43}
44
45impl<'a> TagFilters<'a> {
46    /// Creates tag filters from include and exclude vectors.
47    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    /// Returns true if the given component tags pass the filter.
55    ///
56    /// - Include filter: component must have ALL include tags (AND logic)
57    /// - Exclude filter: component is rejected if it has ANY exclude tag (OR logic)
58    /// - Tag matching is case-insensitive
59    pub fn matches(&self, component_tags: &[String]) -> bool {
60        // Normalize component tags to lowercase for comparison
61        let component_tags_lower: Vec<String> =
62            component_tags.iter().map(|t| t.to_lowercase()).collect();
63
64        // Include filter: must have ALL specified tags
65        if let Some(include) = self.include {
66            // Empty include array means no filter (all pass)
67            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        // Exclude filter: rejected if has ANY specified tag
78        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
91/// Routes MCP requests to the appropriate handlers.
92pub struct Router {
93    tools: HashMap<String, BoxedToolHandler>,
94    resources: HashMap<String, BoxedResourceHandler>,
95    prompts: HashMap<String, BoxedPromptHandler>,
96    resource_templates: HashMap<String, ResourceTemplateEntry>,
97    /// Pre-sorted template keys by specificity (most specific first).
98    /// Updated whenever templates are added/modified.
99    sorted_template_keys: Vec<String>,
100    /// Whether to enforce strict input validation (reject extra properties).
101    strict_input_validation: bool,
102}
103
104impl Router {
105    /// Creates a new empty router.
106    #[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    /// Sets whether to use strict input validation.
119    ///
120    /// When enabled, tool input validation will reject any properties not
121    /// explicitly defined in the tool's input schema (enforces `additionalProperties: false`).
122    ///
123    /// When disabled (default), extra properties are allowed unless the schema
124    /// explicitly sets `additionalProperties: false`.
125    pub fn set_strict_input_validation(&mut self, strict: bool) {
126        self.strict_input_validation = strict;
127    }
128
129    /// Returns whether strict input validation is enabled.
130    #[must_use]
131    pub fn strict_input_validation(&self) -> bool {
132        self.strict_input_validation
133    }
134
135    /// Rebuilds the sorted template keys vector.
136    /// Called after any modification to resource_templates.
137    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    /// Adds a tool handler.
153    ///
154    /// If a tool with the same name already exists, it will be replaced.
155    /// Use [`add_tool_with_behavior`](Self::add_tool_with_behavior) for
156    /// finer control over duplicate handling.
157    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    /// Adds a tool handler with specified duplicate behavior.
163    ///
164    /// Returns `Err` if behavior is [`DuplicateBehavior::Error`] and the
165    /// tool name already exists.
166    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                    // Fall through to insert
189                }
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    /// Adds a resource handler.
201    ///
202    /// If a resource with the same URI already exists, it will be replaced.
203    /// Use [`add_resource_with_behavior`](Self::add_resource_with_behavior) for
204    /// finer control over duplicate handling.
205    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    /// Adds a resource handler with specified duplicate behavior.
225    ///
226    /// Returns `Err` if behavior is [`DuplicateBehavior::Error`] and the
227    /// resource URI already exists.
228    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        // Check for duplicates
237        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                    // Fall through to insert
264                }
265                crate::DuplicateBehavior::Ignore => {
266                    return Ok(());
267                }
268            }
269        }
270
271        // Actually add the resource
272        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    /// Adds a resource template definition.
291    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 // Key already exists, order unchanged
303            }
304            None => {
305                self.resource_templates
306                    .insert(template.uri_template.clone(), entry);
307                true // New key added, need to rebuild
308            }
309        };
310        if needs_rebuild {
311            self.rebuild_sorted_template_keys();
312        }
313    }
314
315    /// Adds a prompt handler.
316    /// Adds a prompt handler.
317    ///
318    /// If a prompt with the same name already exists, it will be replaced.
319    /// Use [`add_prompt_with_behavior`](Self::add_prompt_with_behavior) for
320    /// finer control over duplicate handling.
321    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    /// Adds a prompt handler with specified duplicate behavior.
327    ///
328    /// Returns `Err` if behavior is [`DuplicateBehavior::Error`] and the
329    /// prompt name already exists.
330    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                    // Fall through to insert
353                }
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    /// Returns all tool definitions.
365    #[must_use]
366    pub fn tools(&self) -> Vec<Tool> {
367        self.tools.values().map(|h| h.definition()).collect()
368    }
369
370    /// Returns tool definitions filtered by session state and tags.
371    ///
372    /// Tools that have been disabled in the session state will not be included.
373    /// If tag filters are provided, tools must match the include/exclude criteria.
374    #[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                // Check session state filter
385                if let Some(state) = session_state {
386                    if !state.is_tool_enabled(&def.name) {
387                        return false;
388                    }
389                }
390                // Check tag filters
391                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    /// Returns all resource definitions.
403    #[must_use]
404    pub fn resources(&self) -> Vec<Resource> {
405        self.resources.values().map(|h| h.definition()).collect()
406    }
407
408    /// Returns resource definitions filtered by session state and tags.
409    ///
410    /// Resources that have been disabled in the session state will not be included.
411    /// If tag filters are provided, resources must match the include/exclude criteria.
412    #[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                // Check session state filter
423                if let Some(state) = session_state {
424                    if !state.is_resource_enabled(&def.uri) {
425                        return false;
426                    }
427                }
428                // Check tag filters
429                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    /// Returns all resource templates.
441    #[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    /// Returns resource templates filtered by session state and tags.
453    ///
454    /// Templates that have been disabled in the session state will not be included.
455    /// If tag filters are provided, templates must match the include/exclude criteria.
456    #[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                // Check session state filter
467                if let Some(state) = session_state {
468                    if !state.is_resource_enabled(&entry.template.uri_template) {
469                        return false;
470                    }
471                }
472                // Check tag filters
473                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    /// Returns all prompt definitions.
487    #[must_use]
488    pub fn prompts(&self) -> Vec<Prompt> {
489        self.prompts.values().map(|h| h.definition()).collect()
490    }
491
492    /// Returns prompt definitions filtered by session state and tags.
493    ///
494    /// Prompts that have been disabled in the session state will not be included.
495    /// If tag filters are provided, prompts must match the include/exclude criteria.
496    #[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                // Check session state filter
507                if let Some(state) = session_state {
508                    if !state.is_prompt_enabled(&def.name) {
509                        return false;
510                    }
511                }
512                // Check tag filters
513                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    /// Returns the number of registered tools.
525    #[must_use]
526    pub fn tools_count(&self) -> usize {
527        self.tools.len()
528    }
529
530    /// Returns the number of registered resources.
531    #[must_use]
532    pub fn resources_count(&self) -> usize {
533        self.resources.len()
534    }
535
536    /// Returns the number of registered resource templates.
537    #[must_use]
538    pub fn resource_templates_count(&self) -> usize {
539        self.resource_templates.len()
540    }
541
542    /// Returns the number of registered prompts.
543    #[must_use]
544    pub fn prompts_count(&self) -> usize {
545        self.prompts.len()
546    }
547
548    /// Gets a tool handler by name.
549    #[must_use]
550    pub fn get_tool(&self, name: &str) -> Option<&BoxedToolHandler> {
551        self.tools.get(name)
552    }
553
554    /// Gets a resource handler by URI.
555    #[must_use]
556    pub fn get_resource(&self, uri: &str) -> Option<&BoxedResourceHandler> {
557        self.resources.get(uri)
558    }
559
560    /// Gets a resource template by URI template.
561    #[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    /// Returns true if a resource exists for the given URI (static or template match).
569    #[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        // Use pre-sorted template keys to avoid sorting on every lookup
583        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    /// Gets a prompt handler by name.
597    #[must_use]
598    pub fn get_prompt(&self, name: &str) -> Option<&BoxedPromptHandler> {
599        self.prompts.get(name)
600    }
601
602    // ========================================================================
603    // Request Dispatch Methods
604    // ========================================================================
605
606    /// Handles the initialize request.
607    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        // Initialize the session
621        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    /// Handles the tools/list request.
636    ///
637    /// If session_state is provided, disabled tools will be filtered out.
638    /// If include_tags/exclude_tags are provided, tools are filtered by tags.
639    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    /// Handles the tools/call request.
659    ///
660    /// # Arguments
661    ///
662    /// * `cx` - The asupersync context for cancellation and tracing
663    /// * `request_id` - Internal request ID for tracking
664    /// * `params` - The tool call parameters including tool name and arguments
665    /// * `budget` - Request budget for timeout enforcement
666    /// * `session_state` - Session state for per-session storage
667    /// * `notification_sender` - Optional callback for sending progress notifications
668    /// * `bidirectional_senders` - Optional senders for sampling/elicitation
669    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        // Check cancellation
683        if cx.is_cancel_requested() {
684            return Err(McpError::request_cancelled());
685        }
686
687        // Check budget exhaustion
688        if budget.is_exhausted() {
689            return Err(McpError::new(
690                McpErrorCode::RequestCancelled,
691                "Request budget exhausted",
692            ));
693        }
694
695        // Check if tool is disabled for this session
696        if !session_state.is_tool_enabled(&params.name) {
697            return Err(McpError::new(
698                McpErrorCode::MethodNotFound,
699                format!("Tool '{}' is disabled for this session", params.name),
700            ));
701        }
702
703        // Find the tool handler
704        let handler = self
705            .tools
706            .get(&params.name)
707            .ok_or_else(|| McpError::method_not_found(&format!("tool: {}", params.name)))?;
708
709        // Validate arguments against the tool's input schema
710        // Default to empty object since MCP tool arguments are always objects
711        let arguments = params.arguments.unwrap_or_else(|| serde_json::json!({}));
712        let tool_def = handler.definition();
713
714        // Use strict or lenient validation based on configuration
715        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        // Extract progress token from request metadata
733        let progress_token: Option<ProgressToken> =
734            params.meta.as_ref().and_then(|m| m.progress_token.clone());
735
736        // Create context for the handler with progress reporting, session state, and bidirectional senders
737        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                // Attach bidirectional senders even without progress
754                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        // Call the handler asynchronously - returns McpOutcome (4-valued)
767        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 the request was cancelled, propagate the error as a JSON-RPC error.
775                if matches!(e.code, McpErrorCode::RequestCancelled) {
776                    return Err(e);
777                }
778
779                // Tool errors are returned as content with is_error=true
780                Ok(CallToolResult {
781                    content: vec![Content::Text { text: e.message }],
782                    is_error: true,
783                })
784            }
785            Outcome::Cancelled(_) => {
786                // Cancelled requests are reported as JSON-RPC errors
787                Err(McpError::request_cancelled())
788            }
789            Outcome::Panicked(payload) => {
790                // Panics become internal errors
791                Err(McpError::internal_error(format!(
792                    "Handler panic: {}",
793                    payload.message()
794                )))
795            }
796        }
797    }
798
799    /// Handles the resources/list request.
800    ///
801    /// If session_state is provided, disabled resources will be filtered out.
802    /// If include_tags/exclude_tags are provided, resources are filtered by tags.
803    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    /// Handles the resources/templates/list request.
823    ///
824    /// If session_state is provided, disabled resource templates will be filtered out.
825    /// If include_tags/exclude_tags are provided, templates are filtered by tags.
826    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    /// Handles the resources/read request.
845    ///
846    /// # Arguments
847    ///
848    /// * `cx` - The asupersync context for cancellation and tracing
849    /// * `request_id` - Internal request ID for tracking
850    /// * `params` - The resource read parameters including URI
851    /// * `budget` - Request budget for timeout enforcement
852    /// * `session_state` - Session state for per-session storage
853    /// * `notification_sender` - Optional callback for sending progress notifications
854    /// * `bidirectional_senders` - Optional senders for sampling/elicitation
855    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        // Check cancellation
868        if cx.is_cancel_requested() {
869            return Err(McpError::request_cancelled());
870        }
871
872        // Check budget exhaustion
873        if budget.is_exhausted() {
874            return Err(McpError::new(
875                McpErrorCode::RequestCancelled,
876                "Request budget exhausted",
877            ));
878        }
879
880        // Check if resource is disabled for this session
881        if !session_state.is_resource_enabled(&params.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(&params.uri)
890            .ok_or_else(|| McpError::resource_not_found(&params.uri))?;
891
892        // Extract progress token from request metadata
893        let progress_token: Option<ProgressToken> =
894            params.meta.as_ref().and_then(|m| m.progress_token.clone());
895
896        // Create context for the handler with progress reporting, session state, and bidirectional senders
897        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                // Attach bidirectional senders even without progress
914                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        // Read the resource asynchronously - returns McpOutcome (4-valued)
927        let outcome = block_on(resolved.handler.read_async_with_uri(
928            &ctx,
929            &params.uri,
930            &resolved.params,
931        ));
932
933        // Convert 4-valued Outcome to McpResult for JSON-RPC response
934        let contents = outcome.into_mcp_result()?;
935
936        Ok(ReadResourceResult { contents })
937    }
938
939    /// Handles the prompts/list request.
940    ///
941    /// If session_state is provided, disabled prompts will be filtered out.
942    /// If include_tags/exclude_tags are provided, prompts are filtered by tags.
943    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    /// Handles the prompts/get request.
963    ///
964    /// # Arguments
965    ///
966    /// * `cx` - The asupersync context for cancellation and tracing
967    /// * `request_id` - Internal request ID for tracking
968    /// * `params` - The prompt get parameters including name and arguments
969    /// * `budget` - Request budget for timeout enforcement
970    /// * `session_state` - Session state for per-session storage
971    /// * `notification_sender` - Optional callback for sending progress notifications
972    /// * `bidirectional_senders` - Optional senders for sampling/elicitation
973    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        // Check cancellation
987        if cx.is_cancel_requested() {
988            return Err(McpError::request_cancelled());
989        }
990
991        // Check budget exhaustion
992        if budget.is_exhausted() {
993            return Err(McpError::new(
994                McpErrorCode::RequestCancelled,
995                "Request budget exhausted",
996            ));
997        }
998
999        // Check if prompt is disabled for this session
1000        if !session_state.is_prompt_enabled(&params.name) {
1001            return Err(McpError::new(
1002                McpErrorCode::PromptNotFound,
1003                format!("Prompt '{}' is disabled for this session", params.name),
1004            ));
1005        }
1006
1007        // Find the prompt handler
1008        let handler = self.prompts.get(&params.name).ok_or_else(|| {
1009            McpError::new(
1010                fastmcp_core::McpErrorCode::PromptNotFound,
1011                format!("Prompt not found: {}", params.name),
1012            )
1013        })?;
1014
1015        // Extract progress token from request metadata
1016        let progress_token: Option<ProgressToken> =
1017            params.meta.as_ref().and_then(|m| m.progress_token.clone());
1018
1019        // Create context for the handler with progress reporting, session state, and bidirectional senders
1020        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                // Attach bidirectional senders even without progress
1037                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        // Get the prompt asynchronously - returns McpOutcome (4-valued)
1050        let arguments = params.arguments.unwrap_or_default();
1051        let outcome = block_on(handler.get_async(&ctx, arguments));
1052
1053        // Convert 4-valued Outcome to McpResult for JSON-RPC response
1054        let messages = outcome.into_mcp_result()?;
1055
1056        Ok(GetPromptResult {
1057            description: handler.definition().description,
1058            messages,
1059        })
1060    }
1061
1062    // ========================================================================
1063    // Task Dispatch Methods (Docket/SEP-1686)
1064    // ========================================================================
1065
1066    /// Handles the tasks/list request.
1067    ///
1068    /// Lists all background tasks, optionally filtered by status.
1069    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, // Pagination not yet implemented
1088        })
1089    }
1090
1091    /// Handles the tasks/get request.
1092    ///
1093    /// Gets information about a specific task, including its result if completed.
1094    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(&params.id)
1111            .ok_or_else(|| McpError::invalid_params(format!("Task not found: {}", params.id)))?;
1112
1113        let result = task_manager.get_result(&params.id);
1114
1115        Ok(GetTaskResult { task, result })
1116    }
1117
1118    /// Handles the tasks/cancel request.
1119    ///
1120    /// Requests cancellation of a running or pending task.
1121    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(&params.id, params.reason)?;
1137
1138        Ok(CancelTaskResult {
1139            cancelled: true,
1140            task,
1141        })
1142    }
1143
1144    /// Handles the tasks/submit request.
1145    ///
1146    /// Submits a new background task for execution.
1147    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, &params.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// ============================================================================
1178// Mount/Composition Support
1179// ============================================================================
1180
1181/// Result of a mount operation.
1182#[derive(Debug, Default)]
1183pub struct MountResult {
1184    /// Number of tools mounted.
1185    pub tools: usize,
1186    /// Number of resources mounted.
1187    pub resources: usize,
1188    /// Number of resource templates mounted.
1189    pub resource_templates: usize,
1190    /// Number of prompts mounted.
1191    pub prompts: usize,
1192    /// Any warnings generated during mounting (e.g., name conflicts).
1193    pub warnings: Vec<String>,
1194}
1195
1196impl MountResult {
1197    /// Returns true if any components were mounted.
1198    #[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    /// Returns true if mounting was successful (currently always true).
1204    #[must_use]
1205    pub fn is_success(&self) -> bool {
1206        true
1207    }
1208}
1209
1210impl Router {
1211    /// Applies a prefix to a name or URI.
1212    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    /// Validates a prefix string.
1220    ///
1221    /// Prefixes must be alphanumeric plus underscores and hyphens,
1222    /// and cannot contain slashes.
1223    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        // Allow alphanumeric, underscore, hyphen
1231        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    /// Mounts all handlers from another router with an optional prefix.
1243    ///
1244    /// This consumes the source router and moves its handlers into this router.
1245    /// Names/URIs are prefixed with `prefix/` if a prefix is provided.
1246    ///
1247    /// # Example
1248    ///
1249    /// ```ignore
1250    /// let mut main_router = Router::new();
1251    /// let db_router = Router::new();
1252    /// // ... add handlers to db_router ...
1253    ///
1254    /// main_router.mount(db_router, Some("db"));
1255    /// // Tool "query" becomes "db/query"
1256    /// ```
1257    pub fn mount(&mut self, other: Router, prefix: Option<&str>) -> MountResult {
1258        let mut result = MountResult::default();
1259
1260        // Validate prefix
1261        if let Some(p) = prefix {
1262            if let Err(e) = Self::validate_prefix(p) {
1263                result.warnings.push(e);
1264                // Continue anyway, but log the warning
1265            }
1266        }
1267
1268        // Mount tools
1269        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        // Mount resources
1274        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        // Mount resource templates
1279        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        // Mount prompts
1284        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        // Log mount result
1289        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    /// Mounts only tools from a router.
1305    pub fn mount_tools(&mut self, other: Router, prefix: Option<&str>) -> MountResult {
1306        self.mount_tools_from(other.tools, prefix)
1307    }
1308
1309    /// Internal: mount tools from a HashMap.
1310    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            // Check for conflicts
1329            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            // Wrap with mounted name and insert
1337            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    /// Mounts only resources from a router.
1346    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    /// Internal: mount resources from a HashMap.
1355    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            // Check for conflicts
1374            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            // Wrap with mounted URI and insert
1382            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    /// Internal: mount resource templates from a HashMap.
1391    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            // Check for conflicts
1410            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            // Create new template with mounted URI
1418            let mut mounted_template = entry.template.clone();
1419            mounted_template.uri_template = mounted_uri_template.clone();
1420
1421            // Wrap handler if present
1422            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            // Create new entry with mounted template
1433            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        // Rebuild sorted keys if we added templates
1445        if result.resource_templates > 0 {
1446            self.rebuild_sorted_template_keys();
1447        }
1448
1449        result
1450    }
1451
1452    /// Mounts only prompts from a router.
1453    pub fn mount_prompts(&mut self, other: Router, prefix: Option<&str>) -> MountResult {
1454        self.mount_prompts_from(other.prompts, prefix)
1455    }
1456
1457    /// Internal: mount prompts from a HashMap.
1458    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            // Check for conflicts
1477            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            // Wrap with mounted name and insert
1485            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    /// Consumes the router and returns its internal handlers.
1494    ///
1495    /// This is used internally for mounting operations.
1496    #[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
1520/// Entry for a resource template with its matcher and optional handler.
1521pub(crate) struct ResourceTemplateEntry {
1522    pub(crate) matcher: UriTemplate,
1523    pub(crate) template: ResourceTemplate,
1524    pub(crate) handler: Option<BoxedResourceHandler>,
1525}
1526
1527/// A parsed URI template for matching resource URIs.
1528#[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    /// Creates a new URI template from a pattern.
1550    ///
1551    /// If the pattern is invalid, logs a warning and returns a template
1552    /// that will never match any URI (fail-safe behavior).
1553    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            // Return a template with no segments that can never match
1562            Self {
1563                pattern: pattern.to_string(),
1564                segments: vec![UriSegment::Literal("\0INVALID\0".to_string())],
1565            }
1566        })
1567    }
1568
1569    /// Attempts to create a URI template, returning an error if invalid.
1570    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                        // Last param: only allow "/" when this is the sole param.
1680                        // Multi-param templates should not let the tail param
1681                        // consume extra path segments.
1682                        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
1756// ============================================================================
1757// Resource Reader Implementation
1758// ============================================================================
1759
1760use fastmcp_core::{
1761    MAX_RESOURCE_READ_DEPTH, ResourceContentItem, ResourceReadResult, ResourceReader,
1762};
1763use std::pin::Pin;
1764
1765/// A wrapper that implements `ResourceReader` for a shared `Router`.
1766///
1767/// This allows handlers to read resources from within tool/resource/prompt
1768/// handlers, enabling cross-component access.
1769pub struct RouterResourceReader {
1770    /// The shared router.
1771    router: Arc<Router>,
1772    /// Session state for handlers.
1773    session_state: SessionState,
1774}
1775
1776impl RouterResourceReader {
1777    /// Creates a new resource reader with the given router and session state.
1778    #[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        // Check recursion depth
1801        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        // Clone what we need for the async block
1814        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            // Resolve the resource
1823            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            // Create a child context with incremented depth
1831            // Clone router again for the nested reader (the original is borrowed by resolved)
1832            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            // Read the resource
1842            let outcome = block_on(resolved.handler.read_async_with_uri(
1843                &child_ctx,
1844                &uri,
1845                &resolved.params,
1846            ));
1847
1848            // Convert outcome to result
1849            let contents = outcome.into_mcp_result()?;
1850
1851            // Convert protocol ResourceContent to core ResourceContentItem
1852            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
1867// ============================================================================
1868// Tool Caller Implementation
1869// ============================================================================
1870
1871use fastmcp_core::{MAX_TOOL_CALL_DEPTH, ToolCallResult, ToolCaller, ToolContentItem};
1872
1873/// A wrapper that implements `ToolCaller` for a shared `Router`.
1874///
1875/// This allows handlers to call other tools from within tool/resource/prompt
1876/// handlers, enabling cross-component access.
1877pub struct RouterToolCaller {
1878    /// The shared router.
1879    router: Arc<Router>,
1880    /// Session state for handlers.
1881    session_state: SessionState,
1882}
1883
1884impl RouterToolCaller {
1885    /// Creates a new tool caller with the given router and session state.
1886    #[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        // Check recursion depth
1906        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        // Clone what we need for the async block
1916        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            // Find the tool handler
1925            let handler = router
1926                .tools
1927                .get(&name)
1928                .ok_or_else(|| McpError::method_not_found(&format!("tool: {}", name)))?;
1929
1930            // Validate arguments against the tool's input schema
1931            let tool_def = handler.definition();
1932
1933            // Use strict or lenient validation based on router configuration
1934            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            // Create a child context with incremented depth
1952            // Clone router again for nested calls
1953            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            // Call the tool
1967            let outcome = block_on(handler.call_async(&child_ctx, args));
1968
1969            // Convert outcome to result
1970            match outcome {
1971                Outcome::Ok(content) => {
1972                    // Convert protocol Content to core ToolContentItem
1973                    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                    // Tool errors become error results, not failures
1992                    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}