turbomcp_server/handlers/
utils.rs

1//! Utility functions for creating handlers from closures
2//!
3//! This module provides convenience functions for creating handler implementations
4//! from simple closures. These are primarily used by the `#[server]` macro but
5//! can also be used directly for quick handler creation.
6
7use std::collections::HashMap;
8use turbomcp_protocol::RequestContext;
9use turbomcp_protocol::types::{
10    CallToolRequest, CallToolResult, GetPromptRequest, GetPromptResult, Prompt,
11    ReadResourceRequest, ReadResourceResult, Resource, Tool, ToolInputSchema,
12};
13
14use crate::ServerResult;
15use crate::handlers::implementations::FunctionToolHandler;
16use crate::handlers::traits::{PromptHandler, ResourceHandler, ToolHandler};
17
18/// Create a tool handler from a closure
19///
20/// This is a convenience function for creating simple tool handlers without
21/// manually constructing Tool definitions.
22///
23/// # Arguments
24///
25/// * `name` - Tool name
26/// * `description` - Tool description
27/// * `handler` - Async closure that handles tool calls
28///
29/// # Examples
30///
31/// ```rust,no_run
32/// use turbomcp_server::handlers::utils::tool;
33/// use turbomcp_protocol::RequestContext;
34/// use turbomcp_protocol::types::{CallToolRequest, CallToolResult, Content, TextContent};
35///
36/// let my_tool = tool("echo", "Echoes back the input", |req: CallToolRequest, _ctx: RequestContext| async move {
37///     Ok(CallToolResult {
38///         content: vec![Content::Text(TextContent {
39///             text: format!("Echo: {:?}", req.arguments),
40///             annotations: None,
41///             meta: None,
42///         })],
43///         is_error: None,
44///         structured_content: None,
45///         _meta: None,
46///     })
47/// });
48/// ```
49pub fn tool<F, Fut>(
50    name: impl Into<String>,
51    description: impl Into<String>,
52    handler: F,
53) -> impl ToolHandler
54where
55    F: Fn(CallToolRequest, RequestContext) -> Fut + Send + Sync + 'static,
56    Fut: std::future::Future<Output = ServerResult<CallToolResult>> + Send + 'static,
57{
58    let name = name.into();
59    let description = description.into();
60
61    let tool_def = Tool {
62        name: name.clone(),
63        title: Some(name),
64        description: Some(description),
65        input_schema: ToolInputSchema {
66            schema_type: "object".to_string(),
67            properties: Some(HashMap::new()),
68            required: None,
69            additional_properties: None,
70        },
71        output_schema: None,
72        annotations: None,
73        meta: None,
74        ..Tool::default()
75    };
76
77    FunctionToolHandler::new(tool_def, handler)
78}
79
80/// Create a tool handler with a custom schema
81///
82/// This allows specifying the input schema for the tool, which is used by
83/// the `#[server]` macro to provide type-safe tool definitions.
84///
85/// # Arguments
86///
87/// * `name` - Tool name
88/// * `description` - Tool description
89/// * `schema` - Input schema for the tool
90/// * `handler` - Async closure that handles tool calls
91pub fn tool_with_schema<F, Fut>(
92    name: impl Into<String>,
93    description: impl Into<String>,
94    schema: ToolInputSchema,
95    handler: F,
96) -> impl ToolHandler
97where
98    F: Fn(CallToolRequest, RequestContext) -> Fut + Send + Sync + 'static,
99    Fut: std::future::Future<Output = ServerResult<CallToolResult>> + Send + 'static,
100{
101    let name = name.into();
102    let description = description.into();
103
104    let tool_def = Tool {
105        name: name.clone(),
106        title: Some(name),
107        description: Some(description),
108        input_schema: schema,
109        output_schema: None,
110        annotations: None,
111        meta: None,
112        ..Tool::default()
113    };
114
115    FunctionToolHandler::new(tool_def, handler)
116}
117
118/// Function-based prompt handler
119pub struct FunctionPromptHandler {
120    prompt: Prompt,
121    handler: Box<
122        dyn Fn(
123                GetPromptRequest,
124                RequestContext,
125            ) -> futures::future::BoxFuture<'static, ServerResult<GetPromptResult>>
126            + Send
127            + Sync,
128    >,
129}
130
131impl std::fmt::Debug for FunctionPromptHandler {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        f.debug_struct("FunctionPromptHandler")
134            .field("prompt", &self.prompt)
135            .finish()
136    }
137}
138
139impl FunctionPromptHandler {
140    /// Create new prompt handler
141    pub fn new<F, Fut>(prompt: Prompt, handler: F) -> Self
142    where
143        F: Fn(GetPromptRequest, RequestContext) -> Fut + Send + Sync + 'static,
144        Fut: std::future::Future<Output = ServerResult<GetPromptResult>> + Send + 'static,
145    {
146        Self {
147            prompt,
148            handler: Box::new(move |req, ctx| Box::pin(handler(req, ctx))),
149        }
150    }
151}
152
153#[async_trait::async_trait]
154impl PromptHandler for FunctionPromptHandler {
155    async fn handle(
156        &self,
157        request: GetPromptRequest,
158        ctx: RequestContext,
159    ) -> ServerResult<GetPromptResult> {
160        (self.handler)(request, ctx).await
161    }
162
163    fn prompt_definition(&self) -> Prompt {
164        self.prompt.clone()
165    }
166}
167
168/// Create a prompt handler from a closure
169///
170/// # Arguments
171///
172/// * `name` - Prompt name
173/// * `description` - Prompt description
174/// * `handler` - Async closure that handles prompt requests
175pub fn prompt<F, Fut>(
176    name: impl Into<String>,
177    description: impl Into<String>,
178    handler: F,
179) -> impl PromptHandler
180where
181    F: Fn(GetPromptRequest, RequestContext) -> Fut + Send + Sync + 'static,
182    Fut: std::future::Future<Output = ServerResult<GetPromptResult>> + Send + 'static,
183{
184    let name = name.into();
185    let description = description.into();
186
187    let prompt_def = Prompt {
188        name: name.clone(),
189        title: Some(name),
190        description: Some(description),
191        arguments: None,
192        meta: None,
193    };
194
195    FunctionPromptHandler::new(prompt_def, handler)
196}
197
198/// Function-based resource handler
199pub struct FunctionResourceHandler {
200    resource: Resource,
201    handler: Box<
202        dyn Fn(
203                ReadResourceRequest,
204                RequestContext,
205            ) -> futures::future::BoxFuture<'static, ServerResult<ReadResourceResult>>
206            + Send
207            + Sync,
208    >,
209}
210
211impl std::fmt::Debug for FunctionResourceHandler {
212    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213        f.debug_struct("FunctionResourceHandler")
214            .field("resource", &self.resource)
215            .finish()
216    }
217}
218
219impl FunctionResourceHandler {
220    /// Create new resource handler
221    pub fn new<F, Fut>(resource: Resource, handler: F) -> Self
222    where
223        F: Fn(ReadResourceRequest, RequestContext) -> Fut + Send + Sync + 'static,
224        Fut: std::future::Future<Output = ServerResult<ReadResourceResult>> + Send + 'static,
225    {
226        Self {
227            resource,
228            handler: Box::new(move |req, ctx| Box::pin(handler(req, ctx))),
229        }
230    }
231}
232
233#[async_trait::async_trait]
234impl ResourceHandler for FunctionResourceHandler {
235    async fn handle(
236        &self,
237        request: ReadResourceRequest,
238        ctx: RequestContext,
239    ) -> ServerResult<ReadResourceResult> {
240        (self.handler)(request, ctx).await
241    }
242
243    fn resource_definition(&self) -> Resource {
244        self.resource.clone()
245    }
246
247    async fn exists(&self, _uri: &str) -> bool {
248        true // Default implementation
249    }
250}
251
252/// Create a resource handler from a closure
253///
254/// # Arguments
255///
256/// * `uri` - Resource URI
257/// * `name` - Resource name
258/// * `handler` - Async closure that handles resource read requests
259pub fn resource<F, Fut>(
260    uri: impl Into<String>,
261    name: impl Into<String>,
262    handler: F,
263) -> impl ResourceHandler
264where
265    F: Fn(ReadResourceRequest, RequestContext) -> Fut + Send + Sync + 'static,
266    Fut: std::future::Future<Output = ServerResult<ReadResourceResult>> + Send + 'static,
267{
268    let uri = uri.into();
269    let name = name.into();
270
271    let resource_def = Resource {
272        name: name.clone(),
273        title: Some(name),
274        uri,
275        description: None,
276        mime_type: Some("text/plain".to_string()),
277        annotations: None,
278        size: None,
279        meta: None,
280    };
281
282    FunctionResourceHandler::new(resource_def, handler)
283}