1use crate::config::{InvocationMode, McpConfig};
4use crate::discovery;
5use crate::error::{McpError, Result};
6use crate::invocation::RequestInvoker;
7use crate::types::{McpCapability, McpTool};
8use bytes::Bytes;
9use http_body_util::{BodyExt, Full};
10use hyper::body::Incoming;
11use hyper::server::conn::http1;
12use hyper::Response;
13use hyper_util::rt::TokioIo;
14use rustapi_core::{Request as CoreRequest, RustApi};
15use rustapi_openapi::OpenApiSpec;
16use std::collections::HashMap;
17use std::convert::Infallible;
18use std::future::Future;
19use std::net::SocketAddr;
20use std::sync::Arc;
21use tokio::net::TcpListener;
22use tracing::{error, info};
23
24#[derive(Debug, Clone)]
30pub struct McpServer {
31 config: Arc<McpConfig>,
32 openapi: Option<OpenApiSpec>,
34 http_base: Option<String>,
37 tool_map: HashMap<String, ToolExecutionInfo>,
40
41 invoker: Option<RequestInvoker>,
44}
45
46#[derive(Debug, Clone)]
49struct ToolExecutionInfo {
50 path_template: String,
52 method: String,
54}
55
56impl McpServer {
57 pub fn new(config: McpConfig) -> Self {
59 Self {
60 config: Arc::new(config),
61 openapi: None,
62 http_base: None,
63 tool_map: HashMap::new(),
64 invoker: None,
65 }
66 }
67
68 pub fn from_rustapi(app: &RustApi, config: McpConfig) -> Self {
74 let mut server = Self::new(config);
75
76 let spec = app.openapi_spec().clone();
77 server.openapi = Some(spec.clone());
78 server.rebuild_tool_map(&spec);
79
80 let mode = server.config.invocation_mode;
83 if mode == InvocationMode::InProcess || mode == InvocationMode::Auto {
84 let dispatcher = app.request_dispatcher();
85 server.invoker = Some(RequestInvoker::new(dispatcher, server.config.clone()));
86 }
87
88 server
89 }
90
91 pub fn from_spec(config: McpConfig, spec: &OpenApiSpec) -> Self {
93 let mut server = Self::new(config);
94 server.openapi = Some(spec.clone());
95 server.rebuild_tool_map(spec);
96 server
97 }
98
99 pub fn with_openapi(mut self, spec: OpenApiSpec) -> Self {
103 self.rebuild_tool_map(&spec);
104 self.openapi = Some(spec);
105 self
106 }
107
108 pub fn with_http_base(mut self, base: impl Into<String>) -> Self {
115 self.http_base = Some(base.into());
116 self
117 }
118
119 fn rebuild_tool_map(&mut self, spec: &OpenApiSpec) {
122 self.tool_map.clear();
123
124 let config = &*self.config;
125
126 let insert_tool = |map: &mut HashMap<String, ToolExecutionInfo>,
127 path: &str,
128 method: &str,
129 op: &rustapi_openapi::Operation| {
130 if !config.allowed_tags.is_empty() {
131 let has_match = op.tags.iter().any(|t| config.allowed_tags.contains(t));
132 if !has_match {
133 return;
134 }
135 }
136
137 let name = generate_tool_name(method, path, op);
138 let info = ToolExecutionInfo {
139 path_template: path.to_string(),
140 method: method.to_string(),
141 };
142 map.insert(name, info);
143 };
144
145 for (path, path_item) in &spec.paths {
146 if !path_matches_prefixes(path, &config.allowed_path_prefixes) {
147 continue;
148 }
149
150 if let Some(op) = &path_item.get {
151 insert_tool(&mut self.tool_map, path, "GET", op);
152 }
153 if let Some(op) = &path_item.post {
154 insert_tool(&mut self.tool_map, path, "POST", op);
155 }
156 if let Some(op) = &path_item.put {
157 insert_tool(&mut self.tool_map, path, "PUT", op);
158 }
159 if let Some(op) = &path_item.patch {
160 insert_tool(&mut self.tool_map, path, "PATCH", op);
161 }
162 if let Some(op) = &path_item.delete {
163 insert_tool(&mut self.tool_map, path, "DELETE", op);
164 }
165 }
166 }
167
168 pub fn config(&self) -> &McpConfig {
170 &self.config
171 }
172
173 pub fn capabilities(&self) -> Vec<McpCapability> {
175 let mut caps = vec![];
176
177 if self.config.tools_enabled {
178 caps.push(McpCapability::Tools);
179 }
180
181 caps
182 }
183
184 pub fn initialize(&self) -> InitializeResult {
188 InitializeResult {
189 name: self.config.name.clone(),
190 version: self.config.version.clone(),
191 description: self.config.description.clone(),
192 capabilities: self.capabilities(),
193 }
194 }
195
196 pub async fn list_tools(&self) -> Result<Vec<McpTool>> {
201 if !self.config.tools_enabled {
202 return Err(crate::error::McpError::CapabilityNotEnabled(
203 "tools".to_string(),
204 ));
205 }
206
207 if let Some(spec) = &self.openapi {
208 let tools = discovery::extract_tools_from_spec(spec, &self.config);
209 Ok(tools)
210 } else {
211 Ok(vec![])
213 }
214 }
215
216 pub async fn call_tool(
222 &self,
223 req: crate::types::ToolCallRequest,
224 ) -> Result<crate::types::ToolCallResponse> {
225 let info = self.tool_map.get(&req.name).ok_or_else(|| {
226 McpError::ToolNotFound(format!("no tool registered with name '{}'", req.name))
227 })?;
228
229 let path = substitute_path_params(&info.path_template, &req.arguments);
230
231 let use_inprocess = if let Some(inv) = &self.invoker {
233 inv.should_use()
234 } else {
235 false
236 };
237
238 if use_inprocess {
239 if let Some(invoker) = &self.invoker {
240 let state = invoker.state_ref();
241 let core_req = self
242 .build_core_request(info, &req.arguments, &path, state)
243 .await?;
244 let core_resp = invoker.invoke(core_req).await;
245 return self.core_response_to_tool_response(core_resp).await;
246 }
247 }
248
249 let base = self.http_base.as_deref().unwrap_or("http://127.0.0.1:8080");
251
252 let url = format!("{}{}", base.trim_end_matches('/'), path);
253
254 let method =
255 reqwest::Method::from_bytes(info.method.as_bytes()).unwrap_or(reqwest::Method::GET);
256
257 let client = reqwest::Client::new();
258
259 let mut request_builder = client.request(method.clone(), &url);
260
261 let is_body_method = matches!(info.method.as_str(), "POST" | "PUT" | "PATCH");
263 if is_body_method && !req.arguments.is_empty() {
264 request_builder = request_builder
265 .header("content-type", "application/json")
266 .json(&req.arguments);
267 } else if !is_body_method && !req.arguments.is_empty() {
268 }
271
272 let resp = request_builder.send().await.map_err(|e| {
273 McpError::ToolExecution(format!("failed to proxy tool call to main API: {}", e))
274 })?;
275
276 let status = resp.status();
277 let is_error = !status.is_success();
278
279 let content = if let Ok(text) = resp.text().await {
280 if text.trim().is_empty() {
281 serde_json::Value::Null
282 } else {
283 serde_json::from_str(&text).unwrap_or(serde_json::Value::String(text))
284 }
285 } else {
286 serde_json::Value::Null
287 };
288
289 Ok(crate::types::ToolCallResponse {
290 content,
291 is_error,
292 meta: Some(serde_json::json!({ "proxied_status": status.as_u16() })),
293 })
294 }
295
296 async fn build_core_request(
298 &self,
299 info: &ToolExecutionInfo,
300 arguments: &std::collections::HashMap<String, serde_json::Value>,
301 substituted_path: &str,
302 state: Arc<http::Extensions>,
303 ) -> Result<CoreRequest> {
304 use bytes::Bytes;
305 use http::{Method, Request as HttpRequest, Uri, Version};
306 use rustapi_core::{BodyVariant, PathParams};
307
308 let method = Method::from_bytes(info.method.as_bytes()).unwrap_or(Method::GET);
309 let uri: Uri = substituted_path
310 .parse()
311 .unwrap_or_else(|_| "/".parse().expect("valid fallback uri"));
312
313 let mut builder = HttpRequest::builder()
314 .method(method.clone())
315 .uri(uri)
316 .version(Version::HTTP_11);
317
318 let is_body_method = matches!(info.method.as_str(), "POST" | "PUT" | "PATCH");
319
320 let body_bytes = if is_body_method && !arguments.is_empty() {
321 builder = builder.header("content-type", "application/json");
322 let json = serde_json::to_vec(arguments).unwrap_or_default();
323 Bytes::from(json)
324 } else {
325 Bytes::new()
326 };
327
328 let http_req = builder
329 .body(())
330 .map_err(|e| McpError::ToolExecution(format!("failed to build request: {}", e)))?;
331
332 let (mut parts, _) = http_req.into_parts();
333
334 parts.method = method;
335 parts.uri = substituted_path.parse().unwrap_or(parts.uri);
336
337 let path_params = PathParams::new();
338
339 let core_req =
340 CoreRequest::new(parts, BodyVariant::Buffered(body_bytes), state, path_params);
341
342 Ok(core_req)
343 }
344
345 async fn core_response_to_tool_response(
347 &self,
348 resp: rustapi_core::Response,
349 ) -> Result<crate::types::ToolCallResponse> {
350 use http_body_util::BodyExt;
351
352 let status = resp.status();
353 let is_error = !status.is_success();
354
355 let body_bytes = resp
357 .into_body()
358 .collect()
359 .await
360 .map(|collected| collected.to_bytes())
361 .unwrap_or_default();
362
363 let content = if body_bytes.is_empty() {
364 serde_json::Value::Null
365 } else if let Ok(text) = std::str::from_utf8(&body_bytes) {
366 if text.trim().is_empty() {
367 serde_json::Value::Null
368 } else {
369 serde_json::from_str(text).unwrap_or(serde_json::Value::String(text.to_string()))
370 }
371 } else {
372 serde_json::Value::String(String::from_utf8_lossy(&body_bytes).to_string())
373 };
374
375 Ok(crate::types::ToolCallResponse {
376 content,
377 is_error,
378 meta: Some(serde_json::json!({ "status": status.as_u16(), "in_process": true })),
379 })
380 }
381}
382
383#[derive(Debug, Clone)]
385pub struct InitializeResult {
386 pub name: String,
388 pub version: String,
390 pub description: Option<String>,
392 pub capabilities: Vec<McpCapability>,
394}
395
396impl McpServer {
397 pub async fn serve(self, addr: &str) -> Result<()> {
407 self.serve_with_shutdown(addr, std::future::pending()).await
408 }
409
410 pub async fn serve_with_shutdown<F>(self, addr: &str, signal: F) -> Result<()>
412 where
413 F: Future<Output = ()> + Send + 'static,
414 {
415 let addr: SocketAddr = addr
416 .parse()
417 .map_err(|e| McpError::Transport(format!("invalid MCP address '{}': {}", addr, e)))?;
418
419 let listener = TcpListener::bind(addr).await.map_err(|e| {
420 McpError::Transport(format!("failed to bind MCP listener on {}: {}", addr, e))
421 })?;
422
423 info!("🧠 MCP server listening on http://{}", addr);
424
425 let this = Arc::new(self);
426 tokio::pin!(signal);
427
428 loop {
429 tokio::select! {
430 biased;
431
432 accept_result = listener.accept() => {
433 let (stream, remote) = match accept_result {
434 Ok(v) => v,
435 Err(e) => {
436 error!("MCP accept error: {}", e);
437 continue;
438 }
439 };
440
441 let io = TokioIo::new(stream);
442 let mcp = this.clone();
443
444 tokio::task::spawn(async move {
445 let service = hyper::service::service_fn(move |req| {
446 let mcp = mcp.clone();
447 async move { handle_mcp_http_request(mcp, req).await }
448 });
449
450 if let Err(e) = http1::Builder::new()
451 .serve_connection(io, service)
452 .await
453 {
454 error!("MCP connection error from {}: {}", remote, e);
455 }
456 });
457 }
458
459 _ = &mut signal => {
460 info!("MCP server received shutdown signal");
461 break;
462 }
463 }
464 }
465
466 Ok(())
467 }
468}
469
470async fn handle_mcp_http_request(
472 mcp: Arc<McpServer>,
473 req: hyper::Request<Incoming>,
474) -> std::result::Result<Response<Full<Bytes>>, Infallible> {
475 if req.method() != hyper::Method::POST {
476 let body = Bytes::from_static(b"{\"error\":\"MCP transport expects POST requests\"}");
477 return Ok(Response::builder()
478 .status(405)
479 .header("content-type", "application/json")
480 .body(Full::new(body))
481 .expect("static response must build"));
482 }
483
484 let body_bytes = match req.collect().await {
485 Ok(collected) => collected.to_bytes(),
486 Err(e) => {
487 let err_body = format!("{{\"error\":\"failed to read body: {}\"}}", e);
488 return Ok(Response::builder()
489 .status(400)
490 .header("content-type", "application/json")
491 .body(Full::new(Bytes::from(err_body)))
492 .expect("error response must build"));
493 }
494 };
495
496 let json: serde_json::Value = match serde_json::from_slice(&body_bytes) {
497 Ok(v) => v,
498 Err(_) => {
499 return Ok(jsonrpc_error_response(
500 serde_json::Value::Null,
501 -32700,
502 "parse error",
503 ));
504 }
505 };
506
507 let id = json.get("id").cloned().unwrap_or(serde_json::Value::Null);
508 let method = json.get("method").and_then(|m| m.as_str()).unwrap_or("");
509
510 match method {
511 "initialize" => {
512 let init = mcp.initialize();
513 let result = serde_json::json!({
514 "protocolVersion": "2024-11-05",
515 "serverInfo": {
516 "name": init.name,
517 "version": init.version
518 },
519 "capabilities": {
520 "tools": {}
521 }
522 });
523 Ok(jsonrpc_success_response(id, result))
524 }
525 "tools/list" => match mcp.list_tools().await {
526 Ok(tools) => {
527 let tool_defs: Vec<_> = tools
528 .into_iter()
529 .map(|t| {
530 let mut def = serde_json::json!({
531 "name": t.name,
532 "description": t.description,
533 "inputSchema": t.input_schema
534 });
535
536 if let Some(perm) = &t.permission {
538 def["permission"] = serde_json::Value::String(perm.clone());
539 }
540 if let Some(confirm) = t.requires_confirmation {
541 if confirm {
542 def["requiresConfirmation"] = serde_json::Value::Bool(true);
543 }
544 }
545 def
548 })
549 .collect();
550
551 let result = serde_json::json!({ "tools": tool_defs });
552 Ok(jsonrpc_success_response(id, result))
553 }
554 Err(e) => Ok(jsonrpc_error_response(
555 id,
556 -32603,
557 &format!("internal error: {}", e),
558 )),
559 },
560 "tools/call" => {
561 let params = json.get("params").cloned().unwrap_or(serde_json::json!({}));
562 let name = params
563 .get("name")
564 .and_then(|v| v.as_str())
565 .unwrap_or("")
566 .to_string();
567 let arguments: std::collections::HashMap<String, serde_json::Value> = params
568 .get("arguments")
569 .and_then(|v| serde_json::from_value(v.clone()).ok())
570 .unwrap_or_default();
571
572 let tool_req = crate::types::ToolCallRequest { name, arguments };
573
574 match mcp.call_tool(tool_req).await {
575 Ok(resp) => {
576 let text = if resp.content.is_null() {
580 String::new()
581 } else if let Some(s) = resp.content.as_str() {
582 s.to_owned()
583 } else {
584 serde_json::to_string_pretty(&resp.content)
585 .unwrap_or_else(|_| resp.content.to_string())
586 };
587
588 let result = serde_json::json!({
589 "content": [{
590 "type": "text",
591 "text": text
592 }],
593 "isError": resp.is_error
594 });
595 Ok(jsonrpc_success_response(id, result))
596 }
597 Err(e) => {
598 let result = serde_json::json!({
601 "content": [{
602 "type": "text",
603 "text": format!("Tool execution error: {}", e)
604 }],
605 "isError": true
606 });
607 Ok(jsonrpc_success_response(id, result))
608 }
609 }
610 }
611 _ => Ok(jsonrpc_error_response(id, -32601, "method not found")),
612 }
613}
614
615fn jsonrpc_success_response(
616 id: serde_json::Value,
617 result: serde_json::Value,
618) -> Response<Full<Bytes>> {
619 let body = serde_json::json!({
620 "jsonrpc": "2.0",
621 "id": id,
622 "result": result
623 });
624 Response::builder()
625 .header("content-type", "application/json")
626 .body(Full::new(Bytes::from(
627 serde_json::to_vec(&body).expect("json must serialize"),
628 )))
629 .expect("response must build")
630}
631
632fn jsonrpc_error_response(
633 id: serde_json::Value,
634 code: i32,
635 message: &str,
636) -> Response<Full<Bytes>> {
637 let body = serde_json::json!({
638 "jsonrpc": "2.0",
639 "id": id,
640 "error": {
641 "code": code,
642 "message": message
643 }
644 });
645 Response::builder()
646 .header("content-type", "application/json")
647 .body(Full::new(Bytes::from(
648 serde_json::to_vec(&body).expect("json must serialize"),
649 )))
650 .expect("response must build")
651}
652
653fn path_matches_prefixes(path: &str, prefixes: &[String]) -> bool {
655 if prefixes.is_empty() {
656 return true;
657 }
658 prefixes.iter().any(|p| path.starts_with(p))
659}
660
661fn generate_tool_name(method: &str, path: &str, op: &rustapi_openapi::Operation) -> String {
662 if let Some(oid) = &op.operation_id {
663 return sanitize_name(oid);
664 }
665 let mut slug = path
666 .trim_start_matches('/')
667 .replace(['/', '{', '}', ':'], "_")
668 .replace(['-', '.', ' '], "_");
669 while slug.contains("__") {
670 slug = slug.replace("__", "_");
671 }
672 let slug = slug.trim_matches('_').to_string();
673 let method_lower = method.to_lowercase();
674 if slug.is_empty() {
675 method_lower
676 } else {
677 format!("{}_{}", method_lower, slug)
678 }
679}
680
681fn sanitize_name(s: &str) -> String {
682 s.chars()
683 .map(|c| {
684 if c.is_alphanumeric() || c == '_' {
685 c
686 } else {
687 '_'
688 }
689 })
690 .collect::<String>()
691 .trim_matches('_')
692 .to_string()
693 .to_lowercase()
694}
695
696fn substitute_path_params(
698 template: &str,
699 args: &std::collections::HashMap<String, serde_json::Value>,
700) -> String {
701 let mut result = template.to_string();
702 for (key, value) in args {
703 let placeholder = format!("{{{}}}", key);
704 if result.contains(&placeholder) {
705 let val_str = match value {
706 serde_json::Value::String(s) => s.clone(),
707 other => other.to_string().trim_matches('"').to_string(),
708 };
709 result = result.replace(&placeholder, &val_str);
710 }
711 }
712 result
713}