Skip to main content

fastmcp_server/
handler.rs

1//! Handler traits for tools, resources, and prompts.
2//!
3//! Handlers support both synchronous and asynchronous execution patterns:
4//!
5//! - **Sync handlers**: Implement `call()`, `read()`, or `get()` directly
6//! - **Async handlers**: Override `call_async()`, `read_async()`, or `get_async()`
7//!
8//! The router always calls the async variants, which by default delegate to
9//! the sync versions. This allows gradual migration to async without breaking
10//! existing code.
11
12use std::collections::HashMap;
13use std::future::Future;
14use std::pin::Pin;
15use std::sync::Arc;
16use std::time::Duration;
17
18use fastmcp_core::{
19    McpContext, McpOutcome, McpResult, NotificationSender, Outcome, ProgressReporter, SessionState,
20};
21use fastmcp_protocol::{
22    Content, Icon, JsonRpcRequest, ProgressParams, ProgressToken, Prompt, PromptMessage, Resource,
23    ResourceContent, ResourceTemplate, Tool, ToolAnnotations,
24};
25
26// ============================================================================
27// Progress Notification Sender
28// ============================================================================
29
30/// A notification sender that sends progress notifications via a callback.
31///
32/// This is the server-side implementation used to send notifications back
33/// to the client during handler execution.
34pub struct ProgressNotificationSender<F>
35where
36    F: Fn(JsonRpcRequest) + Send + Sync,
37{
38    /// The progress token from the original request.
39    token: ProgressToken,
40    /// Callback to send notifications.
41    send_fn: F,
42}
43
44impl<F> ProgressNotificationSender<F>
45where
46    F: Fn(JsonRpcRequest) + Send + Sync,
47{
48    /// Creates a new progress notification sender.
49    pub fn new(token: ProgressToken, send_fn: F) -> Self {
50        Self { token, send_fn }
51    }
52
53    /// Creates a progress reporter from this sender.
54    pub fn into_reporter(self) -> ProgressReporter
55    where
56        Self: 'static,
57    {
58        ProgressReporter::new(Arc::new(self))
59    }
60}
61
62impl<F> NotificationSender for ProgressNotificationSender<F>
63where
64    F: Fn(JsonRpcRequest) + Send + Sync,
65{
66    fn send_progress(&self, progress: f64, total: Option<f64>, message: Option<&str>) {
67        let params = match total {
68            Some(t) => ProgressParams::with_total(self.token.clone(), progress, t),
69            None => ProgressParams::new(self.token.clone(), progress),
70        };
71
72        let params = if let Some(msg) = message {
73            params.with_message(msg)
74        } else {
75            params
76        };
77
78        // Create a notification (request without id)
79        let notification = JsonRpcRequest::notification(
80            "notifications/progress",
81            Some(serde_json::to_value(&params).unwrap_or_default()),
82        );
83
84        (self.send_fn)(notification);
85    }
86}
87
88impl<F> std::fmt::Debug for ProgressNotificationSender<F>
89where
90    F: Fn(JsonRpcRequest) + Send + Sync,
91{
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        f.debug_struct("ProgressNotificationSender")
94            .field("token", &self.token)
95            .finish_non_exhaustive()
96    }
97}
98
99/// Configuration for bidirectional senders to attach to context.
100#[derive(Clone, Default)]
101pub struct BidirectionalSenders {
102    /// Optional sampling sender for LLM completions.
103    pub sampling: Option<Arc<dyn fastmcp_core::SamplingSender>>,
104    /// Optional elicitation sender for user input requests.
105    pub elicitation: Option<Arc<dyn fastmcp_core::ElicitationSender>>,
106}
107
108impl BidirectionalSenders {
109    /// Creates empty senders (no bidirectional features).
110    #[must_use]
111    pub fn new() -> Self {
112        Self::default()
113    }
114
115    /// Sets the sampling sender.
116    #[must_use]
117    pub fn with_sampling(mut self, sender: Arc<dyn fastmcp_core::SamplingSender>) -> Self {
118        self.sampling = Some(sender);
119        self
120    }
121
122    /// Sets the elicitation sender.
123    #[must_use]
124    pub fn with_elicitation(mut self, sender: Arc<dyn fastmcp_core::ElicitationSender>) -> Self {
125        self.elicitation = Some(sender);
126        self
127    }
128}
129
130impl std::fmt::Debug for BidirectionalSenders {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.debug_struct("BidirectionalSenders")
133            .field("sampling", &self.sampling.is_some())
134            .field("elicitation", &self.elicitation.is_some())
135            .finish()
136    }
137}
138
139/// Helper to create an McpContext with optional progress reporting and session state.
140pub fn create_context_with_progress<F>(
141    cx: asupersync::Cx,
142    request_id: u64,
143    progress_token: Option<ProgressToken>,
144    state: Option<SessionState>,
145    send_fn: F,
146) -> McpContext
147where
148    F: Fn(JsonRpcRequest) + Send + Sync + 'static,
149{
150    create_context_with_progress_and_senders(cx, request_id, progress_token, state, send_fn, None)
151}
152
153/// Helper to create an McpContext with optional progress reporting, session state, and bidirectional senders.
154pub fn create_context_with_progress_and_senders<F>(
155    cx: asupersync::Cx,
156    request_id: u64,
157    progress_token: Option<ProgressToken>,
158    state: Option<SessionState>,
159    send_fn: F,
160    senders: Option<&BidirectionalSenders>,
161) -> McpContext
162where
163    F: Fn(JsonRpcRequest) + Send + Sync + 'static,
164{
165    let mut ctx = match (progress_token, state) {
166        (Some(token), Some(state)) => {
167            let sender = ProgressNotificationSender::new(token, send_fn);
168            McpContext::with_state_and_progress(cx, request_id, state, sender.into_reporter())
169        }
170        (Some(token), None) => {
171            let sender = ProgressNotificationSender::new(token, send_fn);
172            McpContext::with_progress(cx, request_id, sender.into_reporter())
173        }
174        (None, Some(state)) => McpContext::with_state(cx, request_id, state),
175        (None, None) => McpContext::new(cx, request_id),
176    };
177
178    // Attach bidirectional senders if provided
179    if let Some(senders) = senders {
180        if let Some(ref sampling) = senders.sampling {
181            ctx = ctx.with_sampling(sampling.clone());
182        }
183        if let Some(ref elicitation) = senders.elicitation {
184            ctx = ctx.with_elicitation(elicitation.clone());
185        }
186    }
187
188    ctx
189}
190
191/// A boxed future for async handler results.
192pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
193
194/// URI template parameters extracted from a matched resource URI.
195pub type UriParams = HashMap<String, String>;
196
197/// Handler for a tool.
198///
199/// This trait is typically implemented via the `#[tool]` macro.
200///
201/// # Sync vs Async
202///
203/// By default, implement `call()` for synchronous execution. For async tools,
204/// override `call_async()` instead. The router always calls `call_async()`,
205/// which defaults to running `call()` in an async block.
206///
207/// # Return Type
208///
209/// Async handlers return `McpOutcome<Vec<Content>>`, a 4-valued type supporting:
210/// - `Ok(content)` - Successful result
211/// - `Err(McpError)` - Recoverable error
212/// - `Cancelled` - Request was cancelled
213/// - `Panicked` - Unrecoverable failure
214pub trait ToolHandler: Send + Sync {
215    /// Returns the tool definition.
216    fn definition(&self) -> Tool;
217
218    /// Returns the tool's icon, if any.
219    ///
220    /// Default implementation returns `None`. Override to provide an icon.
221    /// Note: Icons can also be set directly in `definition()`.
222    fn icon(&self) -> Option<&Icon> {
223        None
224    }
225
226    /// Returns the tool's version, if any.
227    ///
228    /// Default implementation returns `None`. Override to provide a version.
229    /// Note: Version can also be set directly in `definition()`.
230    fn version(&self) -> Option<&str> {
231        None
232    }
233
234    /// Returns the tool's tags for filtering and organization.
235    ///
236    /// Default implementation returns an empty slice. Override to provide tags.
237    /// Note: Tags can also be set directly in `definition()`.
238    fn tags(&self) -> &[String] {
239        &[]
240    }
241
242    /// Returns the tool's annotations providing behavioral hints.
243    ///
244    /// Default implementation returns `None`. Override to provide annotations
245    /// like `destructive`, `idempotent`, `read_only`, or `open_world_hint`.
246    /// Note: Annotations can also be set directly in `definition()`.
247    fn annotations(&self) -> Option<&ToolAnnotations> {
248        None
249    }
250
251    /// Returns the tool's output schema (JSON Schema).
252    ///
253    /// Default implementation returns `None`. Override to provide a schema
254    /// that describes the structure of the tool's output.
255    /// Note: Output schema can also be set directly in `definition()`.
256    fn output_schema(&self) -> Option<serde_json::Value> {
257        None
258    }
259
260    /// Returns the tool's custom timeout duration.
261    ///
262    /// Default implementation returns `None`, meaning the server's default
263    /// timeout applies. Override to specify a per-handler timeout.
264    ///
265    /// When set, creates a child budget with the specified timeout that
266    /// overrides the server's default timeout for this handler.
267    fn timeout(&self) -> Option<Duration> {
268        None
269    }
270
271    /// Calls the tool synchronously with the given arguments.
272    ///
273    /// This is the default implementation point. Override this for simple
274    /// synchronous tools. Returns `McpResult` which is converted to `McpOutcome`
275    /// by the async wrapper.
276    fn call(&self, ctx: &McpContext, arguments: serde_json::Value) -> McpResult<Vec<Content>>;
277
278    /// Calls the tool asynchronously with the given arguments.
279    ///
280    /// Override this for tools that need true async execution (e.g., I/O-bound
281    /// operations, database queries, HTTP requests).
282    ///
283    /// Returns `McpOutcome` to properly represent all four states: success,
284    /// error, cancellation, and panic.
285    ///
286    /// The default implementation delegates to the sync `call()` method and
287    /// converts the `McpResult` to `McpOutcome`.
288    fn call_async<'a>(
289        &'a self,
290        ctx: &'a McpContext,
291        arguments: serde_json::Value,
292    ) -> BoxFuture<'a, McpOutcome<Vec<Content>>> {
293        Box::pin(async move {
294            match self.call(ctx, arguments) {
295                Ok(v) => Outcome::Ok(v),
296                Err(e) => Outcome::Err(e),
297            }
298        })
299    }
300}
301
302/// Handler for a resource.
303///
304/// This trait is typically implemented via the `#[resource]` macro.
305///
306/// # Sync vs Async
307///
308/// By default, implement `read()` for synchronous execution. For async resources,
309/// override `read_async()` instead. The router uses `read_async_with_uri()` so
310/// implementations can access matched URI parameters when needed.
311/// which defaults to running `read()` in an async block.
312///
313/// # Return Type
314///
315/// Async handlers return `McpOutcome<Vec<ResourceContent>>`, a 4-valued type.
316pub trait ResourceHandler: Send + Sync {
317    /// Returns the resource definition.
318    fn definition(&self) -> Resource;
319
320    /// Returns the resource template definition, if this resource uses a URI template.
321    fn template(&self) -> Option<ResourceTemplate> {
322        None
323    }
324
325    /// Returns the resource's icon, if any.
326    ///
327    /// Default implementation returns `None`. Override to provide an icon.
328    /// Note: Icons can also be set directly in `definition()`.
329    fn icon(&self) -> Option<&Icon> {
330        None
331    }
332
333    /// Returns the resource's version, if any.
334    ///
335    /// Default implementation returns `None`. Override to provide a version.
336    /// Note: Version can also be set directly in `definition()`.
337    fn version(&self) -> Option<&str> {
338        None
339    }
340
341    /// Returns the resource's tags for filtering and organization.
342    ///
343    /// Default implementation returns an empty slice. Override to provide tags.
344    /// Note: Tags can also be set directly in `definition()`.
345    fn tags(&self) -> &[String] {
346        &[]
347    }
348
349    /// Returns the resource's custom timeout duration.
350    ///
351    /// Default implementation returns `None`, meaning the server's default
352    /// timeout applies. Override to specify a per-handler timeout.
353    fn timeout(&self) -> Option<Duration> {
354        None
355    }
356
357    /// Reads the resource content synchronously.
358    ///
359    /// This is the default implementation point. Override this for simple
360    /// synchronous resources. Returns `McpResult` which is converted to `McpOutcome`
361    /// by the async wrapper.
362    fn read(&self, ctx: &McpContext) -> McpResult<Vec<ResourceContent>>;
363
364    /// Reads the resource content synchronously with the matched URI and parameters.
365    ///
366    /// Default implementation ignores URI params and delegates to `read()`.
367    fn read_with_uri(
368        &self,
369        ctx: &McpContext,
370        _uri: &str,
371        _params: &UriParams,
372    ) -> McpResult<Vec<ResourceContent>> {
373        self.read(ctx)
374    }
375
376    /// Reads the resource content asynchronously with the matched URI and parameters.
377    ///
378    /// Default implementation delegates to the sync `read_with_uri()` method.
379    fn read_async_with_uri<'a>(
380        &'a self,
381        ctx: &'a McpContext,
382        uri: &'a str,
383        params: &'a UriParams,
384    ) -> BoxFuture<'a, McpOutcome<Vec<ResourceContent>>> {
385        Box::pin(async move {
386            if params.is_empty() {
387                self.read_async(ctx).await
388            } else {
389                match self.read_with_uri(ctx, uri, params) {
390                    Ok(v) => Outcome::Ok(v),
391                    Err(e) => Outcome::Err(e),
392                }
393            }
394        })
395    }
396
397    /// Reads the resource content asynchronously.
398    ///
399    /// Override this for resources that need true async execution (e.g., file I/O,
400    /// database queries, remote fetches).
401    ///
402    /// Returns `McpOutcome` to properly represent all four states.
403    ///
404    /// The default implementation delegates to the sync `read()` method.
405    fn read_async<'a>(
406        &'a self,
407        ctx: &'a McpContext,
408    ) -> BoxFuture<'a, McpOutcome<Vec<ResourceContent>>> {
409        Box::pin(async move {
410            match self.read(ctx) {
411                Ok(v) => Outcome::Ok(v),
412                Err(e) => Outcome::Err(e),
413            }
414        })
415    }
416}
417
418/// Handler for a prompt.
419///
420/// This trait is typically implemented via the `#[prompt]` macro.
421///
422/// # Sync vs Async
423///
424/// By default, implement `get()` for synchronous execution. For async prompts,
425/// override `get_async()` instead. The router always calls `get_async()`,
426/// which defaults to running `get()` in an async block.
427///
428/// # Return Type
429///
430/// Async handlers return `McpOutcome<Vec<PromptMessage>>`, a 4-valued type.
431pub trait PromptHandler: Send + Sync {
432    /// Returns the prompt definition.
433    fn definition(&self) -> Prompt;
434
435    /// Returns the prompt's icon, if any.
436    ///
437    /// Default implementation returns `None`. Override to provide an icon.
438    /// Note: Icons can also be set directly in `definition()`.
439    fn icon(&self) -> Option<&Icon> {
440        None
441    }
442
443    /// Returns the prompt's version, if any.
444    ///
445    /// Default implementation returns `None`. Override to provide a version.
446    /// Note: Version can also be set directly in `definition()`.
447    fn version(&self) -> Option<&str> {
448        None
449    }
450
451    /// Returns the prompt's tags for filtering and organization.
452    ///
453    /// Default implementation returns an empty slice. Override to provide tags.
454    /// Note: Tags can also be set directly in `definition()`.
455    fn tags(&self) -> &[String] {
456        &[]
457    }
458
459    /// Returns the prompt's custom timeout duration.
460    ///
461    /// Default implementation returns `None`, meaning the server's default
462    /// timeout applies. Override to specify a per-handler timeout.
463    fn timeout(&self) -> Option<Duration> {
464        None
465    }
466
467    /// Gets the prompt messages synchronously with the given arguments.
468    ///
469    /// This is the default implementation point. Override this for simple
470    /// synchronous prompts. Returns `McpResult` which is converted to `McpOutcome`
471    /// by the async wrapper.
472    fn get(
473        &self,
474        ctx: &McpContext,
475        arguments: std::collections::HashMap<String, String>,
476    ) -> McpResult<Vec<PromptMessage>>;
477
478    /// Gets the prompt messages asynchronously with the given arguments.
479    ///
480    /// Override this for prompts that need true async execution (e.g., template
481    /// fetching, dynamic content generation).
482    ///
483    /// Returns `McpOutcome` to properly represent all four states.
484    ///
485    /// The default implementation delegates to the sync `get()` method.
486    fn get_async<'a>(
487        &'a self,
488        ctx: &'a McpContext,
489        arguments: std::collections::HashMap<String, String>,
490    ) -> BoxFuture<'a, McpOutcome<Vec<PromptMessage>>> {
491        Box::pin(async move {
492            match self.get(ctx, arguments) {
493                Ok(v) => Outcome::Ok(v),
494                Err(e) => Outcome::Err(e),
495            }
496        })
497    }
498}
499
500/// A boxed tool handler.
501pub type BoxedToolHandler = Box<dyn ToolHandler>;
502
503/// A boxed resource handler.
504pub type BoxedResourceHandler = Box<dyn ResourceHandler>;
505
506/// A boxed prompt handler.
507pub type BoxedPromptHandler = Box<dyn PromptHandler>;
508
509// ============================================================================
510// Mounted Handler Wrappers
511// ============================================================================
512
513/// A wrapper for a tool handler that overrides its name.
514///
515/// Used by `mount()` to prefix tool names when mounting from another server.
516pub struct MountedToolHandler {
517    inner: BoxedToolHandler,
518    mounted_name: String,
519}
520
521impl MountedToolHandler {
522    /// Creates a new mounted tool handler with the given name.
523    pub fn new(inner: BoxedToolHandler, mounted_name: String) -> Self {
524        Self {
525            inner,
526            mounted_name,
527        }
528    }
529}
530
531impl ToolHandler for MountedToolHandler {
532    fn definition(&self) -> Tool {
533        let mut def = self.inner.definition();
534        def.name.clone_from(&self.mounted_name);
535        def
536    }
537
538    fn tags(&self) -> &[String] {
539        self.inner.tags()
540    }
541
542    fn annotations(&self) -> Option<&ToolAnnotations> {
543        self.inner.annotations()
544    }
545
546    fn output_schema(&self) -> Option<serde_json::Value> {
547        self.inner.output_schema()
548    }
549
550    fn timeout(&self) -> Option<Duration> {
551        self.inner.timeout()
552    }
553
554    fn call(&self, ctx: &McpContext, arguments: serde_json::Value) -> McpResult<Vec<Content>> {
555        self.inner.call(ctx, arguments)
556    }
557
558    fn call_async<'a>(
559        &'a self,
560        ctx: &'a McpContext,
561        arguments: serde_json::Value,
562    ) -> BoxFuture<'a, McpOutcome<Vec<Content>>> {
563        self.inner.call_async(ctx, arguments)
564    }
565}
566
567/// A wrapper for a resource handler that overrides its URI.
568///
569/// Used by `mount()` to prefix resource URIs when mounting from another server.
570pub struct MountedResourceHandler {
571    inner: BoxedResourceHandler,
572    mounted_uri: String,
573    mounted_template: Option<ResourceTemplate>,
574}
575
576impl MountedResourceHandler {
577    /// Creates a new mounted resource handler with the given URI.
578    pub fn new(inner: BoxedResourceHandler, mounted_uri: String) -> Self {
579        Self {
580            inner,
581            mounted_uri,
582            mounted_template: None,
583        }
584    }
585
586    /// Creates a new mounted resource handler with a mounted template.
587    pub fn with_template(
588        inner: BoxedResourceHandler,
589        mounted_uri: String,
590        mounted_template: ResourceTemplate,
591    ) -> Self {
592        Self {
593            inner,
594            mounted_uri,
595            mounted_template: Some(mounted_template),
596        }
597    }
598}
599
600impl ResourceHandler for MountedResourceHandler {
601    fn definition(&self) -> Resource {
602        let mut def = self.inner.definition();
603        def.uri.clone_from(&self.mounted_uri);
604        def
605    }
606
607    fn template(&self) -> Option<ResourceTemplate> {
608        self.mounted_template.clone()
609    }
610
611    fn tags(&self) -> &[String] {
612        self.inner.tags()
613    }
614
615    fn timeout(&self) -> Option<Duration> {
616        self.inner.timeout()
617    }
618
619    fn read(&self, ctx: &McpContext) -> McpResult<Vec<ResourceContent>> {
620        self.inner.read(ctx)
621    }
622
623    fn read_with_uri(
624        &self,
625        ctx: &McpContext,
626        uri: &str,
627        params: &UriParams,
628    ) -> McpResult<Vec<ResourceContent>> {
629        self.inner.read_with_uri(ctx, uri, params)
630    }
631
632    fn read_async_with_uri<'a>(
633        &'a self,
634        ctx: &'a McpContext,
635        uri: &'a str,
636        params: &'a UriParams,
637    ) -> BoxFuture<'a, McpOutcome<Vec<ResourceContent>>> {
638        self.inner.read_async_with_uri(ctx, uri, params)
639    }
640
641    fn read_async<'a>(
642        &'a self,
643        ctx: &'a McpContext,
644    ) -> BoxFuture<'a, McpOutcome<Vec<ResourceContent>>> {
645        self.inner.read_async(ctx)
646    }
647}
648
649/// A wrapper for a prompt handler that overrides its name.
650///
651/// Used by `mount()` to prefix prompt names when mounting from another server.
652pub struct MountedPromptHandler {
653    inner: BoxedPromptHandler,
654    mounted_name: String,
655}
656
657impl MountedPromptHandler {
658    /// Creates a new mounted prompt handler with the given name.
659    pub fn new(inner: BoxedPromptHandler, mounted_name: String) -> Self {
660        Self {
661            inner,
662            mounted_name,
663        }
664    }
665}
666
667impl PromptHandler for MountedPromptHandler {
668    fn definition(&self) -> Prompt {
669        let mut def = self.inner.definition();
670        def.name.clone_from(&self.mounted_name);
671        def
672    }
673
674    fn tags(&self) -> &[String] {
675        self.inner.tags()
676    }
677
678    fn timeout(&self) -> Option<Duration> {
679        self.inner.timeout()
680    }
681
682    fn get(
683        &self,
684        ctx: &McpContext,
685        arguments: std::collections::HashMap<String, String>,
686    ) -> McpResult<Vec<PromptMessage>> {
687        self.inner.get(ctx, arguments)
688    }
689
690    fn get_async<'a>(
691        &'a self,
692        ctx: &'a McpContext,
693        arguments: std::collections::HashMap<String, String>,
694    ) -> BoxFuture<'a, McpOutcome<Vec<PromptMessage>>> {
695        self.inner.get_async(ctx, arguments)
696    }
697}