rmcp_openapi/
openapi_spec.rs

1use crate::error::OpenApiError;
2use crate::server::ToolMetadata;
3use crate::tool_generator::ToolGenerator;
4use openapiv3::OpenAPI;
5use reqwest::Method;
6use serde_json::Value;
7use url::Url;
8
9/// OpenAPI specification wrapper that provides convenience methods
10/// for working with openapiv3::OpenAPI
11#[derive(Debug, Clone)]
12pub struct OpenApiSpec {
13    pub spec: OpenAPI,
14}
15
16impl OpenApiSpec {
17    /// Load and parse an OpenAPI specification from a URL
18    pub async fn from_url(url: &Url) -> Result<Self, OpenApiError> {
19        let client = reqwest::Client::new();
20        let response = client.get(url.clone()).send().await?;
21        let text = response.text().await?;
22        let spec: OpenAPI = serde_json::from_str(&text)?;
23
24        Ok(OpenApiSpec { spec })
25    }
26
27    /// Load and parse an OpenAPI specification from a file
28    pub async fn from_file(path: &str) -> Result<Self, OpenApiError> {
29        let content = tokio::fs::read_to_string(path).await?;
30        let spec: OpenAPI = serde_json::from_str(&content)?;
31
32        Ok(OpenApiSpec { spec })
33    }
34
35    /// Parse an OpenAPI specification from a JSON value
36    pub fn from_value(json_value: Value) -> Result<Self, OpenApiError> {
37        let spec: OpenAPI = serde_json::from_value(json_value)?;
38        Ok(OpenApiSpec { spec })
39    }
40
41    /// Convert all operations to MCP tool metadata
42    pub fn to_tool_metadata(&self) -> Result<Vec<ToolMetadata>, OpenApiError> {
43        let mut tools = Vec::new();
44
45        for (path, path_item_ref) in &self.spec.paths.paths {
46            // Handle ReferenceOr<PathItem>
47            let path_item = match path_item_ref {
48                openapiv3::ReferenceOr::Item(item) => item,
49                openapiv3::ReferenceOr::Reference { .. } => continue, // Skip references for now
50            };
51
52            // Handle operations in the path item
53            let operations = [
54                (Method::GET, &path_item.get),
55                (Method::POST, &path_item.post),
56                (Method::PUT, &path_item.put),
57                (Method::DELETE, &path_item.delete),
58                (Method::PATCH, &path_item.patch),
59                (Method::HEAD, &path_item.head),
60                (Method::OPTIONS, &path_item.options),
61                (Method::TRACE, &path_item.trace),
62            ];
63
64            for (method, operation_ref) in operations {
65                if let Some(operation) = operation_ref {
66                    let tool_metadata = ToolGenerator::generate_tool_metadata(
67                        operation,
68                        method.to_string(),
69                        path.clone(),
70                    )?;
71                    tools.push(tool_metadata);
72                }
73            }
74        }
75
76        Ok(tools)
77    }
78
79    /// Get operation by operation ID
80    pub fn get_operation(
81        &self,
82        operation_id: &str,
83    ) -> Option<(&openapiv3::Operation, String, String)> {
84        for (path, path_item_ref) in &self.spec.paths.paths {
85            // Handle ReferenceOr<PathItem>
86            let path_item = match path_item_ref {
87                openapiv3::ReferenceOr::Item(item) => item,
88                openapiv3::ReferenceOr::Reference { .. } => continue, // Skip references for now
89            };
90
91            let operations = [
92                (Method::GET, &path_item.get),
93                (Method::POST, &path_item.post),
94                (Method::PUT, &path_item.put),
95                (Method::DELETE, &path_item.delete),
96                (Method::PATCH, &path_item.patch),
97                (Method::HEAD, &path_item.head),
98                (Method::OPTIONS, &path_item.options),
99                (Method::TRACE, &path_item.trace),
100            ];
101
102            for (method, operation_ref) in operations {
103                if let Some(operation) = operation_ref {
104                    let default_id = format!(
105                        "{}_{}",
106                        method,
107                        path.replace('/', "_").replace(['{', '}'], "")
108                    );
109                    let op_id = operation.operation_id.as_deref().unwrap_or(&default_id);
110
111                    if op_id == operation_id {
112                        return Some((operation, method.to_string(), path.clone()));
113                    }
114                }
115            }
116        }
117        None
118    }
119
120    /// Get all operation IDs
121    pub fn get_operation_ids(&self) -> Vec<String> {
122        let mut operation_ids = Vec::new();
123
124        for (path, path_item_ref) in &self.spec.paths.paths {
125            // Handle ReferenceOr<PathItem>
126            let path_item = match path_item_ref {
127                openapiv3::ReferenceOr::Item(item) => item,
128                openapiv3::ReferenceOr::Reference { .. } => continue, // Skip references for now
129            };
130
131            let operations = [
132                (Method::GET, &path_item.get),
133                (Method::POST, &path_item.post),
134                (Method::PUT, &path_item.put),
135                (Method::DELETE, &path_item.delete),
136                (Method::PATCH, &path_item.patch),
137                (Method::HEAD, &path_item.head),
138                (Method::OPTIONS, &path_item.options),
139                (Method::TRACE, &path_item.trace),
140            ];
141
142            for (method, operation_ref) in operations {
143                if let Some(operation) = operation_ref {
144                    let default_id = format!(
145                        "{}_{}",
146                        method,
147                        path.replace('/', "_").replace(['{', '}'], "")
148                    );
149                    let op_id = operation.operation_id.as_deref().unwrap_or(&default_id);
150                    operation_ids.push(op_id.to_string());
151                }
152            }
153        }
154
155        operation_ids
156    }
157}