Skip to main content

mcp_kit/server/
builder.rs

1use std::sync::Arc;
2
3use crate::types::{
4    prompt::Prompt,
5    resource::{Resource, ResourceTemplate},
6    tool::Tool,
7    ServerInfo,
8};
9
10use crate::server::{
11    cancellation::CancellationManager,
12    core::McpServer,
13    handler::{
14        CompletionHandler, PromptHandler, PromptHandlerFn, ResourceHandler, ResourceHandlerFn,
15        ToolHandler, ToolHandlerFn,
16    },
17    router::Router,
18};
19
20#[cfg(feature = "auth")]
21use crate::auth::DynAuthProvider;
22
23#[cfg(feature = "plugin")]
24use crate::plugin::PluginManager;
25
26/// Builder for `McpServer` — the main entry point for configuring your server.
27pub struct McpServerBuilder {
28    name: String,
29    version: String,
30    instructions: Option<String>,
31    router: Router,
32    #[cfg(feature = "auth")]
33    auth_provider: Option<DynAuthProvider>,
34    #[cfg(feature = "auth")]
35    require_auth: bool,
36    #[cfg(feature = "plugin")]
37    plugin_manager: Option<PluginManager>,
38}
39
40impl McpServerBuilder {
41    pub fn new() -> Self {
42        Self {
43            name: "mcp-server".to_owned(),
44            version: "0.1.0".to_owned(),
45            instructions: None,
46            router: Router::new(),
47            #[cfg(feature = "auth")]
48            auth_provider: None,
49            #[cfg(feature = "auth")]
50            require_auth: true,
51            #[cfg(feature = "plugin")]
52            plugin_manager: None,
53        }
54    }
55
56    /// Set the server name (shown to clients during handshake)
57    pub fn name(mut self, name: impl Into<String>) -> Self {
58        self.name = name.into();
59        self
60    }
61
62    /// Set the server version
63    pub fn version(mut self, version: impl Into<String>) -> Self {
64        self.version = version.into();
65        self
66    }
67
68    /// Human-readable instructions for how to use this server
69    pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
70        self.instructions = Some(instructions.into());
71        self
72    }
73
74    // ─── Auth configuration ───────────────────────────────────────────────────
75
76    /// Require authentication on all requests using the given provider.
77    ///
78    /// Requests with no or invalid credentials receive HTTP 401 on SSE/HTTP
79    /// transports. Stdio transport is unaffected (it relies on process-level
80    /// access control).
81    ///
82    /// # Example
83    /// ```rust,no_run
84    /// use mcp_kit::prelude::*;
85    /// use mcp_kit::auth::BearerTokenProvider;
86    /// use std::sync::Arc;
87    ///
88    /// McpServer::builder()
89    ///     .name("my-server")
90    ///     .version("1.0")
91    ///     .auth(Arc::new(BearerTokenProvider::new(["secret"])))
92    ///     .build();
93    /// ```
94    #[cfg(feature = "auth")]
95    pub fn auth(mut self, provider: DynAuthProvider) -> Self {
96        self.auth_provider = Some(provider);
97        self.require_auth = true;
98        self
99    }
100
101    /// Accept an auth provider but allow unauthenticated requests through.
102    ///
103    /// Authenticated requests have an identity available via `Auth`; unauthenticated
104    /// requests have no identity and may reach handlers with `None`.
105    #[cfg(feature = "auth")]
106    pub fn optional_auth(mut self, provider: DynAuthProvider) -> Self {
107        self.auth_provider = Some(provider);
108        self.require_auth = false;
109        self
110    }
111
112    // ─── Tool registration ────────────────────────────────────────────────────
113
114    /// Register a tool with an explicit `Tool` descriptor and a handler function.
115    pub fn tool<M>(mut self, tool: Tool, handler: impl ToolHandler<M>) -> Self {
116        self.router.add_tool(tool, handler.into_handler_fn());
117        self
118    }
119
120    /// Register a tool using a pre-built `ToolDef` (from the `#[tool]` macro).
121    pub fn tool_def(mut self, def: ToolDef) -> Self {
122        self.router.add_tool(def.tool, def.handler);
123        self
124    }
125
126    /// Convenience: register a no-parameter tool.
127    pub fn tool_fn<M>(
128        mut self,
129        name: impl Into<String>,
130        description: impl Into<String>,
131        handler: impl ToolHandler<M>,
132    ) -> Self {
133        self.router.add_tool(
134            Tool::no_params(name, description),
135            handler.into_handler_fn(),
136        );
137        self
138    }
139
140    // ─── Resource registration ────────────────────────────────────────────────
141
142    /// Register a static resource (exact URI match).
143    pub fn resource<M>(mut self, resource: Resource, handler: impl ResourceHandler<M>) -> Self {
144        self.router
145            .add_resource(resource, handler.into_handler_fn());
146        self
147    }
148
149    /// Register a URI-template resource (e.g. `"file://{path}"`).
150    pub fn resource_template<M>(
151        mut self,
152        template: ResourceTemplate,
153        handler: impl ResourceHandler<M>,
154    ) -> Self {
155        self.router
156            .add_resource_template(template, handler.into_handler_fn());
157        self
158    }
159
160    /// Register a resource using a pre-built `ResourceDef` (from the `#[resource]` macro).
161    pub fn resource_def(mut self, def: ResourceDef) -> Self {
162        match def {
163            ResourceDef::Static { resource, handler } => {
164                self.router.add_resource(resource, handler);
165            }
166            ResourceDef::Template { template, handler } => {
167                self.router.add_resource_template(template, handler);
168            }
169        }
170        self
171    }
172
173    // ─── Prompt registration ──────────────────────────────────────────────────
174
175    /// Register a prompt template.
176    pub fn prompt<M>(mut self, prompt: Prompt, handler: impl PromptHandler<M>) -> Self {
177        self.router.add_prompt(prompt, handler.into_handler_fn());
178        self
179    }
180
181    /// Register a prompt using a pre-built `PromptDef` (from the `#[prompt]` macro).
182    pub fn prompt_def(mut self, def: PromptDef) -> Self {
183        self.router.add_prompt(def.prompt, def.handler);
184        self
185    }
186
187    // ─── Completion registration ──────────────────────────────────────────────
188
189    /// Register a global completion handler for auto-completing prompt/resource arguments.
190    ///
191    /// This handler is called for any `completion/complete` request that doesn't have
192    /// a more specific handler (prompt-specific or resource-specific).
193    ///
194    /// # Example
195    /// ```rust,no_run
196    /// use mcp_kit::prelude::*;
197    /// use mcp_kit::types::messages::{CompleteRequest, CompleteResult};
198    ///
199    /// McpServer::builder()
200    ///     .name("my-server")
201    ///     .completion(|req: CompleteRequest| async move {
202    ///         // Auto-complete based on argument name
203    ///         let values = match req.argument.name.as_str() {
204    ///             "language" => vec!["rust", "python", "javascript"],
205    ///             _ => vec![],
206    ///         };
207    ///         Ok(CompleteResult::new(values))
208    ///     })
209    ///     .build();
210    /// ```
211    pub fn completion<M>(mut self, handler: impl CompletionHandler<M>) -> Self {
212        self.router
213            .set_completion_handler(handler.into_handler_fn());
214        self
215    }
216
217    /// Register a completion handler for a specific resource URI pattern.
218    ///
219    /// The pattern can be an exact URI or a template like `"file://{path}"`.
220    pub fn resource_completion<M>(
221        mut self,
222        uri_pattern: impl Into<String>,
223        handler: impl CompletionHandler<M>,
224    ) -> Self {
225        self.router
226            .add_resource_completion(uri_pattern.into(), handler.into_handler_fn());
227        self
228    }
229
230    /// Register a prompt with an associated completion handler.
231    ///
232    /// The completion handler provides auto-complete suggestions for the prompt's arguments.
233    pub fn prompt_with_completion<M1, M2>(
234        mut self,
235        prompt: Prompt,
236        handler: impl PromptHandler<M1>,
237        completion: impl CompletionHandler<M2>,
238    ) -> Self {
239        self.router.add_prompt_with_completion(
240            prompt,
241            handler.into_handler_fn(),
242            completion.into_handler_fn(),
243        );
244        self
245    }
246
247    // ─── Plugin registration ──────────────────────────────────────────────────
248
249    /// Attach a plugin manager with pre-loaded plugins.
250    ///
251    /// All tools, resources, and prompts from loaded plugins will be
252    /// automatically registered with the server.
253    ///
254    /// # Example
255    /// ```rust,no_run
256    /// use mcp_kit::prelude::*;
257    /// use mcp_kit::plugin::PluginManager;
258    ///
259    /// let mut plugin_manager = PluginManager::new();
260    /// plugin_manager.load_from_path("./plugins/weather.so")?;
261    ///
262    /// McpServer::builder()
263    ///     .name("my-server")
264    ///     .with_plugin_manager(plugin_manager)
265    ///     .build();
266    /// # Ok::<(), mcp_kit::McpError>(())
267    /// ```
268    #[cfg(feature = "plugin")]
269    pub fn with_plugin_manager(mut self, plugin_manager: PluginManager) -> Self {
270        self.plugin_manager = Some(plugin_manager);
271        self
272    }
273
274    /// Load a plugin from a file path.
275    ///
276    /// This is a convenience method that creates a PluginManager if needed
277    /// and loads the plugin.
278    ///
279    /// # Example
280    /// ```rust,no_run
281    /// use mcp_kit::prelude::*;
282    ///
283    /// McpServer::builder()
284    ///     .name("my-server")
285    ///     .load_plugin("./plugins/weather.so")?
286    ///     .load_plugin("./plugins/database.so")?
287    ///     .build();
288    /// # Ok::<(), mcp_kit::McpError>(())
289    /// ```
290    #[cfg(all(feature = "plugin", feature = "plugin-native"))]
291    pub fn load_plugin(mut self, path: &str) -> crate::McpResult<Self> {
292        let manager = self.plugin_manager.get_or_insert_with(PluginManager::new);
293        manager.load_from_path(path)?;
294        Ok(self)
295    }
296
297    /// Load a plugin with custom configuration.
298    #[cfg(all(feature = "plugin", feature = "plugin-native"))]
299    pub fn load_plugin_with_config(
300        mut self,
301        path: &str,
302        config: crate::plugin::PluginConfig,
303    ) -> crate::McpResult<Self> {
304        let manager = self.plugin_manager.get_or_insert_with(PluginManager::new);
305        manager.load_from_path_with_config(path, config)?;
306        Ok(self)
307    }
308
309    // ─── Build ────────────────────────────────────────────────────────────────
310
311    pub fn build(mut self) -> McpServer {
312        // Register all tools/resources/prompts from plugins
313        #[cfg(feature = "plugin")]
314        if let Some(plugin_manager) = &self.plugin_manager {
315            tracing::debug!("Registering plugin tools, resources, and prompts");
316
317            // Register tools from plugins
318            for tool_def in plugin_manager.collect_tools() {
319                self.router.add_tool(tool_def.tool, tool_def.handler);
320            }
321
322            // Register resources from plugins
323            for resource_def in plugin_manager.collect_resources() {
324                self.router
325                    .add_resource(resource_def.resource, resource_def.handler);
326            }
327
328            // Register prompts from plugins
329            for prompt_def in plugin_manager.collect_prompts() {
330                self.router
331                    .add_prompt(prompt_def.prompt, prompt_def.handler);
332            }
333        }
334
335        McpServer {
336            info: ServerInfo::new(self.name, self.version),
337            instructions: self.instructions,
338            router: Arc::new(self.router),
339            cancellation: CancellationManager::new(),
340            #[cfg(feature = "auth")]
341            auth_provider: self.auth_provider,
342            #[cfg(feature = "auth")]
343            require_auth: self.require_auth,
344        }
345    }
346}
347
348impl Default for McpServerBuilder {
349    fn default() -> Self {
350        Self::new()
351    }
352}
353
354// ─── ToolDef ─────────────────────────────────────────────────────────────────
355
356/// A fully-described tool produced by the `#[tool]` proc macro.
357pub struct ToolDef {
358    pub tool: Tool,
359    pub handler: ToolHandlerFn,
360}
361
362impl ToolDef {
363    pub fn new(tool: Tool, handler: ToolHandlerFn) -> Self {
364        Self { tool, handler }
365    }
366}
367
368// ─── ResourceDef ─────────────────────────────────────────────────────────────
369
370/// A fully-described resource produced by the `#[resource]` proc macro.
371pub enum ResourceDef {
372    Static {
373        resource: Resource,
374        handler: ResourceHandlerFn,
375    },
376    Template {
377        template: ResourceTemplate,
378        handler: ResourceHandlerFn,
379    },
380}
381
382impl ResourceDef {
383    pub fn new_static(resource: Resource, handler: ResourceHandlerFn) -> Self {
384        Self::Static { resource, handler }
385    }
386
387    pub fn new_template(template: ResourceTemplate, handler: ResourceHandlerFn) -> Self {
388        Self::Template { template, handler }
389    }
390}
391
392// ─── PromptDef ───────────────────────────────────────────────────────────────
393
394/// A fully-described prompt produced by the `#[prompt]` proc macro.
395pub struct PromptDef {
396    pub prompt: Prompt,
397    pub handler: PromptHandlerFn,
398}
399
400impl PromptDef {
401    pub fn new(prompt: Prompt, handler: PromptHandlerFn) -> Self {
402        Self { prompt, handler }
403    }
404}