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 crate::transformer::ResponseTransformer;
12use rmcp::model::{CallToolResult, Tool as McpTool};
13use serde_json::Value;
14use std::sync::Arc;
15
16#[derive(Clone)]
18pub struct Tool {
19 pub metadata: ToolMetadata,
20 http_client: HttpClient,
21 pub(crate) response_transformer: Option<Arc<dyn ResponseTransformer>>,
23}
24
25impl Tool {
26 pub fn new(metadata: ToolMetadata, http_client: HttpClient) -> Result<Self, Error> {
28 Ok(Self {
29 metadata,
30 http_client,
31 response_transformer: None,
32 })
33 }
34
35 pub async fn call(
43 &self,
44 arguments: &Value,
45 authorization: Authorization,
46 server_transformer: Option<&dyn ResponseTransformer>,
47 ) -> Result<CallToolResult, crate::error::ToolCallError> {
48 use rmcp::model::Content;
49 use serde_json::json;
50
51 let observer = SecurityObserver::new(&authorization);
53
54 let has_auth = match &authorization {
56 Authorization::None => false,
57 #[cfg(feature = "authorization-token-passthrough")]
58 Authorization::PassthroughWarn(header) | Authorization::PassthroughSilent(header) => {
59 header.is_some()
60 }
61 };
62
63 observer.observe_request(&self.metadata.name, has_auth, self.metadata.requires_auth());
64
65 let auth_header: Option<&rmcp_actix_web::transport::AuthorizationHeader> =
67 match &authorization {
68 Authorization::None => None,
69 #[cfg(feature = "authorization-token-passthrough")]
70 Authorization::PassthroughWarn(header)
71 | Authorization::PassthroughSilent(header) => header.as_ref(),
72 };
73
74 let client = if let Some(auth) = auth_header {
76 self.http_client.with_authorization(&auth.0)
77 } else {
78 self.http_client.clone()
79 };
80
81 let transformer = self
83 .response_transformer
84 .as_ref()
85 .map(|t| t.as_ref() as &dyn ResponseTransformer)
86 .or(server_transformer);
87
88 match client.execute_tool_call(&self.metadata, arguments).await {
90 Ok(response) => {
91 if response.is_image()
93 && let Some(bytes) = &response.body_bytes
94 {
95 use base64::{Engine as _, engine::general_purpose::STANDARD};
97 let base64_data = STANDARD.encode(bytes);
98
99 let mime_type = response.content_type.as_deref().ok_or_else(|| {
101 crate::error::ToolCallError::Execution(
102 crate::error::ToolCallExecutionError::ResponseParsingError {
103 reason: "Image response missing Content-Type header".to_string(),
104 raw_response: None,
105 },
106 )
107 })?;
108
109 return Ok(CallToolResult {
111 content: vec![Content::image(base64_data, mime_type)],
112 structured_content: None,
113 is_error: Some(!response.is_success),
114 meta: None,
115 });
116 }
117
118 let structured_content = if self.metadata.output_schema.is_some() {
120 match response.json() {
122 Ok(json_value) => {
123 let transformed_body = if let Some(t) = transformer {
125 t.transform_response(json_value)
126 } else {
127 json_value
128 };
129
130 Some(json!({
132 "status": response.status_code,
133 "body": transformed_body
134 }))
135 }
136 Err(_) => None, }
138 } else {
139 None
140 };
141
142 let content = if let Some(ref structured) = structured_content {
144 match serde_json::to_string(structured) {
148 Ok(json_string) => vec![Content::text(json_string)],
149 Err(e) => {
150 let error = crate::error::ToolCallError::Execution(
152 crate::error::ToolCallExecutionError::ResponseParsingError {
153 reason: format!("Failed to serialize structured content: {e}"),
154 raw_response: None,
155 },
156 );
157 return Err(error);
158 }
159 }
160 } else {
161 vec![Content::text(response.to_mcp_content())]
162 };
163
164 Ok(CallToolResult {
166 content,
167 structured_content,
168 is_error: Some(!response.is_success),
169 meta: None,
170 })
171 }
172 Err(e) => {
173 Err(e)
175 }
176 }
177 }
178
179 pub async fn execute(
181 &self,
182 arguments: &Value,
183 authorization: Authorization,
184 ) -> Result<crate::http_client::HttpResponse, crate::error::ToolCallError> {
185 let auth_header: Option<&rmcp_actix_web::transport::AuthorizationHeader> =
187 match &authorization {
188 Authorization::None => None,
189 #[cfg(feature = "authorization-token-passthrough")]
190 Authorization::PassthroughWarn(header)
191 | Authorization::PassthroughSilent(header) => header.as_ref(),
192 };
193
194 let client = if let Some(auth) = auth_header {
196 self.http_client.with_authorization(&auth.0)
197 } else {
198 self.http_client.clone()
199 };
200
201 client.execute_tool_call(&self.metadata, arguments).await
204 }
205}
206
207impl From<&Tool> for McpTool {
209 fn from(tool: &Tool) -> Self {
210 (&tool.metadata).into()
211 }
212}