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(¶ms).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}