Skip to main content

mcp_kit/server/
handler.rs

1use std::{future::Future, pin::Pin, sync::Arc};
2
3use crate::{
4    error::{McpError, McpResult},
5    types::{
6        messages::{CallToolRequest, GetPromptRequest, ReadResourceRequest},
7        prompt::GetPromptResult,
8        resource::ReadResourceResult,
9        tool::CallToolResult,
10    },
11};
12use serde::de::DeserializeOwned;
13
14/// A type-erased, boxed future
15pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
16
17/// Boxed handler function for tool calls
18pub type HandlerFn<Req, Res> =
19    Arc<dyn Fn(Req) -> BoxFuture<'static, McpResult<Res>> + Send + Sync + 'static>;
20
21pub type ToolHandlerFn = HandlerFn<CallToolRequest, CallToolResult>;
22pub type ResourceHandlerFn = HandlerFn<ReadResourceRequest, ReadResourceResult>;
23pub type PromptHandlerFn = HandlerFn<GetPromptRequest, GetPromptResult>;
24
25// ─── IntoToolResult ───────────────────────────────────────────────────────────
26
27/// Anything that can be returned from a tool handler
28pub trait IntoToolResult {
29    fn into_tool_result(self) -> CallToolResult;
30}
31
32impl IntoToolResult for CallToolResult {
33    fn into_tool_result(self) -> CallToolResult {
34        self
35    }
36}
37
38impl IntoToolResult for String {
39    fn into_tool_result(self) -> CallToolResult {
40        CallToolResult::text(self)
41    }
42}
43
44impl IntoToolResult for &str {
45    fn into_tool_result(self) -> CallToolResult {
46        CallToolResult::text(self)
47    }
48}
49
50impl IntoToolResult for serde_json::Value {
51    fn into_tool_result(self) -> CallToolResult {
52        CallToolResult::text(self.to_string())
53    }
54}
55
56impl<T: IntoToolResult, E: std::fmt::Display> IntoToolResult for Result<T, E> {
57    fn into_tool_result(self) -> CallToolResult {
58        match self {
59            Ok(v) => v.into_tool_result(),
60            Err(e) => CallToolResult::error(e.to_string()),
61        }
62    }
63}
64
65// ─── ToolHandler trait ────────────────────────────────────────────────────────
66
67/// Implemented for async functions that can serve as tool handlers.
68///
69/// Supports two calling conventions:
70///   1. `|args: serde_json::Value| async { ... }` – raw JSON args
71///   2. `|params: MyStruct| async { ... }` – typed, deserialized args
72pub trait ToolHandler<Marker>: Clone + Send + Sync + 'static {
73    fn into_handler_fn(self) -> ToolHandlerFn;
74}
75
76/// Marker for typed (deserialised) handlers.
77/// Works for both `|params: MyStruct|` and `|args: serde_json::Value|` since
78/// `Value` implements `DeserializeOwned`.
79pub struct TypedMarker<T>(std::marker::PhantomData<T>);
80
81impl<F, Fut, R, T> ToolHandler<TypedMarker<T>> for F
82where
83    F: Fn(T) -> Fut + Clone + Send + Sync + 'static,
84    Fut: Future<Output = R> + Send + 'static,
85    R: IntoToolResult + Send + 'static,
86    T: DeserializeOwned + Send + 'static,
87{
88    fn into_handler_fn(self) -> ToolHandlerFn {
89        Arc::new(move |req: CallToolRequest| {
90            let f = self.clone();
91            let args = req.arguments.clone();
92            Box::pin(async move {
93                let params: T = serde_json::from_value(args)
94                    .map_err(|e| McpError::InvalidParams(e.to_string()))?;
95                Ok(f(params).await.into_tool_result())
96            })
97        })
98    }
99}
100
101// ─── AuthenticatedMarker — handler receives (T, Auth) ─────────────────────────
102
103/// Marker for handlers that declare an [`Auth`] extractor as their second parameter.
104///
105/// The generated handler reads the current identity from the task-local auth
106/// context set by `core.rs` before each dispatch. If no identity is present,
107/// the handler returns [`McpError::Unauthorized`] before the user function is
108/// even called.
109///
110/// [`Auth`]: crate::server::extract::Auth
111/// [`McpError::Unauthorized`]: crate::error::McpError::Unauthorized
112#[cfg(feature = "auth")]
113pub struct AuthenticatedMarker<T>(std::marker::PhantomData<T>);
114
115#[cfg(feature = "auth")]
116impl<F, Fut, R, T> ToolHandler<AuthenticatedMarker<T>> for F
117where
118    F: Fn(T, crate::server::extract::Auth) -> Fut + Clone + Send + Sync + 'static,
119    Fut: Future<Output = R> + Send + 'static,
120    R: IntoToolResult + Send + 'static,
121    T: serde::de::DeserializeOwned + Send + 'static,
122{
123    fn into_handler_fn(self) -> ToolHandlerFn {
124        Arc::new(move |req: CallToolRequest| {
125            let f = self.clone();
126            let args = req.arguments.clone();
127            Box::pin(async move {
128                let auth = crate::server::extract::Auth::from_context()?;
129                let params: T = serde_json::from_value(args)
130                    .map_err(|e| McpError::InvalidParams(e.to_string()))?;
131                Ok(f(params, auth).await.into_tool_result())
132            })
133        })
134    }
135}
136
137// ─── PromptHandler ───────────────────────────────────────────────────────────
138
139pub trait PromptHandler<Marker>: Clone + Send + Sync + 'static {
140    fn into_handler_fn(self) -> PromptHandlerFn;
141}
142
143pub struct PromptRawMarker;
144
145impl<F, Fut> PromptHandler<PromptRawMarker> for F
146where
147    F: Fn(GetPromptRequest) -> Fut + Clone + Send + Sync + 'static,
148    Fut: Future<Output = McpResult<GetPromptResult>> + Send + 'static,
149{
150    fn into_handler_fn(self) -> PromptHandlerFn {
151        Arc::new(move |req| {
152            let f = self.clone();
153            Box::pin(async move { f(req).await })
154        })
155    }
156}
157
158// ─── ResourceHandler ─────────────────────────────────────────────────────────
159
160pub trait ResourceHandler<Marker>: Clone + Send + Sync + 'static {
161    fn into_handler_fn(self) -> ResourceHandlerFn;
162}
163
164pub struct ResourceRawMarker;
165
166impl<F, Fut> ResourceHandler<ResourceRawMarker> for F
167where
168    F: Fn(ReadResourceRequest) -> Fut + Clone + Send + Sync + 'static,
169    Fut: Future<Output = McpResult<ReadResourceResult>> + Send + 'static,
170{
171    fn into_handler_fn(self) -> ResourceHandlerFn {
172        Arc::new(move |req| {
173            let f = self.clone();
174            Box::pin(async move { f(req).await })
175        })
176    }
177}