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 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 pub async fn load_openapi_spec(&mut self) -> Result<(), OpenApiError> {
71 let spec = self.spec_location.load_spec().await?;
73 self.register_spec(spec)
74 }
75
76 pub fn register_spec(&mut self, spec: crate::openapi::OpenApiSpec) -> Result<(), OpenApiError> {
82 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 #[must_use]
95 pub fn tool_count(&self) -> usize {
96 self.registry.tool_count()
97 }
98
99 #[must_use]
101 pub fn get_tool_names(&self) -> Vec<String> {
102 self.registry.get_tool_names()
103 }
104
105 #[must_use]
107 pub fn has_tool(&self, name: &str) -> bool {
108 self.registry.has_tool(name)
109 }
110
111 #[must_use]
113 pub fn get_registry_stats(&self) -> crate::tool_registry::ToolRegistryStats {
114 self.registry.get_stats()
115 }
116
117 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 for tool_metadata in self.registry.get_all_tools() {
154 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 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 match self
188 .http_client
189 .execute_tool_call(tool_metadata, &arguments_value)
190 .await
191 {
192 Ok(response) => {
193 Ok(CallToolResult {
195 content: vec![Content::text(response.to_mcp_content())],
196 is_error: Some(!response.is_success),
197 })
198 }
199 Err(e) => {
200 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}