rmcp_openapi/tool/
mod.rs

1pub mod metadata;
2pub mod tool_collection;
3
4pub use metadata::ToolMetadata;
5pub use tool_collection::ToolCollection;
6
7use crate::error::Error;
8use crate::http_client::HttpClient;
9use reqwest::header::HeaderMap;
10use rmcp::model::{CallToolResult, Tool as McpTool};
11use serde_json::Value;
12use url::Url;
13
14/// Self-contained tool with embedded HTTP client
15#[derive(Clone)]
16pub struct Tool {
17    pub metadata: ToolMetadata,
18    http_client: HttpClient,
19}
20
21impl Tool {
22    /// Create tool with HTTP configuration
23    pub fn new(
24        metadata: ToolMetadata,
25        base_url: Option<Url>,
26        default_headers: Option<HeaderMap>,
27    ) -> Result<Self, Error> {
28        let mut http_client = HttpClient::new();
29
30        if let Some(url) = base_url {
31            http_client = http_client.with_base_url(url)?;
32        }
33
34        if let Some(headers) = default_headers {
35            http_client = http_client.with_default_headers(headers);
36        }
37
38        Ok(Self {
39            metadata,
40            http_client,
41        })
42    }
43
44    /// Execute tool and return MCP-compliant result
45    pub async fn call(
46        &self,
47        arguments: &Value,
48    ) -> Result<CallToolResult, crate::error::ToolCallError> {
49        use rmcp::model::Content;
50        use serde_json::json;
51
52        // Execute the HTTP request using the embedded HTTP client
53        match self
54            .http_client
55            .execute_tool_call(&self.metadata, arguments)
56            .await
57        {
58            Ok(response) => {
59                // Check if the tool has an output schema
60                let structured_content = if self.metadata.output_schema.is_some() {
61                    // Try to parse the response body as JSON
62                    match response.json() {
63                        Ok(json_value) => {
64                            // Wrap the response in our standard HTTP response structure
65                            Some(json!({
66                                "status": response.status_code,
67                                "body": json_value
68                            }))
69                        }
70                        Err(_) => None, // If parsing fails, fall back to text content
71                    }
72                } else {
73                    None
74                };
75
76                // For structured content, serialize to JSON for backwards compatibility
77                let content = if let Some(ref structured) = structured_content {
78                    // MCP Specification: https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content
79                    // "For backwards compatibility, a tool that returns structured content SHOULD also
80                    // return the serialized JSON in a TextContent block."
81                    match serde_json::to_string(structured) {
82                        Ok(json_string) => vec![Content::text(json_string)],
83                        Err(e) => {
84                            // Return error if we can't serialize the structured content
85                            let error = crate::error::ToolCallError::Execution(
86                                crate::error::ToolCallExecutionError::ResponseParsingError {
87                                    reason: format!("Failed to serialize structured content: {e}"),
88                                    raw_response: None,
89                                },
90                            );
91                            return Err(error);
92                        }
93                    }
94                } else {
95                    vec![Content::text(response.to_mcp_content())]
96                };
97
98                // Return successful response
99                Ok(CallToolResult {
100                    content,
101                    structured_content,
102                    is_error: Some(!response.is_success),
103                })
104            }
105            Err(e) => {
106                // Return ToolCallError directly
107                Err(e)
108            }
109        }
110    }
111
112    /// Execute tool and return raw HTTP response
113    pub async fn execute(
114        &self,
115        arguments: &Value,
116    ) -> Result<crate::http_client::HttpResponse, crate::error::ToolCallError> {
117        // Execute the HTTP request using the embedded HTTP client
118        // Return the raw HttpResponse without MCP formatting
119        self.http_client
120            .execute_tool_call(&self.metadata, arguments)
121            .await
122    }
123}
124
125/// MCP compliance - Convert Tool to rmcp::model::Tool
126impl From<&Tool> for McpTool {
127    fn from(tool: &Tool) -> Self {
128        (&tool.metadata).into()
129    }
130}