(
&self,
args: serde_json::Value,
ctx: ToolContext,
)
| 152 | } |
| 153 | |
| 154 | async fn execute( |
| 155 | &self, |
| 156 | args: serde_json::Value, |
| 157 | ctx: ToolContext, |
| 158 | ) -> Result<ToolResult, ToolError> { |
| 159 | let input: WebSearchInput = |
| 160 | serde_json::from_value(args).map_err(|e| ToolError::InvalidArguments(e.to_string()))?; |
| 161 | |
| 162 | ctx.ask_permission( |
| 163 | crate::PermissionRequest::new("websearch") |
| 164 | .with_pattern(&input.query) |
| 165 | .with_metadata("query", serde_json::Value::String(input.query.clone())) |
| 166 | .with_metadata( |
| 167 | "numResults", |
| 168 | serde_json::Value::Number(input.num_results.into()), |
| 169 | ) |
| 170 | .always_allow(), |
| 171 | ) |
| 172 | .await?; |
| 173 | |
| 174 | let search_request = McpSearchRequest { |
| 175 | jsonrpc: "2.0".to_string(), |
| 176 | id: 1, |
| 177 | method: "tools/call".to_string(), |
| 178 | params: McpSearchParams { |
| 179 | name: "web_search_exa".to_string(), |
| 180 | arguments: McpSearchArguments { |
| 181 | query: input.query.clone(), |
| 182 | search_type: input.search_type.or(Some("auto".to_string())), |
| 183 | num_results: Some(input.num_results), |
| 184 | livecrawl: input.livecrawl.or(Some("fallback".to_string())), |
| 185 | context_max_characters: input.context_max_characters, |
| 186 | }, |
| 187 | }, |
| 188 | }; |
| 189 | |
| 190 | let response = self |
| 191 | .client |
| 192 | .post(format!("{}/mcp", API_BASE_URL)) |
| 193 | .header("Accept", "application/json, text/event-stream") |
| 194 | .header("Content-Type", "application/json") |
| 195 | .json(&search_request) |
| 196 | .timeout(std::time::Duration::from_secs(25)) |
| 197 | .send() |
| 198 | .await |
| 199 | .map_err(|e| { |
| 200 | if e.is_timeout() { |
| 201 | ToolError::ExecutionError("Search request timed out".to_string()) |
| 202 | } else { |
| 203 | ToolError::ExecutionError(format!("Search request failed: {}", e)) |
| 204 | } |
| 205 | })?; |
| 206 | |
| 207 | let status = response.status(); |
| 208 | let response_text = response |
| 209 | .text() |
| 210 | .await |
| 211 | .map_err(|e| ToolError::ExecutionError(format!("Failed to read response: {}", e)))?; |
nothing calls this directly
no test coverage detected