Skip to main content

model_context_protocol/
server.rs

1//! MCP Server configuration and runtime.
2//!
3//! This module provides a clean API for creating and running MCP servers:
4//!
5//! ```ignore
6//! use mcp::macros::mcp_tool;
7//! use mcp::{McpServerConfig, McpServer};
8//!
9//! #[mcp_tool(description = "Add two numbers")]
10//! fn add(a: f64, b: f64) -> f64 { a + b }
11//!
12//! #[tokio::main]
13//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
14//!     let config = McpServerConfig::builder()
15//!         .name("calculator")
16//!         .version("1.0.0")
17//!         .with_stdio_transport()
18//!         .with_tools(tools![AddTool])
19//!         .build();
20//!
21//!     McpServer::run(config).await?;
22//!     Ok(())
23//! }
24//! ```
25
26use std::io::{self, BufRead, Write};
27use std::sync::Arc;
28
29use serde_json::Value;
30
31use crate::protocol::{
32    JsonRpcId, JsonRpcRequest, JsonRpcResponse, McpCapabilities, McpServerInfo,
33    MCP_PROTOCOL_VERSION,
34};
35use crate::tool::{DynTool, McpTool, ToolCallResult, ToolProvider, ToolRegistry};
36
37/// Server transport configuration.
38#[derive(Debug, Clone)]
39pub enum ServerTransport {
40    /// No transport configured.
41    None,
42    /// Standard I/O transport (stdin/stdout).
43    Stdio,
44    /// HTTP transport with optional SSE support.
45    #[cfg(feature = "http-server")]
46    Http {
47        /// Host to bind to.
48        host: String,
49        /// Port to bind to.
50        port: u16,
51    },
52}
53
54impl Default for ServerTransport {
55    fn default() -> Self {
56        Self::None
57    }
58}
59
60/// Configuration for an MCP server.
61///
62/// Use the builder pattern to create a configuration, then pass it to `McpServer::run()`.
63///
64/// # Example
65///
66/// ```ignore
67/// use mcp::{McpServerConfig, McpServer, tools};
68///
69/// let config = McpServerConfig::builder()
70///     .name("my-server")
71///     .version("1.0.0")
72///     .with_stdio_transport()
73///     .with_tools(tools![MyTool])
74///     .build();
75///
76/// McpServer::run(config).await?;
77/// ```
78pub struct McpServerConfig {
79    pub(crate) name: String,
80    pub(crate) version: String,
81    pub(crate) transport: ServerTransport,
82    pub(crate) registry: ToolRegistry,
83    pub(crate) capabilities: McpCapabilities,
84}
85
86impl McpServerConfig {
87    /// Creates a new configuration builder.
88    pub fn builder() -> McpServerConfigBuilder {
89        McpServerConfigBuilder::new()
90    }
91
92    /// Returns the server name.
93    pub fn name(&self) -> &str {
94        &self.name
95    }
96
97    /// Returns the server version.
98    pub fn version(&self) -> &str {
99        &self.version
100    }
101
102    /// Returns the tool registry for direct tool access.
103    pub fn registry(&self) -> &ToolRegistry {
104        &self.registry
105    }
106}
107
108/// Builder for creating MCP server configurations.
109#[derive(Default)]
110pub struct McpServerConfigBuilder {
111    name: String,
112    version: String,
113    transport: ServerTransport,
114    registry: ToolRegistry,
115    capabilities: McpCapabilities,
116}
117
118impl McpServerConfigBuilder {
119    /// Creates a new configuration builder with defaults.
120    pub fn new() -> Self {
121        Self {
122            name: "mcp-server".to_string(),
123            version: "0.1.0".to_string(),
124            transport: ServerTransport::default(),
125            registry: ToolRegistry::new(),
126            capabilities: McpCapabilities {
127                tools: Some(serde_json::json!({})),
128                ..Default::default()
129            },
130        }
131    }
132
133    /// Sets the server name.
134    pub fn name(mut self, name: impl Into<String>) -> Self {
135        self.name = name.into();
136        self
137    }
138
139    /// Sets the server version.
140    pub fn version(mut self, version: impl Into<String>) -> Self {
141        self.version = version.into();
142        self
143    }
144
145    /// Configures the server to use stdio transport.
146    pub fn with_stdio_transport(mut self) -> Self {
147        self.transport = ServerTransport::Stdio;
148        self
149    }
150
151    /// Configures the server to use HTTP transport.
152    #[cfg(feature = "http-server")]
153    pub fn with_http_transport(mut self, host: impl Into<String>, port: u16) -> Self {
154        self.transport = ServerTransport::Http {
155            host: host.into(),
156            port,
157        };
158        self
159    }
160
161    /// Adds a single tool to the server.
162    pub fn with_tool<T: McpTool + 'static>(mut self, tool: T) -> Self {
163        self.registry.register(Arc::new(tool));
164        self
165    }
166
167    /// Adds a dynamic tool reference to the server.
168    pub fn with_dyn_tool(mut self, tool: DynTool) -> Self {
169        self.registry.register(tool);
170        self
171    }
172
173    /// Adds multiple tools from a vector.
174    ///
175    /// Use the `tools![]` macro to create the vector:
176    ///
177    /// ```ignore
178    /// .with_tools(tools![AddTool, SubtractTool])
179    /// ```
180    pub fn with_tools(mut self, tools: Vec<DynTool>) -> Self {
181        for tool in tools {
182            self.registry.register(tool);
183        }
184        self
185    }
186
187    /// Adds all tools from a provider.
188    pub fn with_tools_from<P: ToolProvider>(mut self, provider: P) -> Self {
189        self.registry.register_provider(provider);
190        self
191    }
192
193    /// Registers all auto-discovered tools.
194    ///
195    /// This collects all tools defined with `#[mcp_tool]` in the crate
196    /// and registers them with the server.
197    ///
198    /// # Example
199    ///
200    /// ```ignore
201    /// use mcp::macros::mcp_tool;
202    /// use mcp::{McpServerConfig, McpServer};
203    ///
204    /// #[mcp_tool(description = "Add numbers", group = "math")]
205    /// fn add(a: f64, b: f64) -> f64 { a + b }
206    ///
207    /// let config = McpServerConfig::builder()
208    ///     .name("my-server")
209    ///     .register_tools()  // Registers all #[mcp_tool] functions
210    ///     .build();
211    /// ```
212    pub fn register_tools(mut self) -> Self {
213        for tool in crate::tool::all_tools() {
214            self.registry.register(tool);
215        }
216        self
217    }
218
219    /// Registers auto-discovered tools filtered by group.
220    ///
221    /// Only registers tools that have the specified group.
222    ///
223    /// # Example
224    ///
225    /// ```ignore
226    /// use mcp::macros::mcp_tool;
227    /// use mcp::{McpServerConfig, McpServer};
228    ///
229    /// #[mcp_tool(description = "Add numbers", group = "math")]
230    /// fn add(a: f64, b: f64) -> f64 { a + b }
231    ///
232    /// #[mcp_tool(description = "Echo text", group = "text")]
233    /// fn echo(msg: String) -> String { msg }
234    ///
235    /// let config = McpServerConfig::builder()
236    ///     .name("math-server")
237    ///     .register_tools_in_group("math")  // Only registers "add"
238    ///     .build();
239    /// ```
240    pub fn register_tools_in_group(mut self, group: &str) -> Self {
241        for tool in crate::tool::tools_in_group(group) {
242            self.registry.register(tool);
243        }
244        self
245    }
246
247    /// Adds capabilities to the server.
248    pub fn with_capabilities(mut self, capabilities: McpCapabilities) -> Self {
249        self.capabilities = capabilities;
250        self
251    }
252
253    /// Enables resource capabilities.
254    pub fn with_resources(mut self) -> Self {
255        self.capabilities.resources = Some(serde_json::json!({}));
256        self
257    }
258
259    /// Enables prompt capabilities.
260    pub fn with_prompts(mut self) -> Self {
261        self.capabilities.prompts = Some(serde_json::json!({}));
262        self
263    }
264
265    /// Builds the server configuration.
266    pub fn build(self) -> McpServerConfig {
267        McpServerConfig {
268            name: self.name,
269            version: self.version,
270            transport: self.transport,
271            registry: self.registry,
272            capabilities: self.capabilities,
273        }
274    }
275}
276
277/// MCP Server runtime.
278///
279/// Use `McpServer::run(config)` to start a server with the given configuration.
280pub struct McpServer {
281    config: McpServerConfig,
282    registry: Arc<ToolRegistry>,
283}
284
285impl McpServer {
286    /// Runs an MCP server with the given configuration.
287    ///
288    /// This is the main entry point for running a server.
289    ///
290    /// # Example
291    ///
292    /// ```ignore
293    /// let config = McpServerConfig::builder()
294    ///     .name("my-server")
295    ///     .version("1.0.0")
296    ///     .with_stdio_transport()
297    ///     .build();
298    ///
299    /// McpServer::run(config).await?;
300    /// ```
301    pub async fn run(config: McpServerConfig) -> Result<(), ServerError> {
302        let server = Self {
303            registry: Arc::new(config.registry),
304            config: McpServerConfig {
305                name: config.name,
306                version: config.version,
307                transport: config.transport,
308                registry: ToolRegistry::new(), // Already moved to Arc
309                capabilities: config.capabilities,
310            },
311        };
312
313        match server.config.transport.clone() {
314            ServerTransport::None => Err(ServerError::Transport(
315                "No transport configured for server".to_string(),
316            )),
317            #[cfg(feature = "stdio")]
318            ServerTransport::Stdio => server.run_stdio().await,
319            #[cfg(feature = "http-server")]
320            ServerTransport::Http { host, port } => server.run_http(&host, port).await,
321        }
322    }
323
324    /// Returns the server name.
325    pub fn name(&self) -> &str {
326        &self.config.name
327    }
328
329    /// Returns the server version.
330    pub fn version(&self) -> &str {
331        &self.config.version
332    }
333
334    /// Returns server info for MCP protocol.
335    pub fn server_info(&self) -> McpServerInfo {
336        McpServerInfo {
337            name: self.config.name.clone(),
338            version: self.config.version.clone(),
339        }
340    }
341
342    /// Runs the stdio transport loop.
343    async fn run_stdio(&self) -> Result<(), ServerError> {
344        let stdin = io::stdin();
345        let stdout = io::stdout();
346        let reader = stdin.lock();
347        let mut writer = stdout.lock();
348
349        for line in reader.lines() {
350            let line = line.map_err(ServerError::Io)?;
351            if line.is_empty() {
352                continue;
353            }
354
355            let response = self.handle_request(&line).await;
356            let response_json =
357                serde_json::to_string(&response).map_err(ServerError::Serialization)?;
358
359            writeln!(writer, "{}", response_json).map_err(ServerError::Io)?;
360            writer.flush().map_err(ServerError::Io)?;
361        }
362
363        Ok(())
364    }
365
366    /// Runs the HTTP transport (actix-web server).
367    #[cfg(feature = "http-server")]
368    async fn run_http(&self, host: &str, port: u16) -> Result<(), ServerError> {
369        use actix_web::{web, App, HttpResponse, HttpServer};
370
371        let registry = self.registry.clone();
372        let name = self.config.name.clone();
373        let version = self.config.version.clone();
374        let capabilities = self.config.capabilities.clone();
375
376        HttpServer::new(move || {
377            let registry_tools = registry.clone();
378            let registry_call = registry.clone();
379            let registry_rpc = registry.clone();
380            let name_rpc = name.clone();
381            let version_rpc = version.clone();
382            let caps_rpc = capabilities.clone();
383
384            App::new()
385                .route(
386                    "/tools",
387                    web::get().to(move || {
388                        let r = registry_tools.clone();
389                        async move {
390                            let tools = r.definitions();
391                            HttpResponse::Ok().json(tools)
392                        }
393                    }),
394                )
395                .route(
396                    "/call",
397                    web::post().to(move |body: web::Json<CallToolRequest>| {
398                        let r = registry_call.clone();
399                        async move {
400                            let result = r.call(&body.name, body.arguments.clone()).await;
401                            match result {
402                                Ok(content) => HttpResponse::Ok().json(content),
403                                Err(e) => {
404                                    HttpResponse::InternalServerError().json(serde_json::json!({
405                                        "error": e.to_string()
406                                    }))
407                                }
408                            }
409                        }
410                    }),
411                )
412                .route(
413                    "/rpc",
414                    web::post().to(move |body: String| {
415                        let r = registry_rpc.clone();
416                        let n = name_rpc.clone();
417                        let v = version_rpc.clone();
418                        let c = caps_rpc.clone();
419                        async move {
420                            let response = handle_rpc_request_static(&body, &r, &n, &v, &c).await;
421                            HttpResponse::Ok().json(response)
422                        }
423                    }),
424                )
425        })
426        .bind((host, port))
427        .map_err(|e| ServerError::Io(io::Error::new(io::ErrorKind::AddrInUse, e)))?
428        .run()
429        .await
430        .map_err(|e| ServerError::Io(io::Error::new(io::ErrorKind::Other, e)))
431    }
432
433    /// Handles a JSON-RPC request.
434    pub async fn handle_request(&self, request_str: &str) -> JsonRpcResponse {
435        let request: JsonRpcRequest = match serde_json::from_str(request_str) {
436            Ok(req) => req,
437            Err(e) => {
438                return JsonRpcResponse::error(
439                    JsonRpcId::Null,
440                    -32700,
441                    format!("Parse error: {}", e),
442                    None,
443                );
444            }
445        };
446
447        self.handle_rpc_request(request).await
448    }
449
450    /// Handles a parsed JSON-RPC request.
451    pub async fn handle_rpc_request(&self, request: JsonRpcRequest) -> JsonRpcResponse {
452        match request.method.as_str() {
453            "initialize" => self.handle_initialize(request.id),
454            "tools/list" => self.handle_tools_list(request.id),
455            "tools/call" => self.handle_tools_call(request.id, request.params).await,
456            "ping" => JsonRpcResponse::success(request.id, serde_json::json!({})),
457            _ => JsonRpcResponse::error(
458                request.id,
459                -32601,
460                format!("Method not found: {}", request.method),
461                None,
462            ),
463        }
464    }
465
466    fn handle_initialize(&self, id: JsonRpcId) -> JsonRpcResponse {
467        JsonRpcResponse::success(
468            id,
469            serde_json::json!({
470                "protocolVersion": MCP_PROTOCOL_VERSION,
471                "serverInfo": self.server_info(),
472                "capabilities": self.config.capabilities
473            }),
474        )
475    }
476
477    fn handle_tools_list(&self, id: JsonRpcId) -> JsonRpcResponse {
478        let tools = self.registry.definitions();
479        JsonRpcResponse::success(id, serde_json::json!({ "tools": tools }))
480    }
481
482    async fn handle_tools_call(&self, id: JsonRpcId, params: Option<Value>) -> JsonRpcResponse {
483        let params = match params {
484            Some(p) => p,
485            None => {
486                return JsonRpcResponse::error(id, -32602, "Missing params".to_string(), None);
487            }
488        };
489
490        let name = match params.get("name").and_then(|n| n.as_str()) {
491            Some(n) => n,
492            None => {
493                return JsonRpcResponse::error(id, -32602, "Missing tool name".to_string(), None);
494            }
495        };
496
497        let arguments = params
498            .get("arguments")
499            .cloned()
500            .unwrap_or(serde_json::json!({}));
501
502        let result = self.registry.call(name, arguments).await;
503
504        match result {
505            Ok(content) => JsonRpcResponse::success(
506                id,
507                serde_json::json!({
508                    "content": content,
509                    "isError": false
510                }),
511            ),
512            Err(e) => JsonRpcResponse::success(
513                id,
514                serde_json::json!({
515                    "content": [{ "type": "text", "text": e.to_string() }],
516                    "isError": true
517                }),
518            ),
519        }
520    }
521
522    /// Calls a tool directly by name.
523    pub async fn call_tool(&self, name: &str, args: Value) -> ToolCallResult {
524        self.registry.call(name, args).await
525    }
526}
527
528/// Helper for HTTP handler - handles RPC without &self
529#[cfg(feature = "http-server")]
530async fn handle_rpc_request_static(
531    request_str: &str,
532    registry: &ToolRegistry,
533    name: &str,
534    version: &str,
535    capabilities: &McpCapabilities,
536) -> JsonRpcResponse {
537    let request: JsonRpcRequest = match serde_json::from_str(request_str) {
538        Ok(req) => req,
539        Err(e) => {
540            return JsonRpcResponse::error(
541                JsonRpcId::Null,
542                -32700,
543                format!("Parse error: {}", e),
544                None,
545            );
546        }
547    };
548
549    match request.method.as_str() {
550        "initialize" => JsonRpcResponse::success(
551            request.id,
552            serde_json::json!({
553                "protocolVersion": MCP_PROTOCOL_VERSION,
554                "serverInfo": {
555                    "name": name,
556                    "version": version
557                },
558                "capabilities": capabilities
559            }),
560        ),
561        "tools/list" => {
562            let tools = registry.definitions();
563            JsonRpcResponse::success(request.id, serde_json::json!({ "tools": tools }))
564        }
565        "tools/call" => {
566            let params = match request.params {
567                Some(p) => p,
568                None => {
569                    return JsonRpcResponse::error(
570                        request.id,
571                        -32602,
572                        "Missing params".to_string(),
573                        None,
574                    );
575                }
576            };
577
578            let tool_name = match params.get("name").and_then(|n| n.as_str()) {
579                Some(n) => n,
580                None => {
581                    return JsonRpcResponse::error(
582                        request.id,
583                        -32602,
584                        "Missing tool name".to_string(),
585                        None,
586                    );
587                }
588            };
589
590            let arguments = params
591                .get("arguments")
592                .cloned()
593                .unwrap_or(serde_json::json!({}));
594            let result = registry.call(tool_name, arguments).await;
595
596            match result {
597                Ok(content) => JsonRpcResponse::success(
598                    request.id,
599                    serde_json::json!({
600                        "content": content,
601                        "isError": false
602                    }),
603                ),
604                Err(e) => JsonRpcResponse::success(
605                    request.id,
606                    serde_json::json!({
607                        "content": [{ "type": "text", "text": e.to_string() }],
608                        "isError": true
609                    }),
610                ),
611            }
612        }
613        "ping" => JsonRpcResponse::success(request.id, serde_json::json!({})),
614        _ => JsonRpcResponse::error(
615            request.id,
616            -32601,
617            format!("Method not found: {}", request.method),
618            None,
619        ),
620    }
621}
622
623/// Request body for the /call HTTP endpoint.
624#[cfg(feature = "http-server")]
625#[derive(serde::Deserialize)]
626struct CallToolRequest {
627    name: String,
628    arguments: Value,
629}
630
631/// Errors that can occur when running an MCP server.
632#[derive(Debug)]
633pub enum ServerError {
634    /// I/O error.
635    Io(io::Error),
636    /// JSON serialization error.
637    Serialization(serde_json::Error),
638    /// Transport error.
639    Transport(String),
640}
641
642impl std::fmt::Display for ServerError {
643    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
644        match self {
645            ServerError::Io(e) => write!(f, "I/O error: {}", e),
646            ServerError::Serialization(e) => write!(f, "Serialization error: {}", e),
647            ServerError::Transport(e) => write!(f, "Transport error: {}", e),
648        }
649    }
650}
651
652impl std::error::Error for ServerError {
653    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
654        match self {
655            ServerError::Io(e) => Some(e),
656            ServerError::Serialization(e) => Some(e),
657            ServerError::Transport(_) => None,
658        }
659    }
660}
661
662/// Creates a vector of tools from tool types.
663///
664/// This macro makes it easy to register multiple tools at once:
665///
666/// ```ignore
667/// use mcp::macros::mcp_tool;
668/// use mcp::{McpServerConfig, McpServer, tools};
669///
670/// #[mcp_tool(description = "Add numbers")]
671/// fn add(a: f64, b: f64) -> f64 { a + b }
672///
673/// #[mcp_tool(description = "Subtract numbers")]
674/// fn subtract(a: f64, b: f64) -> f64 { a - b }
675///
676/// let config = McpServerConfig::builder()
677///     .name("calc")
678///     .with_tools(tools![AddTool, SubtractTool])
679///     .build();
680/// ```
681#[macro_export]
682macro_rules! tools {
683    () => {
684        Vec::new()
685    };
686    ($($tool:expr),+ $(,)?) => {
687        vec![
688            $(std::sync::Arc::new($tool) as $crate::DynTool),+
689        ]
690    };
691}
692
693#[cfg(test)]
694mod tests {
695    use super::*;
696    use crate::protocol::{McpToolDef, ToolContent};
697    use crate::tool::BoxFuture;
698
699    struct EchoTool;
700
701    impl McpTool for EchoTool {
702        fn definition(&self) -> McpToolDef {
703            McpToolDef {
704                name: "echo".to_string(),
705                description: Some("Echo the input".to_string()),
706                group: None,
707                input_schema: serde_json::json!({
708                    "type": "object",
709                    "properties": {
710                        "message": { "type": "string" }
711                    }
712                }),
713            }
714        }
715
716        fn call<'a>(&'a self, args: Value) -> BoxFuture<'a, ToolCallResult> {
717            Box::pin(async move {
718                let message = args
719                    .get("message")
720                    .and_then(|m| m.as_str())
721                    .unwrap_or("no message");
722                Ok(vec![ToolContent::text(message)])
723            })
724        }
725    }
726
727    #[test]
728    fn test_config_builder() {
729        let config = McpServerConfig::builder()
730            .name("test-server")
731            .version("1.0.0")
732            .with_stdio_transport()
733            .with_tool(EchoTool)
734            .build();
735
736        assert_eq!(config.name(), "test-server");
737        assert_eq!(config.version(), "1.0.0");
738        assert_eq!(config.registry.len(), 1);
739    }
740
741    #[test]
742    fn test_tools_macro() {
743        let tools = tools![EchoTool];
744        assert_eq!(tools.len(), 1);
745    }
746
747    #[test]
748    fn test_config_with_tools() {
749        let config = McpServerConfig::builder()
750            .name("test-server")
751            .version("1.0.0")
752            .with_tools(tools![EchoTool])
753            .build();
754
755        assert_eq!(config.registry.len(), 1);
756    }
757}