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