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
15pub 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 pub fn builder() -> HttpMcpServerBuilder {
32 HttpMcpServerBuilder::new()
33 }
34
35 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
53pub 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 pub fn name(mut self, name: impl Into<String>) -> Self {
83 self.name = name.into();
84 self
85 }
86
87 pub fn version(mut self, version: impl Into<String>) -> Self {
89 self.version = version.into();
90 self
91 }
92
93 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 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 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 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 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 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 pub fn enable_cors(mut self, enable: bool) -> Self {
233 self.enable_cors = enable;
234 self
235 }
236
237 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 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}