1use serde::{Deserialize, Serialize};
2use serde_json::{Value, json};
3use std::collections::HashMap;
4use std::path::PathBuf;
5use tokio::fs;
6
7#[derive(Debug, Serialize, Deserialize)]
8pub struct MCPRequest {
9 pub jsonrpc: String,
10 pub id: Option<Value>,
11 pub method: String,
12 pub params: Option<Value>,
13}
14
15#[derive(Debug, Serialize, Deserialize)]
16pub struct MCPResponse {
17 pub jsonrpc: String,
18 pub id: Option<Value>,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub result: Option<Value>,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub error: Option<MCPError>,
23}
24
25#[derive(Debug, Serialize, Deserialize)]
26pub struct MCPError {
27 pub code: i32,
28 pub message: String,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub data: Option<Value>,
31}
32
33#[derive(Debug, Serialize, Deserialize)]
34pub struct MCPManifest {
35 #[serde(rename = "serverInfo")]
36 pub server_info: Option<ServerInfo>,
37 pub capabilities: Option<Capabilities>,
38}
39
40#[derive(Debug, Serialize, Deserialize)]
41pub struct ServerInfo {
42 pub name: String,
43 pub version: String,
44}
45
46#[derive(Debug, Serialize, Deserialize)]
47pub struct Capabilities {
48 pub resources: Option<Vec<Value>>,
49 pub tools: Option<Vec<Value>>,
50}
51
52#[derive(Debug, Clone)]
53pub enum SourceType {
54 Local(PathBuf),
55 Remote(String),
56}
57
58pub struct StaticMCPBridge {
59 pub source: SourceType,
60 pub manifest: Option<MCPManifest>,
61 pub client: reqwest::Client,
62}
63
64impl StaticMCPBridge {
65 pub fn new(source_path: String) -> Self {
66 let source = if source_path.starts_with("http://") || source_path.starts_with("https://") {
67 SourceType::Remote(source_path.trim_end_matches('/').to_string())
68 } else {
69 SourceType::Local(PathBuf::from(source_path))
70 };
71
72 eprintln!(
73 "🔗 Bridge mode: {}",
74 match &source {
75 SourceType::Remote(_) => "Remote",
76 SourceType::Local(_) => "Local",
77 }
78 );
79
80 match &source {
81 SourceType::Remote(url) => eprintln!("📍 Source: {url}"),
82 SourceType::Local(path) => eprintln!("📍 Source: {}", path.display()),
83 }
84
85 Self {
86 source,
87 manifest: None,
88 client: reqwest::Client::new(),
89 }
90 }
91
92 pub async fn load_json(
93 &self,
94 relative_path: &str,
95 ) -> Result<Value, Box<dyn std::error::Error>> {
96 match &self.source {
97 SourceType::Remote(base_url) => {
98 let url = format!("{base_url}/{relative_path}");
99 eprintln!("🌐 Fetching: {url}");
100 let response = self.client.get(&url).send().await?;
101 if !response.status().is_success() {
102 return Err(format!(
103 "HTTP {}: {}",
104 response.status(),
105 response.status().canonical_reason().unwrap_or("Unknown")
106 )
107 .into());
108 }
109 let text = response.text().await?;
110 Ok(serde_json::from_str(&text)?)
111 }
112 SourceType::Local(base_path) => {
113 let full_path = base_path.join(relative_path);
114 eprintln!("📁 Reading: {}", full_path.display());
115 let content = fs::read_to_string(full_path).await?;
116 Ok(serde_json::from_str(&content)?)
117 }
118 }
119 }
120
121 pub async fn load_manifest(&mut self) -> Result<&MCPManifest, Box<dyn std::error::Error>> {
122 if self.manifest.is_none() {
123 let manifest_data = self.load_json("mcp.json").await?;
124 let manifest: MCPManifest = serde_json::from_value(manifest_data)?;
125
126 let server_name = manifest
127 .server_info
128 .as_ref()
129 .map(|s| s.name.as_str())
130 .unwrap_or("Unknown");
131 let server_version = manifest
132 .server_info
133 .as_ref()
134 .map(|s| s.version.as_str())
135 .unwrap_or("0.0.0");
136
137 eprintln!("✅ Loaded manifest: {server_name} v{server_version}");
138 self.manifest = Some(manifest);
139 }
140 Ok(self.manifest.as_ref().unwrap())
141 }
142
143 pub fn uri_to_path(&self, uri: &str) -> String {
144 if uri.starts_with("file://") {
145 format!("resources/{}.json", uri.strip_prefix("file://").unwrap())
146 } else if uri.contains("://") {
147 let parts: Vec<&str> = uri.split("://").collect();
148 if parts.len() == 2 {
149 format!("resources/{}.json", parts[1])
150 } else {
151 format!("{uri}.json")
152 }
153 } else if uri.ends_with(".json") {
154 uri.to_string()
155 } else {
156 format!("{uri}.json")
157 }
158 }
159
160 pub fn tool_to_path(&self, tool_name: &str, args: &HashMap<String, Value>) -> String {
161 let tool_dir = format!("tools/{tool_name}");
162
163 if args.is_empty() {
164 return format!("{tool_dir}.json");
165 }
166
167 if args.len() == 1 {
168 let arg_value = args.values().next().unwrap();
169 let arg_str = match arg_value {
170 Value::String(s) => s.clone(),
171 Value::Number(n) => n.to_string(),
172 Value::Bool(b) => b.to_string(),
173 _ => serde_json::to_string(arg_value).unwrap_or_default(),
174 };
175 return format!("{tool_dir}/{arg_str}.json");
176 }
177
178 if args.len() == 2 {
179 let mut values: Vec<String> = args
180 .values()
181 .map(|v| match v {
182 Value::String(s) => s.clone(),
183 Value::Number(n) => n.to_string(),
184 Value::Bool(b) => b.to_string(),
185 _ => serde_json::to_string(v).unwrap_or_default(),
186 })
187 .collect();
188 values.sort(); return format!("{}/{}/{}.json", tool_dir, values[0], values[1]);
190 }
191
192 let mut sorted_args: Vec<(String, String)> = args
194 .iter()
195 .map(|(k, v)| {
196 let val_str = match v {
197 Value::String(s) => s.clone(),
198 Value::Number(n) => n.to_string(),
199 Value::Bool(b) => b.to_string(),
200 _ => serde_json::to_string(v).unwrap_or_default(),
201 };
202 (k.clone(), val_str)
203 })
204 .collect();
205 sorted_args.sort_by(|a, b| a.0.cmp(&b.0));
206
207 let arg_string = sorted_args
208 .iter()
209 .map(|(k, v)| format!("{k}={v}"))
210 .collect::<Vec<_>>()
211 .join("&");
212
213 let hash = base64::Engine::encode(
214 &base64::engine::general_purpose::STANDARD,
215 arg_string.as_bytes(),
216 )
217 .replace(['/', '+', '='], "_");
218
219 format!("{tool_dir}/{hash}.json")
220 }
221
222 pub async fn handle_initialize(&mut self, id: Option<Value>) -> MCPResponse {
223 MCPResponse {
224 jsonrpc: "2.0".to_string(),
225 id,
226 result: Some(json!({
227 "protocolVersion": "2024-11-05",
228 "capabilities": {
229 "resources": {},
230 "tools": {}
231 },
232 "serverInfo": {
233 "name": "generic-static-mcp-bridge",
234 "version": "1.0.0"
235 }
236 })),
237 error: None,
238 }
239 }
240
241 pub async fn handle_list_resources(&mut self, id: Option<Value>) -> MCPResponse {
242 match self.load_manifest().await {
243 Ok(manifest) => {
244 let resources = manifest
245 .capabilities
246 .as_ref()
247 .and_then(|c| c.resources.as_ref())
248 .cloned()
249 .unwrap_or_default();
250
251 eprintln!("📋 Listed {} resources", resources.len());
252
253 MCPResponse {
254 jsonrpc: "2.0".to_string(),
255 id,
256 result: Some(json!({ "resources": resources })),
257 error: None,
258 }
259 }
260 Err(e) => {
261 eprintln!("❌ Error listing resources: {e}");
262 MCPResponse {
263 jsonrpc: "2.0".to_string(),
264 id,
265 result: None,
266 error: Some(MCPError {
267 code: -32603,
268 message: format!("Failed to list resources: {e}"),
269 data: None,
270 }),
271 }
272 }
273 }
274 }
275
276 pub async fn handle_read_resource(&mut self, id: Option<Value>, params: Value) -> MCPResponse {
277 let uri = params.get("uri").and_then(|u| u.as_str()).unwrap_or("");
278 eprintln!("📖 Reading resource: {uri}");
279
280 let resource_path = self.uri_to_path(uri);
281
282 match self.load_json(&resource_path).await {
283 Ok(resource) => {
284 let contents = if let Some(contents) = resource.get("contents") {
285 contents.clone()
286 } else if resource.get("uri").is_some()
287 && resource.get("mimeType").is_some()
288 && resource.get("text").is_some()
289 {
290 json!([{
291 "uri": resource["uri"],
292 "mimeType": resource["mimeType"],
293 "text": resource["text"]
294 }])
295 } else {
296 json!([{
297 "uri": uri,
298 "mimeType": "application/json",
299 "text": serde_json::to_string_pretty(&resource).unwrap_or_default()
300 }])
301 };
302
303 MCPResponse {
304 jsonrpc: "2.0".to_string(),
305 id,
306 result: Some(json!({ "contents": contents })),
307 error: None,
308 }
309 }
310 Err(e) => {
311 eprintln!("❌ Error reading resource {uri}: {e}");
312 MCPResponse {
313 jsonrpc: "2.0".to_string(),
314 id,
315 result: None,
316 error: Some(MCPError {
317 code: -32603,
318 message: format!("Failed to read resource {uri}: {e}"),
319 data: None,
320 }),
321 }
322 }
323 }
324 }
325
326 pub async fn handle_list_tools(&mut self, id: Option<Value>) -> MCPResponse {
327 match self.load_manifest().await {
328 Ok(manifest) => {
329 let tools = manifest
330 .capabilities
331 .as_ref()
332 .and_then(|c| c.tools.as_ref())
333 .cloned()
334 .unwrap_or_default();
335
336 eprintln!("🔧 Listed {} tools", tools.len());
337
338 MCPResponse {
339 jsonrpc: "2.0".to_string(),
340 id,
341 result: Some(json!({ "tools": tools })),
342 error: None,
343 }
344 }
345 Err(e) => {
346 eprintln!("❌ Error listing tools: {e}");
347 MCPResponse {
348 jsonrpc: "2.0".to_string(),
349 id,
350 result: None,
351 error: Some(MCPError {
352 code: -32603,
353 message: format!("Failed to list tools: {e}"),
354 data: None,
355 }),
356 }
357 }
358 }
359 }
360
361 pub async fn handle_call_tool(&mut self, id: Option<Value>, params: Value) -> MCPResponse {
362 let name = params.get("name").and_then(|n| n.as_str()).unwrap_or("");
363 let arguments = params
364 .get("arguments")
365 .and_then(|a| a.as_object())
366 .cloned()
367 .unwrap_or_default();
368
369 let args_map: HashMap<String, Value> = arguments.into_iter().collect();
370
371 eprintln!("🛠️ Calling tool: {name} with args: {args_map:?}");
372
373 let tool_path = self.tool_to_path(name, &args_map);
374
375 match self.load_json(&tool_path).await {
376 Ok(result) => {
377 let content = if result.get("content").is_some() || result.get("contents").is_some()
378 {
379 result
380 } else {
381 json!({
382 "content": [{
383 "type": "text",
384 "text": serde_json::to_string_pretty(&result).unwrap_or_default()
385 }]
386 })
387 };
388
389 MCPResponse {
390 jsonrpc: "2.0".to_string(),
391 id,
392 result: Some(content),
393 error: None,
394 }
395 }
396 Err(e) => {
397 eprintln!("❌ Error calling tool {name}: {e}");
398 MCPResponse {
399 jsonrpc: "2.0".to_string(),
400 id,
401 result: Some(json!({
402 "content": [{
403 "type": "text",
404 "text": format!("Error calling {}: {}", name, e)
405 }],
406 "isError": true
407 })),
408 error: None,
409 }
410 }
411 }
412 }
413
414 pub async fn handle_request(&mut self, request: MCPRequest) -> MCPResponse {
415 match request.method.as_str() {
416 "initialize" => self.handle_initialize(request.id).await,
417 "resources/list" => self.handle_list_resources(request.id).await,
418 "resources/read" => {
419 self.handle_read_resource(request.id, request.params.unwrap_or(json!({})))
420 .await
421 }
422 "tools/list" => self.handle_list_tools(request.id).await,
423 "tools/call" => {
424 self.handle_call_tool(request.id, request.params.unwrap_or(json!({})))
425 .await
426 }
427 _ => MCPResponse {
428 jsonrpc: "2.0".to_string(),
429 id: request.id,
430 result: None,
431 error: Some(MCPError {
432 code: -32601,
433 message: "Method not found".to_string(),
434 data: None,
435 }),
436 },
437 }
438 }
439}