1use async_trait::async_trait;
4use crate::error::BrightDataError;
5use serde_json::Value;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct McpContent {
11 #[serde(rename = "type")]
12 pub content_type: String,
13 pub text: String,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub data: Option<String>, #[serde(skip_serializing_if = "Option::is_none")]
17 pub media_type: Option<String>, }
19
20impl McpContent {
21 pub fn text(text: String) -> Self {
22 Self {
23 content_type: "text".to_string(),
24 text,
25 data: None,
26 media_type: None,
27 }
28 }
29
30 pub fn image(data: String, media_type: String) -> Self {
31 Self {
32 content_type: "image".to_string(),
33 text: "Screenshot captured".to_string(),
34 data: Some(data),
35 media_type: Some(media_type),
36 }
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ToolResult {
43 pub content: Vec<McpContent>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub is_error: Option<bool>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub raw_value: Option<Value>,
49}
50
51impl ToolResult {
52 pub fn success(content: Vec<McpContent>) -> Self {
53 Self {
54 content,
55 is_error: Some(false),
56 raw_value: None,
57 }
58 }
59
60 pub fn success_with_text(text: String) -> Self {
61 Self {
62 content: vec![McpContent::text(text)],
63 is_error: Some(false),
64 raw_value: None,
65 }
66 }
67
68 pub fn success_with_raw(content: Vec<McpContent>, raw: Value) -> Self {
69 Self {
70 content,
71 is_error: Some(false),
72 raw_value: Some(raw),
73 }
74 }
75
76 pub fn error(message: String) -> Self {
77 Self {
78 content: vec![McpContent::text(format!("Error: {}", message))],
79 is_error: Some(true),
80 raw_value: None,
81 }
82 }
83
84 pub fn from_legacy_value(value: Value) -> Self {
86 let text = if let Some(raw_text) = value.get("raw").and_then(|v| v.as_str()) {
87 raw_text.to_string()
88 } else {
89 serde_json::to_string_pretty(&value).unwrap_or_else(|_| value.to_string())
90 };
91
92 Self {
93 content: vec![McpContent::text(text)],
94 is_error: Some(false),
95 raw_value: Some(value),
96 }
97 }
98}
99
100#[async_trait]
101pub trait Tool {
102 fn name(&self) -> &str;
103 fn description(&self) -> &str;
104 fn input_schema(&self) -> Value; async fn execute(&self, parameters: Value) -> Result<ToolResult, BrightDataError>;
108
109 async fn execute_legacy(&self, parameters: Value) -> Result<Value, BrightDataError> {
111 let result = self.execute(parameters).await?;
112 if let Some(raw) = result.raw_value {
113 Ok(raw)
114 } else if !result.content.is_empty() {
115 Ok(serde_json::json!({
116 "content": result.content[0].text
117 }))
118 } else {
119 Ok(serde_json::json!({}))
120 }
121 }
122}
123
124pub struct ToolResolver;
126
127impl Default for ToolResolver {
128 fn default() -> Self {
129 Self
130 }
131}
132
133impl ToolResolver {
134 pub fn resolve(&self, name: &str) -> Option<Box<dyn Tool + Send + Sync>> {
135 match name {
136 "scrape_website" => Some(Box::new(crate::tools::scrape::ScrapeMarkdown)),
138 "search_web" => Some(Box::new(crate::tools::search::SearchEngine)),
139 "extract_data" => Some(Box::new(crate::tools::extract::Extractor)),
140 "get_crypto_data" => Some(Box::new(crate::tools::search::SearchEngine)),
141 "take_screenshot" => Some(Box::new(crate::tools::screenshot::ScreenshotTool)),
142 "multi_zone_search" => Some(Box::new(crate::tools::multi_zone_search::MultiZoneSearch)),
143 "get_stock_data" => Some(Box::new(crate::tools::stock::StockDataTool)),
145 "get_crypto_data" => Some(Box::new(crate::tools::crypto::CryptoDataTool)),
146 "get_etf_data" => Some(Box::new(crate::tools::etf::ETFDataTool)),
147 "get_bond_data" => Some(Box::new(crate::tools::bond::BondDataTool)),
148 "get_mutual_fund_data" => Some(Box::new(crate::tools::mutual_fund::MutualFundDataTool)),
149 "get_commodity_data" => Some(Box::new(crate::tools::commodity::CommodityDataTool)),
150 "get_market_overview" => Some(Box::new(crate::tools::market::MarketOverviewTool)),
151 _ => None,
152 }
153 }
154
155 pub fn list_tools(&self) -> Vec<Value> {
156 vec![
157 serde_json::json!({
158 "name": "scrape_website",
159 "description": "Scrape a webpage into markdown",
160 "inputSchema": {
161 "type": "object",
162 "required": ["url"],
163 "properties": {
164 "url": { "type": "string" },
165 "format": { "type": "string", "enum": ["markdown", "raw"] }
166 }
167 }
168 }),
169 serde_json::json!({
170 "name": "search_web",
171 "description": "Search the web using BrightData",
172 "inputSchema": {
173 "type": "object",
174 "required": ["query"],
175 "properties": {
176 "query": { "type": "string" },
177 "engine": {
178 "type": "string",
179 "enum": ["google", "bing", "yandex", "duckduckgo"]
180 },
181 "cursor": { "type": "string" }
182 }
183 }
184 }),
185 serde_json::json!({
186 "name": "extract_data",
187 "description": "Extract structured data from a webpage",
188 "inputSchema": {
189 "type": "object",
190 "required": ["url"],
191 "properties": {
192 "url": { "type": "string" },
193 "schema": { "type": "object" }
194 }
195 }
196 }),
197 serde_json::json!({
198 "name": "take_screenshot",
199 "description": "Take a screenshot of a webpage",
200 "inputSchema": {
201 "type": "object",
202 "required": ["url"],
203 "properties": {
204 "url": { "type": "string" },
205 "width": { "type": "integer" },
206 "height": { "type": "integer" },
207 "full_page": { "type": "boolean" }
208 }
209 }
210 }),
211 serde_json::json!({
212 "name": "get_crypto_data",
213 "description": "Crypto search using BrightData",
214 "inputSchema": {
215 "type": "object",
216 "required": ["query"],
217 "properties": {
218 "query": { "type": "string" },
219 "engine": {
220 "type": "string",
221 "enum": ["google", "bing", "yandex", "duckduckgo"]
222 },
223 "cursor": { "type": "string" }
224 }
225 }
226 }),
227 serde_json::json!({
228 "name": "multi_zone_search",
229 "description": "Multi-region search across zones",
230 "inputSchema": {
231 "type": "object",
232 "required": ["query", "zones"],
233 "properties": {
234 "query": { "type": "string" },
235 "engine": {
236 "type": "string",
237 "enum": ["google", "bing", "yandex", "duckduckgo"]
238 },
239 "zones": {
240 "type": "array",
241 "items": { "type": "string" }
242 }
243 }
244 }
245 })
246 ]
247 }
248
249
250 pub fn get_available_tool_names(&self) -> Vec<&'static str> {
252 vec![
253 "scrape_website",
254 "search_web",
255 "extract_data",
256 "take_screenshot",
257 "get_crypto_data",
258 "multi_zone_search"
259 ]
260 }
261
262 pub fn tool_exists(&self, name: &str) -> bool {
264 self.get_available_tool_names().contains(&name)
265 }
266
267 pub fn tool_count(&self) -> usize {
269 self.get_available_tool_names().len()
270 }
271}
272