Skip to main content

mcpkit_server/
builder.rs

1//! Fluent server builder for MCP servers.
2//!
3//! The builder uses the typestate pattern to track registered capabilities
4//! at the type level, ensuring compile-time verification of server configuration.
5//!
6//! # Type Parameters
7//!
8//! - `H`: The base server handler
9//! - `Tools`: Tool handler state (`()` = not registered, `TH: ToolHandler` = registered)
10//! - `Resources`: Resource handler state
11//! - `Prompts`: Prompt handler state
12//! - `Tasks`: Task handler state
13//!
14//! # Example
15//!
16//! ```rust
17//! use mcpkit_server::{ServerBuilder, ServerHandler};
18//! use mcpkit_core::capability::{ServerInfo, ServerCapabilities};
19//!
20//! struct MyHandler;
21//!
22//! impl ServerHandler for MyHandler {
23//!     fn server_info(&self) -> ServerInfo {
24//!         ServerInfo::new("my-server", "1.0.0")
25//!     }
26//! }
27//!
28//! let server = ServerBuilder::new(MyHandler).build();
29//! assert_eq!(server.server_info().name, "my-server");
30//! ```
31//!
32//! # Type-Level Capability Tracking
33//!
34//! The builder tracks which handlers have been registered at the type level.
35//! This means you can't accidentally call a method that requires a handler
36//! that hasn't been registered - the compiler will catch it.
37//!
38//! ```rust
39//! use mcpkit_server::{ServerBuilder, ServerHandler, ToolHandler, Context};
40//! use mcpkit_core::capability::{ServerInfo, ServerCapabilities};
41//! use mcpkit_core::types::{Tool, ToolOutput};
42//! use mcpkit_core::error::McpError;
43//! use serde_json::Value;
44//!
45//! struct MyHandler;
46//! impl ServerHandler for MyHandler {
47//!     fn server_info(&self) -> ServerInfo {
48//!         ServerInfo::new("test", "1.0.0")
49//!     }
50//! }
51//!
52//! struct MyToolHandler;
53//! impl ToolHandler for MyToolHandler {
54//!     async fn list_tools(&self, _ctx: &Context<'_>) -> Result<Vec<Tool>, McpError> {
55//!         Ok(vec![])
56//!     }
57//!     async fn call_tool(&self, _name: &str, _args: serde_json::Map<String, Value>, _ctx: &Context<'_>) -> Result<ToolOutput, McpError> {
58//!         Ok(ToolOutput::text("done"))
59//!     }
60//! }
61//!
62//! // Tools are registered - this compiles
63//! let server = ServerBuilder::new(MyHandler)
64//!     .with_tools(MyToolHandler)
65//!     .build();
66//!
67//! assert!(server.capabilities().has_tools());
68//! ```
69
70use crate::handler::{PromptHandler, ResourceHandler, ServerHandler, TaskHandler, ToolHandler};
71use mcpkit_core::capability::ServerCapabilities;
72
73/// Marker type indicating no handler is registered for a capability.
74#[derive(Debug, Clone, Copy, Default)]
75pub struct NotRegistered;
76
77/// Marker type indicating a handler is registered for a capability.
78#[derive(Debug)]
79pub struct Registered<T>(pub T);
80
81/// Builder for constructing MCP servers with specific capabilities.
82///
83/// Uses the typestate pattern with 5 type parameters to track registered
84/// handlers at compile time:
85///
86/// - `H`: Base server handler (always required)
87/// - `Tools`: Tool handler state
88/// - `Resources`: Resource handler state
89/// - `Prompts`: Prompt handler state
90/// - `Tasks`: Task handler state
91///
92/// When a capability is not registered, its type parameter is `NotRegistered`.
93/// When registered, it becomes `Registered<T>` where `T` is the handler type.
94pub struct ServerBuilder<H, Tools, Resources, Prompts, Tasks> {
95    handler: H,
96    tools: Tools,
97    resources: Resources,
98    prompts: Prompts,
99    tasks: Tasks,
100    capabilities: ServerCapabilities,
101}
102
103// Initial builder with no handlers registered
104impl<H: ServerHandler>
105    ServerBuilder<H, NotRegistered, NotRegistered, NotRegistered, NotRegistered>
106{
107    /// Create a new server builder with the given base handler.
108    ///
109    /// The base handler must implement `ServerHandler` and provides
110    /// the core server identity and configuration.
111    #[must_use]
112    pub fn new(handler: H) -> Self {
113        let capabilities = handler.capabilities();
114        Self {
115            handler,
116            tools: NotRegistered,
117            resources: NotRegistered,
118            prompts: NotRegistered,
119            tasks: NotRegistered,
120            capabilities,
121        }
122    }
123}
124
125// Methods available regardless of which handlers are registered
126impl<H, T, R, P, K> ServerBuilder<H, T, R, P, K>
127where
128    H: ServerHandler,
129{
130    /// Override the capabilities advertised by this server.
131    ///
132    /// By default, capabilities are derived from the base handler.
133    /// Use this to customize or extend those capabilities.
134    #[must_use]
135    pub fn capabilities(mut self, caps: ServerCapabilities) -> Self {
136        self.capabilities = caps;
137        self
138    }
139
140    /// Get a reference to the current capabilities.
141    #[must_use]
142    pub const fn get_capabilities(&self) -> &ServerCapabilities {
143        &self.capabilities
144    }
145}
146
147// Tool handler registration (only when tools are not yet registered)
148impl<H, R, P, K> ServerBuilder<H, NotRegistered, R, P, K>
149where
150    H: ServerHandler,
151{
152    /// Register a tool handler.
153    ///
154    /// This method is only available when no tool handler has been registered yet.
155    /// Attempting to register tools twice will result in a compile error.
156    #[must_use]
157    pub fn with_tools<TH: ToolHandler>(
158        self,
159        tools: TH,
160    ) -> ServerBuilder<H, Registered<TH>, R, P, K> {
161        // Task-augmented `tools/call` needs both sides; upgrade the tasks
162        // capability regardless of registration order.
163        let mut capabilities = self.capabilities.with_tools();
164        if capabilities.tasks.is_some() {
165            capabilities = capabilities.with_task_tools();
166        }
167        ServerBuilder {
168            handler: self.handler,
169            tools: Registered(tools),
170            resources: self.resources,
171            prompts: self.prompts,
172            tasks: self.tasks,
173            capabilities,
174        }
175    }
176}
177
178// Opt-in tool I/O schema validation (feature `schema-validation`). Wrapping the
179// registered tool handler covers every dispatch path (normal `tools/call`,
180// task-augmented execution, and the HTTP adapters) because they all go through
181// `ToolHandler::call_tool`.
182#[cfg(feature = "schema-validation")]
183impl<H, TH, R, P, K> ServerBuilder<H, Registered<TH>, R, P, K>
184where
185    H: ServerHandler,
186    TH: ToolHandler,
187{
188    /// Validate both `tools/call` arguments against each tool's `inputSchema`
189    /// and structured results against its `outputSchema`.
190    ///
191    /// Arguments that fail the `inputSchema` yield an `isError: true` result (per
192    /// the Tools spec's error handling); an `outputSchema` violation is logged as
193    /// a server bug, the invalid `structuredContent` is dropped, and the call
194    /// returns `isError: true`. See [`crate::validation`].
195    #[must_use]
196    pub fn validate_tool_io(
197        self,
198    ) -> ServerBuilder<H, Registered<crate::validation::ValidatingToolHandler<TH>>, R, P, K> {
199        self.wrap_tool_validation(crate::validation::ValidationMode::both())
200    }
201
202    /// Validate only `tools/call` arguments against each tool's `inputSchema`.
203    #[must_use]
204    pub fn validate_tool_inputs(
205        self,
206    ) -> ServerBuilder<H, Registered<crate::validation::ValidatingToolHandler<TH>>, R, P, K> {
207        self.wrap_tool_validation(crate::validation::ValidationMode::inputs_only())
208    }
209
210    /// Validate only structured results against each tool's `outputSchema`.
211    #[must_use]
212    pub fn validate_tool_outputs(
213        self,
214    ) -> ServerBuilder<H, Registered<crate::validation::ValidatingToolHandler<TH>>, R, P, K> {
215        self.wrap_tool_validation(crate::validation::ValidationMode::outputs_only())
216    }
217
218    fn wrap_tool_validation(
219        self,
220        mode: crate::validation::ValidationMode,
221    ) -> ServerBuilder<H, Registered<crate::validation::ValidatingToolHandler<TH>>, R, P, K> {
222        ServerBuilder {
223            handler: self.handler,
224            tools: Registered(crate::validation::ValidatingToolHandler::new(
225                self.tools.0,
226                mode,
227            )),
228            resources: self.resources,
229            prompts: self.prompts,
230            tasks: self.tasks,
231            capabilities: self.capabilities,
232        }
233    }
234}
235
236// Resource handler registration (only when resources are not yet registered)
237impl<H, T, P, K> ServerBuilder<H, T, NotRegistered, P, K>
238where
239    H: ServerHandler,
240{
241    /// Register a resource handler.
242    ///
243    /// This method is only available when no resource handler has been registered yet.
244    #[must_use]
245    pub fn with_resources<RH: ResourceHandler>(
246        self,
247        resources: RH,
248    ) -> ServerBuilder<H, T, Registered<RH>, P, K> {
249        ServerBuilder {
250            handler: self.handler,
251            tools: self.tools,
252            resources: Registered(resources),
253            prompts: self.prompts,
254            tasks: self.tasks,
255            capabilities: self.capabilities.with_resources(),
256        }
257    }
258}
259
260// Prompt handler registration (only when prompts are not yet registered)
261impl<H, T, R, K> ServerBuilder<H, T, R, NotRegistered, K>
262where
263    H: ServerHandler,
264{
265    /// Register a prompt handler.
266    ///
267    /// This method is only available when no prompt handler has been registered yet.
268    #[must_use]
269    pub fn with_prompts<PH: PromptHandler>(
270        self,
271        prompts: PH,
272    ) -> ServerBuilder<H, T, R, Registered<PH>, K> {
273        ServerBuilder {
274            handler: self.handler,
275            tools: self.tools,
276            resources: self.resources,
277            prompts: Registered(prompts),
278            tasks: self.tasks,
279            capabilities: self.capabilities.with_prompts(),
280        }
281    }
282}
283
284// Task handler registration (only when tasks are not yet registered)
285impl<H, T, R, P> ServerBuilder<H, T, R, P, NotRegistered>
286where
287    H: ServerHandler,
288{
289    /// Register a task handler.
290    ///
291    /// Tasks are long-running operations that can be tracked, monitored,
292    /// and cancelled.
293    ///
294    /// This method is only available when no task handler has been registered yet.
295    #[must_use]
296    pub fn with_tasks<KH: TaskHandler>(
297        self,
298        tasks: KH,
299    ) -> ServerBuilder<H, T, R, P, Registered<KH>> {
300        // Task-augmented `tools/call` needs both sides; upgrade the tasks
301        // capability regardless of registration order.
302        let mut capabilities = self.capabilities.with_tasks();
303        if capabilities.tools.is_some() {
304            capabilities = capabilities.with_task_tools();
305        }
306        ServerBuilder {
307            handler: self.handler,
308            tools: self.tools,
309            resources: self.resources,
310            prompts: self.prompts,
311            tasks: Registered(tasks),
312            capabilities,
313        }
314    }
315}
316
317// Build method - available for any combination of handlers
318impl<H, T, R, P, K> ServerBuilder<H, T, R, P, K>
319where
320    H: ServerHandler + Send + Sync + 'static,
321    T: Send + Sync + 'static,
322    R: Send + Sync + 'static,
323    P: Send + Sync + 'static,
324    K: Send + Sync + 'static,
325{
326    /// Build the server.
327    ///
328    /// Returns a `Server` configured with the registered handlers and capabilities.
329    #[must_use]
330    pub fn build(self) -> Server<H, T, R, P, K> {
331        Server {
332            handler: self.handler,
333            tools: self.tools,
334            resources: self.resources,
335            prompts: self.prompts,
336            tasks: self.tasks,
337            capabilities: self.capabilities,
338            list_page_size: None,
339            completion: None,
340        }
341    }
342}
343
344/// A configured MCP server ready to serve requests.
345///
346/// The type parameters track which capabilities are available:
347/// - `H`: Base server handler
348/// - `T`: Tool handler (`NotRegistered` or `Registered<TH>`)
349/// - `R`: Resource handler
350/// - `P`: Prompt handler
351/// - `K`: Task handler
352pub struct Server<H, T, R, P, K> {
353    handler: H,
354    pub(crate) tools: T,
355    pub(crate) resources: R,
356    pub(crate) prompts: P,
357    pub(crate) tasks: K,
358    capabilities: ServerCapabilities,
359    /// Page size for `*/list` results; `None` disables pagination (list
360    /// responses return everything, no `nextCursor`).
361    pub(crate) list_page_size: Option<usize>,
362    /// Optional completion handler (`completion/complete`). Not a typestate slot
363    /// — completion is a leaf capability registered post-build so it can also be
364    /// carried by the framework adapters, which take a flat combined handler.
365    pub(crate) completion: Option<std::sync::Arc<dyn crate::dispatch::DynCompletionHandler>>,
366}
367
368impl<H, T, R, P, K> Server<H, T, R, P, K>
369where
370    H: ServerHandler,
371{
372    /// Get the server's capabilities.
373    #[must_use]
374    pub const fn capabilities(&self) -> &ServerCapabilities {
375        &self.capabilities
376    }
377
378    /// Enable pagination of `tools/list`, `resources/list`,
379    /// `resources/templates/list`, and `prompts/list` at the given page size.
380    ///
381    /// By default pagination is disabled (each list returns all items with no
382    /// `nextCursor`). Setting a page size bounds the response payload; clients
383    /// follow the returned `nextCursor` to fetch subsequent pages. A size of `0`
384    /// is treated as disabled.
385    #[must_use]
386    pub const fn list_page_size(mut self, page_size: usize) -> Self {
387        self.list_page_size = Some(page_size);
388        self
389    }
390
391    /// Register a completion handler and advertise the `completions` capability.
392    ///
393    /// This wires `completion/complete` on both the runtime and the framework
394    /// adapters. Completion is a leaf capability, so unlike tools/resources/
395    /// prompts/tasks it is not tracked in the type parameters.
396    #[must_use]
397    pub fn with_completion<C: crate::handler::CompletionHandler + 'static>(
398        mut self,
399        completion: C,
400    ) -> Self {
401        self.completion = Some(std::sync::Arc::new(completion));
402        self.capabilities = self.capabilities.with_completions();
403        self
404    }
405
406    /// Get a reference to the base handler.
407    #[must_use]
408    pub const fn handler(&self) -> &H {
409        &self.handler
410    }
411
412    /// Get the server info from the base handler.
413    #[must_use]
414    pub fn server_info(&self) -> mcpkit_core::capability::ServerInfo {
415        self.handler.server_info()
416    }
417}
418
419// Methods when tools are registered
420impl<H, TH, R, P, K> Server<H, Registered<TH>, R, P, K>
421where
422    H: ServerHandler,
423    TH: ToolHandler,
424{
425    /// Get a reference to the tool handler.
426    #[must_use]
427    pub const fn tool_handler(&self) -> &TH {
428        &self.tools.0
429    }
430}
431
432// Methods when resources are registered
433impl<H, T, RH, P, K> Server<H, T, Registered<RH>, P, K>
434where
435    H: ServerHandler,
436    RH: ResourceHandler,
437{
438    /// Get a reference to the resource handler.
439    #[must_use]
440    pub const fn resource_handler(&self) -> &RH {
441        &self.resources.0
442    }
443}
444
445// Methods when prompts are registered
446impl<H, T, R, PH, K> Server<H, T, R, Registered<PH>, K>
447where
448    H: ServerHandler,
449    PH: PromptHandler,
450{
451    /// Get a reference to the prompt handler.
452    #[must_use]
453    pub const fn prompt_handler(&self) -> &PH {
454        &self.prompts.0
455    }
456}
457
458// Methods when tasks are registered
459impl<H, T, R, P, KH> Server<H, T, R, P, Registered<KH>>
460where
461    H: ServerHandler,
462    KH: TaskHandler,
463{
464    /// Get a reference to the task handler.
465    #[must_use]
466    pub const fn task_handler(&self) -> &KH {
467        &self.tasks.0
468    }
469}
470
471/// Type alias for a fully-configured server with all handlers.
472pub type FullServer<H, TH, RH, PH, KH> =
473    Server<H, Registered<TH>, Registered<RH>, Registered<PH>, Registered<KH>>;
474
475/// Type alias for a minimal server with no optional handlers.
476pub type MinimalServer<H> = Server<H, NotRegistered, NotRegistered, NotRegistered, NotRegistered>;
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481    use crate::context::Context;
482    use crate::handler::ToolHandler;
483    use mcpkit_core::capability::ServerInfo;
484    use mcpkit_core::error::McpError;
485    use mcpkit_core::types::{Tool, ToolOutput};
486    use serde_json::Value;
487
488    struct TestHandler;
489
490    impl ServerHandler for TestHandler {
491        fn server_info(&self) -> ServerInfo {
492            ServerInfo::new("test", "1.0.0")
493        }
494
495        fn capabilities(&self) -> ServerCapabilities {
496            ServerCapabilities::default()
497        }
498    }
499
500    struct TestToolHandler;
501
502    impl ToolHandler for TestToolHandler {
503        async fn list_tools(&self, _ctx: &Context<'_>) -> Result<Vec<Tool>, McpError> {
504            Ok(vec![])
505        }
506
507        async fn call_tool(
508            &self,
509            _name: &str,
510            _args: serde_json::Map<String, Value>,
511            _ctx: &Context<'_>,
512        ) -> Result<ToolOutput, McpError> {
513            Ok(ToolOutput::text("test"))
514        }
515    }
516
517    #[test]
518    fn test_server_builder_minimal() {
519        let server = ServerBuilder::new(TestHandler).build();
520
521        assert_eq!(server.server_info().name, "test");
522        assert_eq!(server.server_info().version, "1.0.0");
523    }
524
525    #[test]
526    fn test_server_builder_with_tools() {
527        let server = ServerBuilder::new(TestHandler)
528            .with_tools(TestToolHandler)
529            .build();
530
531        assert!(server.capabilities().has_tools());
532        // This compiles because tools are registered
533        let _tool_handler: &TestToolHandler = server.tool_handler();
534    }
535
536    struct TestTaskHandler;
537
538    impl crate::handler::TaskHandler for TestTaskHandler {
539        async fn list_tasks(
540            &self,
541            _ctx: &Context<'_>,
542        ) -> Result<mcpkit_core::types::ListTasksResult, McpError> {
543            Ok(vec![].into())
544        }
545        async fn get_task(
546            &self,
547            _id: &mcpkit_core::types::TaskId,
548            _ctx: &Context<'_>,
549        ) -> Result<Option<mcpkit_core::types::GetTaskResult>, McpError> {
550            Ok(None)
551        }
552        async fn cancel_task(
553            &self,
554            _id: &mcpkit_core::types::TaskId,
555            _ctx: &Context<'_>,
556        ) -> Result<Option<mcpkit_core::types::CancelTaskResult>, McpError> {
557            Ok(None)
558        }
559    }
560
561    #[test]
562    fn test_server_builder_with_tasks_advertises_capability() {
563        // Registering a TaskHandler must advertise the `tasks` capability (#81).
564        let server = ServerBuilder::new(TestHandler)
565            .with_tasks(TestTaskHandler)
566            .build();
567
568        assert!(server.capabilities().has_tasks());
569    }
570
571    #[test]
572    fn tasks_capability_shape_is_registration_order_independent() {
573        // Task-augmented `tools/call` (`tasks.requests.tools.call`) must be
574        // advertised when both handlers are registered, in either order.
575        fn tools_call(caps: &mcpkit_core::capability::ServerCapabilities) -> serde_json::Value {
576            serde_json::to_value(caps).unwrap()["tasks"].clone()
577        }
578
579        let tasks_first = ServerBuilder::new(TestHandler)
580            .with_tasks(TestTaskHandler)
581            .with_tools(TestToolHandler)
582            .build();
583        let tools_first = ServerBuilder::new(TestHandler)
584            .with_tools(TestToolHandler)
585            .with_tasks(TestTaskHandler)
586            .build();
587
588        let expected = serde_json::json!({
589            "list": {},
590            "cancel": {},
591            "requests": { "tools": { "call": {} } }
592        });
593        assert_eq!(tools_call(tasks_first.capabilities()), expected);
594        assert_eq!(tools_call(tools_first.capabilities()), expected);
595
596        // A TaskHandler alone must not claim task-augmented tools/call.
597        let tasks_only = ServerBuilder::new(TestHandler)
598            .with_tasks(TestTaskHandler)
599            .build();
600        assert_eq!(
601            tools_call(tasks_only.capabilities()),
602            serde_json::json!({ "list": {}, "cancel": {} })
603        );
604    }
605
606    #[test]
607    fn test_typestate_prevents_double_registration() {
608        // This test verifies the design - double registration would be
609        // a compile error, not a runtime error
610        let _server = ServerBuilder::new(TestHandler)
611            .with_tools(TestToolHandler)
612            // .with_tools(TestToolHandler) // This would NOT compile!
613            .build();
614    }
615
616    #[test]
617    fn test_builder_order_independence() {
618        // Handlers can be registered in any order
619        let server1 = ServerBuilder::new(TestHandler)
620            .with_tools(TestToolHandler)
621            .build();
622
623        // Different order, same result
624        let _server2: Server<
625            TestHandler,
626            Registered<TestToolHandler>,
627            NotRegistered,
628            NotRegistered,
629            NotRegistered,
630        > = ServerBuilder::new(TestHandler)
631            .with_tools(TestToolHandler)
632            .build();
633
634        assert!(server1.capabilities().has_tools());
635    }
636}