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