1pub mod metadata;
2pub mod tool_collection;
3
4pub use metadata::{ParameterMapping, ToolMetadata};
5pub use tool_collection::ToolCollection;
6
7use crate::config::Authorization;
8use crate::error::Error;
9use crate::http_client::HttpClient;
10use crate::security::SecurityObserver;
11use rmcp::model::{CallToolResult, Tool as McpTool};
12use serde_json::Value;
13
14#[derive(Clone)]
16pub struct Tool {
17 pub metadata: ToolMetadata,
18 http_client: HttpClient,
19}
20
21impl Tool {
22 pub fn new(metadata: ToolMetadata, http_client: HttpClient) -> Result<Self, Error> {
24 Ok(Self {
25 metadata,
26 http_client,
27 })
28 }
29
30 pub async fn call(
32 &self,
33 arguments: &Value,
34 authorization: Authorization,
35 ) -> Result<CallToolResult, crate::error::ToolCallError> {
36 use rmcp::model::Content;
37 use serde_json::json;
38
39 let observer = SecurityObserver::new(&authorization);
41
42 let has_auth = match &authorization {
44 Authorization::None => false,
45 #[cfg(feature = "authorization-token-passthrough")]
46 Authorization::PassthroughWarn(header) | Authorization::PassthroughSilent(header) => {
47 header.is_some()
48 }
49 };
50
51 observer.observe_request(&self.metadata.name, has_auth, self.metadata.requires_auth());
52
53 let auth_header: Option<&rmcp_actix_web::transport::AuthorizationHeader> =
55 match &authorization {
56 Authorization::None => None,
57 #[cfg(feature = "authorization-token-passthrough")]
58 Authorization::PassthroughWarn(header)
59 | Authorization::PassthroughSilent(header) => header.as_ref(),
60 };
61
62 let client = if let Some(auth) = auth_header {
64 self.http_client.with_authorization(&auth.0)
65 } else {
66 self.http_client.clone()
67 };
68
69 match client.execute_tool_call(&self.metadata, arguments).await {
71 Ok(response) => {
72 if response.is_image()
74 && let Some(bytes) = &response.body_bytes
75 {
76 use base64::{Engine as _, engine::general_purpose::STANDARD};
78 let base64_data = STANDARD.encode(bytes);
79
80 let mime_type = response.content_type.as_deref().ok_or_else(|| {
82 crate::error::ToolCallError::Execution(
83 crate::error::ToolCallExecutionError::ResponseParsingError {
84 reason: "Image response missing Content-Type header".to_string(),
85 raw_response: None,
86 },
87 )
88 })?;
89
90 return Ok(CallToolResult {
92 content: vec![Content::image(base64_data, mime_type)],
93 structured_content: None,
94 is_error: Some(!response.is_success),
95 meta: None,
96 });
97 }
98
99 let structured_content = if self.metadata.output_schema.is_some() {
101 match response.json() {
103 Ok(json_value) => {
104 Some(json!({
106 "status": response.status_code,
107 "body": json_value
108 }))
109 }
110 Err(_) => None, }
112 } else {
113 None
114 };
115
116 let content = if let Some(ref structured) = structured_content {
118 match serde_json::to_string(structured) {
122 Ok(json_string) => vec![Content::text(json_string)],
123 Err(e) => {
124 let error = crate::error::ToolCallError::Execution(
126 crate::error::ToolCallExecutionError::ResponseParsingError {
127 reason: format!("Failed to serialize structured content: {e}"),
128 raw_response: None,
129 },
130 );
131 return Err(error);
132 }
133 }
134 } else {
135 vec![Content::text(response.to_mcp_content())]
136 };
137
138 Ok(CallToolResult {
140 content,
141 structured_content,
142 is_error: Some(!response.is_success),
143 meta: None,
144 })
145 }
146 Err(e) => {
147 Err(e)
149 }
150 }
151 }
152
153 pub async fn execute(
155 &self,
156 arguments: &Value,
157 authorization: Authorization,
158 ) -> Result<crate::http_client::HttpResponse, crate::error::ToolCallError> {
159 let auth_header: Option<&rmcp_actix_web::transport::AuthorizationHeader> =
161 match &authorization {
162 Authorization::None => None,
163 #[cfg(feature = "authorization-token-passthrough")]
164 Authorization::PassthroughWarn(header)
165 | Authorization::PassthroughSilent(header) => header.as_ref(),
166 };
167
168 let client = if let Some(auth) = auth_header {
170 self.http_client.with_authorization(&auth.0)
171 } else {
172 self.http_client.clone()
173 };
174
175 client.execute_tool_call(&self.metadata, arguments).await
178 }
179}
180
181impl From<&Tool> for McpTool {
183 fn from(tool: &Tool) -> Self {
184 (&tool.metadata).into()
185 }
186}