Skip to main content

mcpkit_server/
handler.rs

1//! Composable handler traits for MCP servers.
2//!
3//! This module defines the traits that MCP servers can implement to handle
4//! different protocol capabilities. Unlike monolithic handler approaches,
5//! these traits are composable - implement only what you need.
6//!
7//! # Overview
8//!
9//! - [`ServerHandler`]: Minimal required trait for all servers
10//! - [`ToolHandler`]: Handle tool discovery and execution
11//! - [`ResourceHandler`]: Handle resource discovery and reading
12//! - [`PromptHandler`]: Handle prompt discovery and rendering
13//! - [`TaskHandler`]: Handle long-running task operations
14//!
15//! # Example
16//!
17//! ```rust
18//! use mcpkit_server::{ServerHandler, ServerBuilder};
19//! use mcpkit_core::capability::{ServerInfo, ServerCapabilities};
20//!
21//! struct MyServer;
22//!
23//! impl ServerHandler for MyServer {
24//!     fn server_info(&self) -> ServerInfo {
25//!         ServerInfo::new("my-server", "1.0.0")
26//!     }
27//!
28//!     fn capabilities(&self) -> ServerCapabilities {
29//!         ServerCapabilities::new().with_tools()
30//!     }
31//! }
32//!
33//! let server = ServerBuilder::new(MyServer).build();
34//! assert_eq!(server.server_info().name, "my-server");
35//! ```
36
37use mcpkit_core::capability::{ServerCapabilities, ServerInfo};
38use mcpkit_core::error::McpError;
39use mcpkit_core::types::{
40    CancelTaskResult, CompleteRequest, CompleteResult, GetPromptResult, GetTaskResult,
41    ListTasksResult, Object, Prompt, Resource, ResourceContents, ResourceTemplate, TaskId, Tool,
42    ToolOutput,
43};
44use serde_json::Value;
45use std::future::Future;
46
47use crate::context::Context;
48
49/// Core server handler trait - required for all MCP servers.
50///
51/// This trait defines the minimal requirements for an MCP server.
52/// All servers must implement this trait. Additional capabilities
53/// are added by implementing optional handler traits.
54///
55/// Note: Context uses lifetime references (no `'static` requirement).
56pub trait ServerHandler: Send + Sync {
57    /// Return information about this server.
58    ///
59    /// This is called during the initialization handshake.
60    fn server_info(&self) -> ServerInfo;
61
62    /// Return the capabilities of this server.
63    ///
64    /// The default implementation returns empty capabilities.
65    /// Override this to advertise specific capabilities.
66    fn capabilities(&self) -> ServerCapabilities {
67        ServerCapabilities::default()
68    }
69
70    /// Return optional instructions for using this server.
71    ///
72    /// These instructions are sent to the client during initialization
73    /// and can help the AI assistant understand how to use this server.
74    fn instructions(&self) -> Option<String> {
75        None
76    }
77
78    /// Called after initialization is complete.
79    ///
80    /// This is a good place to set up any state that requires
81    /// the connection to be established.
82    fn on_initialized(&self, _ctx: &Context<'_>) -> impl Future<Output = ()> + Send {
83        async {}
84    }
85
86    /// Called when the client sends `notifications/roots/list_changed`.
87    ///
88    /// Only invoked when the client advertised the `roots` capability. A good
89    /// place to invalidate cached roots and re-request them via
90    /// [`Context::list_roots`]. The default is a no-op.
91    fn on_roots_list_changed(&self, _ctx: &Context<'_>) -> impl Future<Output = ()> + Send {
92        async {}
93    }
94
95    /// Called when the connection is about to be closed.
96    fn on_shutdown(&self) -> impl Future<Output = ()> + Send {
97        async {}
98    }
99
100    /// Handle a `logging/setLevel` request: the client's requested minimum log
101    /// severity. Only reached when the server advertises the `logging`
102    /// capability. The default is a no-op.
103    fn set_log_level(
104        &self,
105        _level: LogLevel,
106        _ctx: &Context<'_>,
107    ) -> impl Future<Output = Result<(), McpError>> + Send {
108        async { Ok(()) }
109    }
110}
111
112/// Handler for tool-related operations.
113///
114/// Implement this trait to expose tools that AI assistants can call.
115pub trait ToolHandler: Send + Sync {
116    /// List all available tools.
117    ///
118    /// This is called when the client requests the tool list.
119    fn list_tools(
120        &self,
121        ctx: &Context<'_>,
122    ) -> impl Future<Output = Result<Vec<Tool>, McpError>> + Send;
123
124    /// Call a tool with the given arguments.
125    ///
126    /// `args` is passed through **unvalidated**: this generic path does not check
127    /// `args` against the tool's `inputSchema`, nor the returned
128    /// `structuredContent` against its `outputSchema`. Enable the
129    /// `schema-validation` feature and wrap the handler with
130    /// [`ServerBuilder::validate_tool_io`](crate::builder::ServerBuilder::validate_tool_io)
131    /// (or [`ValidatingToolHandler`](crate::validation::ValidatingToolHandler) for
132    /// adapter users) to enforce those schemas.
133    ///
134    /// # CPU-bound or blocking work
135    ///
136    /// The stdio runtime drives requests cooperatively on one task, so a
137    /// `call_tool` that does heavy CPU work (or blocks) *before* awaiting stalls
138    /// all other in-flight requests until it yields. Offload the hot section to
139    /// your runtime's blocking/thread mechanism — `tokio::task::spawn_blocking`,
140    /// `std::thread`, `rayon`, etc. — and `.await` its result. The same applies to
141    /// task-augmented tools: background task futures are polled by the same
142    /// cooperative loop.
143    ///
144    /// # Arguments
145    ///
146    /// * `name` - The name of the tool to call
147    /// * `args` - The arguments as a JSON object map
148    /// * `ctx` - The request context
149    fn call_tool(
150        &self,
151        name: &str,
152        args: Object,
153        ctx: &Context<'_>,
154    ) -> impl Future<Output = Result<ToolOutput, McpError>> + Send;
155
156    /// Called when a tool's definition has changed.
157    ///
158    /// Override this to dynamically add/remove/update tools.
159    fn on_tools_changed(&self) -> impl Future<Output = ()> + Send {
160        async {}
161    }
162}
163
164/// Handler for resource-related operations.
165///
166/// Implement this trait to expose resources that AI assistants can read.
167pub trait ResourceHandler: Send + Sync {
168    /// List all available static resources.
169    ///
170    /// This returns resources with fixed URIs. For dynamic resources with
171    /// parameterized URIs (e.g., `file://{path}`), use `list_resource_templates()`.
172    fn list_resources(
173        &self,
174        ctx: &Context<'_>,
175    ) -> impl Future<Output = Result<Vec<Resource>, McpError>> + Send;
176
177    /// List all available resource templates.
178    ///
179    /// Resource templates describe dynamic resources with parameterized URIs.
180    /// For example, a template with URI `file://{path}` allows clients to
181    /// construct URIs like `file:///etc/hosts` to read specific files.
182    ///
183    /// The default implementation returns an empty list.
184    fn list_resource_templates(
185        &self,
186        _ctx: &Context<'_>,
187    ) -> impl Future<Output = Result<Vec<ResourceTemplate>, McpError>> + Send {
188        async { Ok(vec![]) }
189    }
190
191    /// Read a resource by URI.
192    fn read_resource(
193        &self,
194        uri: &str,
195        ctx: &Context<'_>,
196    ) -> impl Future<Output = Result<Vec<ResourceContents>, McpError>> + Send;
197
198    /// Subscribe to resource updates.
199    ///
200    /// Returns true if the subscription was successful.
201    fn subscribe(
202        &self,
203        _uri: &str,
204        _ctx: &Context<'_>,
205    ) -> impl Future<Output = Result<bool, McpError>> + Send {
206        async { Ok(false) }
207    }
208
209    /// Unsubscribe from resource updates.
210    fn unsubscribe(
211        &self,
212        _uri: &str,
213        _ctx: &Context<'_>,
214    ) -> impl Future<Output = Result<bool, McpError>> + Send {
215        async { Ok(false) }
216    }
217}
218
219/// Handler for prompt-related operations.
220///
221/// Implement this trait to expose prompts that AI assistants can use.
222pub trait PromptHandler: Send + Sync {
223    /// List all available prompts.
224    fn list_prompts(
225        &self,
226        ctx: &Context<'_>,
227    ) -> impl Future<Output = Result<Vec<Prompt>, McpError>> + Send;
228
229    /// Get a prompt with the given arguments.
230    fn get_prompt(
231        &self,
232        name: &str,
233        args: Option<serde_json::Map<String, Value>>,
234        ctx: &Context<'_>,
235    ) -> impl Future<Output = Result<GetPromptResult, McpError>> + Send;
236}
237
238/// Handler for task-related operations.
239///
240/// Implement this trait to support long-running operations that can be
241/// tracked, monitored, and cancelled.
242pub trait TaskHandler: Send + Sync {
243    /// List all tasks.
244    ///
245    /// The [`ListTasksResult`] wrapper may carry `nextCursor` and result-level
246    /// `_meta`; `Vec<Task>::into()` produces one with neither. (Request-cursor
247    /// input for real pagination is not yet threaded — see #150.)
248    fn list_tasks(
249        &self,
250        ctx: &Context<'_>,
251    ) -> impl Future<Output = Result<ListTasksResult, McpError>> + Send;
252
253    /// Get the current state of a task.
254    ///
255    /// Return `Ok(None)` for an unknown task. The [`GetTaskResult`] wrapper may
256    /// carry result-level `_meta`; `Task::into()` produces one with no metadata.
257    fn get_task(
258        &self,
259        id: &TaskId,
260        ctx: &Context<'_>,
261    ) -> impl Future<Output = Result<Option<GetTaskResult>, McpError>> + Send;
262
263    /// Cancel a running task, returning its post-cancellation state.
264    ///
265    /// - `Ok(Some(result))` — cancellation accepted; `result` is the task's state
266    ///   afterwards (and may carry result-level `_meta`).
267    /// - `Ok(None)` — no such task.
268    /// - `Err(..)` — the task exists but cancellation failed (an internal error).
269    fn cancel_task(
270        &self,
271        id: &TaskId,
272        ctx: &Context<'_>,
273    ) -> impl Future<Output = Result<Option<CancelTaskResult>, McpError>> + Send;
274}
275
276/// Handler for completion suggestions (`completion/complete`).
277///
278/// Implement this trait to provide autocomplete suggestions for prompt
279/// arguments and resource-template variables. The full [`CompleteRequest`] is
280/// passed so a handler can dispatch on the [`ref`](CompleteRequest::ref_),
281/// read the [`argument`](CompleteRequest::argument) being typed, and use any
282/// previously-resolved [`context`](CompleteRequest::context). Return a
283/// [`CompleteResult`] — build one from a
284/// [`Completion`](mcpkit_core::types::Completion) via `.into()` for the common
285/// case, or attach result-level `_meta` with [`CompleteResult::with_meta`]. The
286/// route layer caps `values` at
287/// [`MAX_COMPLETION_VALUES`](mcpkit_core::types::MAX_COMPLETION_VALUES).
288pub trait CompletionHandler: Send + Sync {
289    /// Produce completion suggestions for the request.
290    fn complete(
291        &self,
292        request: &CompleteRequest,
293        ctx: &Context<'_>,
294    ) -> impl Future<Output = Result<CompleteResult, McpError>> + Send;
295}
296
297/// The MCP logging severity, re-exported from core.
298///
299/// Inbound `logging/setLevel` is handled by [`ServerHandler::set_log_level`];
300/// outbound `notifications/message` is emitted via the server's `log` helpers.
301pub use mcpkit_core::types::LoggingLevel as LogLevel;
302
303// =============================================================================
304// Blanket implementations for Arc<T>
305//
306// These allow sharing a single handler instance across multiple registrations
307// without requiring Clone on the user's type. The macro-generated `into_server()`
308// method uses Arc internally to wire everything up automatically.
309// =============================================================================
310
311use std::sync::Arc;
312
313impl<T: ServerHandler> ServerHandler for Arc<T> {
314    fn server_info(&self) -> ServerInfo {
315        (**self).server_info()
316    }
317
318    fn capabilities(&self) -> ServerCapabilities {
319        (**self).capabilities()
320    }
321
322    fn instructions(&self) -> Option<String> {
323        (**self).instructions()
324    }
325
326    fn on_initialized(&self, ctx: &Context<'_>) -> impl Future<Output = ()> + Send {
327        (**self).on_initialized(ctx)
328    }
329
330    fn on_roots_list_changed(&self, ctx: &Context<'_>) -> impl Future<Output = ()> + Send {
331        (**self).on_roots_list_changed(ctx)
332    }
333
334    fn on_shutdown(&self) -> impl Future<Output = ()> + Send {
335        (**self).on_shutdown()
336    }
337}
338
339impl<T: ToolHandler> ToolHandler for Arc<T> {
340    fn list_tools(
341        &self,
342        ctx: &Context<'_>,
343    ) -> impl Future<Output = Result<Vec<Tool>, McpError>> + Send {
344        (**self).list_tools(ctx)
345    }
346
347    fn call_tool(
348        &self,
349        name: &str,
350        args: Object,
351        ctx: &Context<'_>,
352    ) -> impl Future<Output = Result<ToolOutput, McpError>> + Send {
353        (**self).call_tool(name, args, ctx)
354    }
355
356    fn on_tools_changed(&self) -> impl Future<Output = ()> + Send {
357        (**self).on_tools_changed()
358    }
359}
360
361impl<T: ResourceHandler> ResourceHandler for Arc<T> {
362    fn list_resources(
363        &self,
364        ctx: &Context<'_>,
365    ) -> impl Future<Output = Result<Vec<Resource>, McpError>> + Send {
366        (**self).list_resources(ctx)
367    }
368
369    fn list_resource_templates(
370        &self,
371        ctx: &Context<'_>,
372    ) -> impl Future<Output = Result<Vec<ResourceTemplate>, McpError>> + Send {
373        (**self).list_resource_templates(ctx)
374    }
375
376    fn read_resource(
377        &self,
378        uri: &str,
379        ctx: &Context<'_>,
380    ) -> impl Future<Output = Result<Vec<ResourceContents>, McpError>> + Send {
381        (**self).read_resource(uri, ctx)
382    }
383
384    fn subscribe(
385        &self,
386        uri: &str,
387        ctx: &Context<'_>,
388    ) -> impl Future<Output = Result<bool, McpError>> + Send {
389        (**self).subscribe(uri, ctx)
390    }
391
392    fn unsubscribe(
393        &self,
394        uri: &str,
395        ctx: &Context<'_>,
396    ) -> impl Future<Output = Result<bool, McpError>> + Send {
397        (**self).unsubscribe(uri, ctx)
398    }
399}
400
401impl<T: PromptHandler> PromptHandler for Arc<T> {
402    fn list_prompts(
403        &self,
404        ctx: &Context<'_>,
405    ) -> impl Future<Output = Result<Vec<Prompt>, McpError>> + Send {
406        (**self).list_prompts(ctx)
407    }
408
409    fn get_prompt(
410        &self,
411        name: &str,
412        args: Option<serde_json::Map<String, Value>>,
413        ctx: &Context<'_>,
414    ) -> impl Future<Output = Result<GetPromptResult, McpError>> + Send {
415        (**self).get_prompt(name, args, ctx)
416    }
417}
418
419impl<T: TaskHandler> TaskHandler for Arc<T> {
420    fn list_tasks(
421        &self,
422        ctx: &Context<'_>,
423    ) -> impl Future<Output = Result<ListTasksResult, McpError>> + Send {
424        (**self).list_tasks(ctx)
425    }
426
427    fn get_task(
428        &self,
429        id: &TaskId,
430        ctx: &Context<'_>,
431    ) -> impl Future<Output = Result<Option<GetTaskResult>, McpError>> + Send {
432        (**self).get_task(id, ctx)
433    }
434
435    fn cancel_task(
436        &self,
437        id: &TaskId,
438        ctx: &Context<'_>,
439    ) -> impl Future<Output = Result<Option<CancelTaskResult>, McpError>> + Send {
440        (**self).cancel_task(id, ctx)
441    }
442}
443
444impl<T: CompletionHandler> CompletionHandler for Arc<T> {
445    fn complete(
446        &self,
447        request: &CompleteRequest,
448        ctx: &Context<'_>,
449    ) -> impl Future<Output = Result<CompleteResult, McpError>> + Send {
450        (**self).complete(request, ctx)
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457
458    struct TestServer;
459
460    impl ServerHandler for TestServer {
461        fn server_info(&self) -> ServerInfo {
462            ServerInfo::new("test", "1.0.0")
463        }
464    }
465
466    #[test]
467    fn test_server_handler() {
468        let server = TestServer;
469        let info = server.server_info();
470        assert_eq!(info.name, "test");
471        assert_eq!(info.version, "1.0.0");
472    }
473
474    #[test]
475    fn test_log_level_ordering() {
476        assert!(LogLevel::Debug < LogLevel::Error);
477        assert!(LogLevel::Info < LogLevel::Warning);
478        assert!(LogLevel::Emergency > LogLevel::Alert);
479    }
480
481    #[test]
482    fn test_arc_server_handler() {
483        let server = Arc::new(TestServer);
484        let info = server.server_info();
485        assert_eq!(info.name, "test");
486        assert_eq!(info.version, "1.0.0");
487    }
488}