httpmcp_rust/
server.rs

1use crate::auth::OAuthConfig;
2use crate::handler_types::{
3    RegisteredEndpoint, RegisteredMultipartEndpoint, RegisteredPrompt, RegisteredResource,
4    RegisteredTool,
5};
6use crate::jsonrpc::JsonRpcResponse;
7use crate::metadata::{EndpointMeta, PromptMeta, ResourceMeta, ToolMeta};
8use crate::protocol::{Implementation, ServerCapabilities};
9use crate::transport::create_app;
10use actix_web::{middleware::Logger, App, HttpServer};
11use std::collections::HashMap;
12use std::sync::Arc;
13use tokio::sync::broadcast;
14
15/// Main HTTP MCP Server
16pub struct HttpMcpServer {
17    pub(crate) server_info: Implementation,
18    pub(crate) capabilities: ServerCapabilities,
19    pub(crate) tools: HashMap<String, RegisteredTool>,
20    pub(crate) resources: HashMap<String, RegisteredResource>,
21    pub(crate) prompts: HashMap<String, RegisteredPrompt>,
22    pub(crate) endpoints: Vec<RegisteredEndpoint>,
23    pub(crate) multipart_endpoints: Vec<RegisteredMultipartEndpoint>,
24    pub(crate) oauth_config: Option<OAuthConfig>,
25    pub(crate) enable_cors: bool,
26    pub(crate) response_tx: broadcast::Sender<JsonRpcResponse>,
27}
28
29impl HttpMcpServer {
30    /// Create a new server builder
31    pub fn builder() -> HttpMcpServerBuilder {
32        HttpMcpServerBuilder::new()
33    }
34
35    /// Run the server on the specified address
36    pub async fn run(self, addr: impl Into<String>) -> std::io::Result<()> {
37        let addr = addr.into();
38        let server = Arc::new(self);
39
40        tracing::info!("Starting MCP server on {}", addr);
41
42        HttpServer::new(move || {
43            App::new()
44                .wrap(Logger::default())
45                .configure(|cfg| create_app(cfg, server.clone()))
46        })
47        .bind(&addr)?
48        .run()
49        .await
50    }
51}
52
53/// Builder for HttpMcpServer
54pub struct HttpMcpServerBuilder {
55    name: String,
56    version: String,
57    tools: HashMap<String, RegisteredTool>,
58    resources: HashMap<String, RegisteredResource>,
59    prompts: HashMap<String, RegisteredPrompt>,
60    endpoints: Vec<RegisteredEndpoint>,
61    multipart_endpoints: Vec<RegisteredMultipartEndpoint>,
62    oauth_config: Option<OAuthConfig>,
63    enable_cors: bool,
64}
65
66impl HttpMcpServerBuilder {
67    pub fn new() -> Self {
68        Self {
69            name: "httpmcp-server".to_string(),
70            version: "1.0.0".to_string(),
71            tools: HashMap::new(),
72            resources: HashMap::new(),
73            prompts: HashMap::new(),
74            endpoints: Vec::new(),
75            multipart_endpoints: Vec::new(),
76            oauth_config: None,
77            enable_cors: true,
78        }
79    }
80
81    /// Set server name
82    pub fn name(mut self, name: impl Into<String>) -> Self {
83        self.name = name.into();
84        self
85    }
86
87    /// Set server version
88    pub fn version(mut self, version: impl Into<String>) -> Self {
89        self.version = version.into();
90        self
91    }
92
93    /// Register a tool with handler
94    pub fn tool<F, Fut>(mut self, name: impl Into<String>, meta: ToolMeta, handler: F) -> Self
95    where
96        F: Fn(HashMap<String, serde_json::Value>, crate::context::RequestContext) -> Fut
97            + Send
98            + Sync
99            + 'static,
100        Fut: std::future::Future<Output = crate::error::Result<serde_json::Value>> + Send + 'static,
101    {
102        let name_str = name.into();
103        let tool = RegisteredTool {
104            meta: meta.to_tool(name_str.clone()),
105            handler: Box::new(move |args, ctx| Box::pin(handler(args, ctx))),
106        };
107        self.tools.insert(name_str, tool);
108        self
109    }
110
111    /// Register a resource with list and read handlers
112    pub fn resource<FL, FR, FutL, FutR>(
113        mut self,
114        uri: impl Into<String>,
115        meta: ResourceMeta,
116        list_handler: FL,
117        read_handler: FR,
118    ) -> Self
119    where
120        FL: Fn(Option<String>, crate::context::RequestContext) -> FutL + Send + Sync + 'static,
121        FutL: std::future::Future<
122                Output = crate::error::Result<(Vec<crate::protocol::Resource>, Option<String>)>,
123            > + Send
124            + 'static,
125        FR: Fn(String, crate::context::RequestContext) -> FutR + Send + Sync + 'static,
126        FutR: std::future::Future<
127                Output = crate::error::Result<Vec<crate::protocol::ResourceContents>>,
128            > + Send
129            + 'static,
130    {
131        let uri_str = uri.into();
132        let resource = RegisteredResource {
133            meta: meta.to_resource(uri_str.clone()),
134            list_handler: Box::new(move |cursor, ctx| Box::pin(list_handler(cursor, ctx))),
135            read_handler: Box::new(move |uri, ctx| Box::pin(read_handler(uri, ctx))),
136        };
137        self.resources.insert(uri_str, resource);
138        self
139    }
140
141    /// Register a prompt with handler
142    pub fn prompt<F, Fut>(mut self, name: impl Into<String>, meta: PromptMeta, handler: F) -> Self
143    where
144        F: Fn(String, Option<HashMap<String, String>>, crate::context::RequestContext) -> Fut
145            + Send
146            + Sync
147            + 'static,
148        Fut: std::future::Future<
149                Output = crate::error::Result<(
150                    Option<String>,
151                    Vec<crate::protocol::PromptMessage>,
152                )>,
153            > + Send
154            + 'static,
155    {
156        let name_str = name.into();
157        let prompt = RegisteredPrompt {
158            meta: meta.to_prompt(name_str.clone()),
159            handler: Box::new(move |name, args, ctx| Box::pin(handler(name, args, ctx))),
160        };
161        self.prompts.insert(name_str, prompt);
162        self
163    }
164
165    /// Register a custom HTTP endpoint
166    pub fn endpoint<F, Fut>(mut self, meta: EndpointMeta, handler: F) -> Self
167    where
168        F: Fn(crate::context::RequestContext, Option<serde_json::Value>) -> Fut
169            + Send
170            + Sync
171            + 'static,
172        Fut: std::future::Future<Output = crate::error::Result<actix_web::HttpResponse>>
173            + Send
174            + 'static,
175    {
176        let endpoint = RegisteredEndpoint {
177            route: meta.get_route().to_string(),
178            method: meta.get_method().to_string(),
179            description: meta.get_description().map(|s| s.to_string()),
180            handler: std::sync::Arc::new(move |ctx, body| Box::pin(handler(ctx, body))),
181        };
182        self.endpoints.push(endpoint);
183        self
184    }
185
186    /// Register a custom multipart HTTP endpoint for file uploads
187    pub fn multipart_endpoint<F, Fut>(mut self, meta: EndpointMeta, handler: F) -> Self
188    where
189        F: Fn(crate::context::RequestContext, actix_multipart::Multipart) -> Fut
190            + Send
191            + Sync
192            + 'static,
193        Fut: std::future::Future<Output = crate::error::Result<actix_web::HttpResponse>> + 'static,
194    {
195        let handler = std::sync::Arc::new(handler);
196        let endpoint = RegisteredMultipartEndpoint {
197            route: meta.get_route().to_string(),
198            method: meta.get_method().to_string(),
199            description: meta.get_description().map(|s| s.to_string()),
200            handler: std::sync::Arc::new(move |ctx, multipart| {
201                let handler = handler.clone();
202                Box::pin(async move { handler(ctx, multipart).await })
203                    as std::pin::Pin<
204                        Box<
205                            dyn std::future::Future<
206                                Output = crate::error::Result<actix_web::HttpResponse>,
207                            >,
208                        >,
209                    >
210            }),
211        };
212        self.multipart_endpoints.push(endpoint);
213        self
214    }
215
216    /// Configure OAuth 2.0
217    pub fn with_oauth(
218        mut self,
219        client_id: impl Into<String>,
220        client_secret: impl Into<String>,
221        _token_url: impl Into<String>,
222        _auth_url: impl Into<String>,
223    ) -> Self {
224        self.oauth_config = Some(OAuthConfig {
225            client_id: client_id.into(),
226            client_secret: client_secret.into(),
227        });
228        self
229    }
230
231    /// Enable or disable CORS
232    pub fn enable_cors(mut self, enable: bool) -> Self {
233        self.enable_cors = enable;
234        self
235    }
236
237    /// Build the server
238    pub fn build(self) -> crate::error::Result<HttpMcpServer> {
239        let capabilities = ServerCapabilities {
240            logging: Some(Default::default()),
241            prompts: if self.prompts.is_empty() {
242                None
243            } else {
244                Some(Default::default())
245            },
246            resources: if self.resources.is_empty() {
247                None
248            } else {
249                Some(Default::default())
250            },
251            tools: if self.tools.is_empty() {
252                None
253            } else {
254                Some(Default::default())
255            },
256        };
257
258        // Create broadcast channel for SSE responses
259        let (response_tx, _) = broadcast::channel(100);
260
261        Ok(HttpMcpServer {
262            server_info: Implementation {
263                name: self.name,
264                version: self.version,
265            },
266            capabilities,
267            tools: self.tools,
268            resources: self.resources,
269            prompts: self.prompts,
270            endpoints: self.endpoints,
271            multipart_endpoints: self.multipart_endpoints,
272            oauth_config: self.oauth_config,
273            enable_cors: self.enable_cors,
274            response_tx,
275        })
276    }
277}
278
279impl Default for HttpMcpServerBuilder {
280    fn default() -> Self {
281        Self::new()
282    }
283}