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    };
75
76    FunctionToolHandler::new(tool_def, handler)
77}
78
79/// Create a tool handler with a custom schema
80///
81/// This allows specifying the input schema for the tool, which is used by
82/// the `#[server]` macro to provide type-safe tool definitions.
83///
84/// # Arguments
85///
86/// * `name` - Tool name
87/// * `description` - Tool description
88/// * `schema` - Input schema for the tool
89/// * `handler` - Async closure that handles tool calls
90pub fn tool_with_schema<F, Fut>(
91    name: impl Into<String>,
92    description: impl Into<String>,
93    schema: ToolInputSchema,
94    handler: F,
95) -> impl ToolHandler
96where
97    F: Fn(CallToolRequest, RequestContext) -> Fut + Send + Sync + 'static,
98    Fut: std::future::Future<Output = ServerResult<CallToolResult>> + Send + 'static,
99{
100    let name = name.into();
101    let description = description.into();
102
103    let tool_def = Tool {
104        name: name.clone(),
105        title: Some(name),
106        description: Some(description),
107        input_schema: schema,
108        output_schema: None,
109        annotations: None,
110        meta: None,
111    };
112
113    FunctionToolHandler::new(tool_def, handler)
114}
115
116/// Function-based prompt handler
117pub struct FunctionPromptHandler {
118    prompt: Prompt,
119    handler: Box<
120        dyn Fn(
121                GetPromptRequest,
122                RequestContext,
123            ) -> futures::future::BoxFuture<'static, ServerResult<GetPromptResult>>
124            + Send
125            + Sync,
126    >,
127}
128
129impl std::fmt::Debug for FunctionPromptHandler {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        f.debug_struct("FunctionPromptHandler")
132            .field("prompt", &self.prompt)
133            .finish()
134    }
135}
136
137impl FunctionPromptHandler {
138    /// Create new prompt handler
139    pub fn new<F, Fut>(prompt: Prompt, handler: F) -> Self
140    where
141        F: Fn(GetPromptRequest, RequestContext) -> Fut + Send + Sync + 'static,
142        Fut: std::future::Future<Output = ServerResult<GetPromptResult>> + Send + 'static,
143    {
144        Self {
145            prompt,
146            handler: Box::new(move |req, ctx| Box::pin(handler(req, ctx))),
147        }
148    }
149}
150
151#[async_trait::async_trait]
152impl PromptHandler for FunctionPromptHandler {
153    async fn handle(
154        &self,
155        request: GetPromptRequest,
156        ctx: RequestContext,
157    ) -> ServerResult<GetPromptResult> {
158        (self.handler)(request, ctx).await
159    }
160
161    fn prompt_definition(&self) -> Prompt {
162        self.prompt.clone()
163    }
164}
165
166/// Create a prompt handler from a closure
167///
168/// # Arguments
169///
170/// * `name` - Prompt name
171/// * `description` - Prompt description
172/// * `handler` - Async closure that handles prompt requests
173pub fn prompt<F, Fut>(
174    name: impl Into<String>,
175    description: impl Into<String>,
176    handler: F,
177) -> impl PromptHandler
178where
179    F: Fn(GetPromptRequest, RequestContext) -> Fut + Send + Sync + 'static,
180    Fut: std::future::Future<Output = ServerResult<GetPromptResult>> + Send + 'static,
181{
182    let name = name.into();
183    let description = description.into();
184
185    let prompt_def = Prompt {
186        name: name.clone(),
187        title: Some(name),
188        description: Some(description),
189        arguments: None,
190        meta: None,
191    };
192
193    FunctionPromptHandler::new(prompt_def, handler)
194}
195
196/// Function-based resource handler
197pub struct FunctionResourceHandler {
198    resource: Resource,
199    handler: Box<
200        dyn Fn(
201                ReadResourceRequest,
202                RequestContext,
203            ) -> futures::future::BoxFuture<'static, ServerResult<ReadResourceResult>>
204            + Send
205            + Sync,
206    >,
207}
208
209impl std::fmt::Debug for FunctionResourceHandler {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        f.debug_struct("FunctionResourceHandler")
212            .field("resource", &self.resource)
213            .finish()
214    }
215}
216
217impl FunctionResourceHandler {
218    /// Create new resource handler
219    pub fn new<F, Fut>(resource: Resource, handler: F) -> Self
220    where
221        F: Fn(ReadResourceRequest, RequestContext) -> Fut + Send + Sync + 'static,
222        Fut: std::future::Future<Output = ServerResult<ReadResourceResult>> + Send + 'static,
223    {
224        Self {
225            resource,
226            handler: Box::new(move |req, ctx| Box::pin(handler(req, ctx))),
227        }
228    }
229}
230
231#[async_trait::async_trait]
232impl ResourceHandler for FunctionResourceHandler {
233    async fn handle(
234        &self,
235        request: ReadResourceRequest,
236        ctx: RequestContext,
237    ) -> ServerResult<ReadResourceResult> {
238        (self.handler)(request, ctx).await
239    }
240
241    fn resource_definition(&self) -> Resource {
242        self.resource.clone()
243    }
244
245    async fn exists(&self, _uri: &str) -> bool {
246        true // Default implementation
247    }
248}
249
250/// Create a resource handler from a closure
251///
252/// # Arguments
253///
254/// * `uri` - Resource URI
255/// * `name` - Resource name
256/// * `handler` - Async closure that handles resource read requests
257pub fn resource<F, Fut>(
258    uri: impl Into<String>,
259    name: impl Into<String>,
260    handler: F,
261) -> impl ResourceHandler
262where
263    F: Fn(ReadResourceRequest, RequestContext) -> Fut + Send + Sync + 'static,
264    Fut: std::future::Future<Output = ServerResult<ReadResourceResult>> + Send + 'static,
265{
266    let uri = uri.into();
267    let name = name.into();
268
269    let resource_def = Resource {
270        name: name.clone(),
271        title: Some(name),
272        uri,
273        description: None,
274        mime_type: Some("text/plain".to_string()),
275        annotations: None,
276        size: None,
277        meta: None,
278    };
279
280    FunctionResourceHandler::new(resource_def, handler)
281}