Skip to main content

rmcp_server_builder/
lib.rs

1//! Composable MCP server builder for zero-boilerplate capability composition.
2//!
3//! This crate provides a builder pattern for composing MCP servers from individual
4//! capability providers, eliminating the boilerplate of implementing `ServerHandler`
5//! and manually delegating methods.
6//!
7//! # Overview
8//!
9//! Instead of implementing the full `ServerHandler` trait and delegating methods,
10//! you can compose a server from individual providers:
11//!
12//! ```ignore
13//! use rmcp_server_builder::{Server, ServerBuilder};
14//! use rmcp::model::Implementation;
15//!
16//! // Compose a server with tools from one provider and prompts from another
17//! let server = ServerBuilder::new()
18//!     .info(Implementation::from_build_env())
19//!     .instructions("You have access to a glTF 2.0 API...")
20//!     .tools(openapi_server)
21//!     .prompts(my_prompts)
22//!     .build();
23//! ```
24//!
25//! # Provider Traits
26//!
27//! Each MCP capability has a corresponding provider trait:
28//!
29//! - [`ToolsProvider`] - `list_tools`, `call_tool`
30//! - [`PromptsProvider`] - `list_prompts`, `get_prompt`
31//! - [`ResourcesProvider`] - `list_resources`, `list_resource_templates`, `read_resource`, `subscribe`, `unsubscribe`
32//! - [`CompletionProvider`] - `complete`
33//! - [`LoggingProvider`] - `set_level`
34//! - [`ServerInfoProvider`] - `get_info` (required)
35//!
36//! # Blanket Implementations
37//!
38//! Any type implementing `ServerHandler` automatically implements all provider traits,
39//! so existing servers can be used as providers without modification.
40//!
41//! # Capability Auto-Detection
42//!
43//! The composed server automatically sets capability flags based on which providers
44//! are configured. If you set a tools provider, `capabilities.tools` will be enabled.
45
46mod builder;
47mod providers;
48mod server;
49
50pub use builder::{ServerBuilder, SimpleInfo};
51pub use providers::{
52    CompletionProvider, LoggingProvider, PromptsProvider, ResourcesProvider, ServerInfoProvider,
53    ToolsProvider,
54};
55pub use server::{Server, Unset};
56
57// Re-export commonly used rmcp types for convenience
58pub use rmcp::handler::server::ServerHandler;
59pub use rmcp::model::{Implementation, ServerCapabilities};
60
61impl<T, P, R, C, L, I> Server<T, P, R, C, L, I> {
62    /// Create a new server builder.
63    pub fn builder() -> ServerBuilder<Unset, Unset, Unset, Unset, Unset, Unset> {
64        ServerBuilder::new()
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use rmcp::model::{
72        CallToolRequestParams, ErrorData, GetPromptRequestParams, GetPromptResult,
73        ListPromptsResult, ListToolsResult, PaginatedRequestParams,
74    };
75    use rmcp::service::RequestContext;
76
77    // A simple tools-only provider for testing
78    struct TestToolsProvider;
79
80    impl ToolsProvider for TestToolsProvider {
81        async fn list_tools(
82            &self,
83            _request: Option<PaginatedRequestParams>,
84            _context: RequestContext<rmcp::service::RoleServer>,
85        ) -> Result<ListToolsResult, ErrorData> {
86            Ok(ListToolsResult::with_all_items(vec![]))
87        }
88
89        async fn call_tool(
90            &self,
91            _request: CallToolRequestParams,
92            _context: RequestContext<rmcp::service::RoleServer>,
93        ) -> Result<rmcp::model::CallToolResult, ErrorData> {
94            Ok(rmcp::model::CallToolResult::success(vec![
95                rmcp::model::Content::text("Tool executed"),
96            ]))
97        }
98    }
99
100    // A simple prompts-only provider for testing
101    struct TestPromptsProvider {
102        prompt_name: String,
103    }
104
105    impl PromptsProvider for TestPromptsProvider {
106        async fn list_prompts(
107            &self,
108            _request: Option<PaginatedRequestParams>,
109            _context: RequestContext<rmcp::service::RoleServer>,
110        ) -> Result<ListPromptsResult, ErrorData> {
111            Ok(ListPromptsResult::with_all_items(vec![]))
112        }
113
114        async fn get_prompt(
115            &self,
116            request: GetPromptRequestParams,
117            _context: RequestContext<rmcp::service::RoleServer>,
118        ) -> Result<GetPromptResult, ErrorData> {
119            if request.name == self.prompt_name {
120                Ok(GetPromptResult::new(vec![]).with_description("Test prompt"))
121            } else {
122                Err(ErrorData::invalid_params("Unknown prompt", None))
123            }
124        }
125    }
126
127    #[test]
128    fn test_builder_compiles() {
129        let _server = ServerBuilder::new()
130            .info(Implementation::new("test", "1.0.0"))
131            .tools(TestToolsProvider)
132            .prompts(TestPromptsProvider {
133                prompt_name: "test_prompt".into(),
134            })
135            .build();
136    }
137
138    #[test]
139    fn test_server_builder_static_method() {
140        let _server = Server::<Unset, Unset, Unset, Unset, Unset, Unset>::builder()
141            .info(Implementation::new("test", "1.0.0"))
142            .build();
143    }
144}