rmcp_openapi/
server.rs

1use rmcp::{
2    RoleServer, ServerHandler,
3    model::{
4        CallToolRequestParam, CallToolResult, Content, ErrorData, Implementation, InitializeResult,
5        ListToolsResult, PaginatedRequestParam, ProtocolVersion, ServerCapabilities, Tool,
6        ToolsCapability,
7    },
8    service::RequestContext,
9};
10use serde_json::Value;
11use std::sync::Arc;
12
13use crate::error::OpenApiError;
14use crate::http_client::HttpClient;
15use crate::openapi_spec::OpenApiSpec;
16use crate::tool_registry::ToolRegistry;
17
18pub struct OpenApiServer {
19    pub spec_url: String,
20    pub registry: ToolRegistry,
21    pub http_client: HttpClient,
22    pub base_url: Option<String>,
23}
24
25#[derive(Debug, Clone, serde::Serialize)]
26pub struct ToolMetadata {
27    pub name: String,
28    pub description: String,
29    pub parameters: Value,
30    pub method: String,
31    pub path: String,
32}
33
34impl OpenApiServer {
35    pub fn new(spec_url: String) -> Self {
36        Self {
37            spec_url,
38            registry: ToolRegistry::new(),
39            http_client: HttpClient::new(),
40            base_url: None,
41        }
42    }
43
44    /// Create a new server with a base URL for API calls
45    pub fn with_base_url(spec_url: String, base_url: String) -> Self {
46        let http_client = HttpClient::new().with_base_url(base_url.clone());
47        Self {
48            spec_url,
49            registry: ToolRegistry::new(),
50            http_client,
51            base_url: Some(base_url),
52        }
53    }
54
55    pub async fn load_openapi_spec(&mut self) -> Result<(), OpenApiError> {
56        // Load the OpenAPI specification
57        let spec = if self.spec_url.starts_with("http") {
58            OpenApiSpec::from_url(&self.spec_url).await?
59        } else {
60            OpenApiSpec::from_file(&self.spec_url).await?
61        };
62
63        // Register tools from the spec
64        let registered_count = self.registry.register_from_spec(spec)?;
65
66        println!("Loaded {registered_count} tools from OpenAPI spec");
67        println!("Registry stats: {}", self.registry.get_stats().summary());
68
69        Ok(())
70    }
71
72    /// Get the number of registered tools
73    pub fn tool_count(&self) -> usize {
74        self.registry.tool_count()
75    }
76
77    /// Get all tool names
78    pub fn get_tool_names(&self) -> Vec<String> {
79        self.registry.get_tool_names()
80    }
81
82    /// Check if a specific tool exists
83    pub fn has_tool(&self, name: &str) -> bool {
84        self.registry.has_tool(name)
85    }
86
87    /// Get registry statistics
88    pub fn get_registry_stats(&self) -> crate::tool_registry::ToolRegistryStats {
89        self.registry.get_stats()
90    }
91
92    /// Validate the registry integrity
93    pub fn validate_registry(&self) -> Result<(), OpenApiError> {
94        self.registry.validate_registry()
95    }
96}
97
98impl ServerHandler for OpenApiServer {
99    fn get_info(&self) -> InitializeResult {
100        InitializeResult {
101            protocol_version: ProtocolVersion::V_2024_11_05,
102            server_info: Implementation {
103                name: "OpenAPI MCP Server".to_string(),
104                version: "0.1.0".to_string(),
105            },
106            capabilities: ServerCapabilities {
107                tools: Some(ToolsCapability {
108                    list_changed: Some(false),
109                }),
110                ..Default::default()
111            },
112            instructions: Some("Exposes OpenAPI endpoints as MCP tools".to_string()),
113        }
114    }
115
116    async fn list_tools(
117        &self,
118        _request: Option<PaginatedRequestParam>,
119        _context: RequestContext<RoleServer>,
120    ) -> Result<ListToolsResult, ErrorData> {
121        let mut tools = Vec::new();
122
123        // Convert all registered tools to MCP Tool format
124        for tool_metadata in self.registry.get_all_tools() {
125            // Convert parameters to the expected Arc<Map> format
126            let input_schema = if let Value::Object(obj) = &tool_metadata.parameters {
127                Arc::new(obj.clone())
128            } else {
129                Arc::new(serde_json::Map::new())
130            };
131
132            let tool = Tool {
133                name: tool_metadata.name.clone().into(),
134                description: Some(tool_metadata.description.clone().into()),
135                input_schema,
136                annotations: None,
137            };
138            tools.push(tool);
139        }
140
141        Ok(ListToolsResult {
142            tools,
143            next_cursor: None,
144        })
145    }
146
147    async fn call_tool(
148        &self,
149        request: CallToolRequestParam,
150        _context: RequestContext<RoleServer>,
151    ) -> Result<CallToolResult, ErrorData> {
152        // Check if tool exists in registry
153        if let Some(tool_metadata) = self.registry.get_tool(&request.name) {
154            let arguments = request.arguments.unwrap_or_default();
155            let arguments_value = Value::Object(arguments.clone());
156
157            // Execute the HTTP request
158            match self
159                .http_client
160                .execute_tool_call(tool_metadata, &arguments_value)
161                .await
162            {
163                Ok(response) => {
164                    // Return successful response
165                    Ok(CallToolResult {
166                        content: vec![Content::text(response.to_mcp_content())],
167                        is_error: Some(!response.is_success),
168                    })
169                }
170                Err(e) => {
171                    // Return error response with details
172                    Ok(CallToolResult {
173                        content: vec![Content::text(format!(
174                            "❌ Error executing tool '{}'\n\nError: {}\n\nTool details:\n- Method: {}\n- Path: {}\n- Arguments: {}",
175                            request.name,
176                            e,
177                            tool_metadata.method.to_uppercase(),
178                            tool_metadata.path,
179                            serde_json::to_string_pretty(&arguments_value)
180                                .unwrap_or_else(|_| "Invalid JSON".to_string())
181                        ))],
182                        is_error: Some(true),
183                    })
184                }
185            }
186        } else {
187            Err(OpenApiError::ToolNotFound(request.name.to_string()).into())
188        }
189    }
190}