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}