1use std::collections::HashMap;
11use std::io::{self, BufRead, Write};
12use std::sync::Arc;
13use tokio::sync::RwLock;
14
15use serde::{Deserialize, Serialize};
16use serde_json::{json, Value};
17
18use crate::{
19 ArxivClient, BiorxivClient, ClinicalTrialsClient, CrossRefClient,
20 FdaClient, FredClient, MedrxivClient, NativeDiscoveryEngine,
21 NoaaClient, OpenAlexClient, PubMedClient, SemanticScholarClient,
22 SimpleEmbedder, WikidataClient, WikipediaClient, WorldBankClient,
23 NativeEngineConfig, Result, FrameworkError,
24};
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct JsonRpcRequest {
33 pub jsonrpc: String,
34 pub id: Option<Value>,
35 pub method: String,
36 pub params: Option<Value>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct JsonRpcResponse {
42 pub jsonrpc: String,
43 pub id: Option<Value>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub result: Option<Value>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub error: Option<JsonRpcError>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct JsonRpcError {
53 pub code: i32,
54 pub message: String,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub data: Option<Value>,
57}
58
59#[allow(dead_code)]
61impl JsonRpcError {
62 pub const PARSE_ERROR: i32 = -32700;
63 pub const INVALID_REQUEST: i32 = -32600;
64 pub const METHOD_NOT_FOUND: i32 = -32601;
65 pub const INVALID_PARAMS: i32 = -32602;
66 pub const INTERNAL_ERROR: i32 = -32603;
67
68 pub fn parse_error(msg: &str) -> Self {
69 Self { code: Self::PARSE_ERROR, message: msg.to_string(), data: None }
70 }
71
72 pub fn invalid_request(msg: &str) -> Self {
73 Self { code: Self::INVALID_REQUEST, message: msg.to_string(), data: None }
74 }
75
76 pub fn method_not_found(method: &str) -> Self {
77 Self {
78 code: Self::METHOD_NOT_FOUND,
79 message: format!("Method not found: {}", method),
80 data: None
81 }
82 }
83
84 pub fn invalid_params(msg: &str) -> Self {
85 Self { code: Self::INVALID_PARAMS, message: msg.to_string(), data: None }
86 }
87
88 pub fn internal_error(msg: &str) -> Self {
89 Self { code: Self::INTERNAL_ERROR, message: msg.to_string(), data: None }
90 }
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct ServerCapabilities {
100 pub tools: ToolsCapability,
101 pub resources: ResourcesCapability,
102 pub prompts: PromptsCapability,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct ToolsCapability {
107 pub list_changed: bool,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct ResourcesCapability {
112 pub list_changed: bool,
113 pub subscribe: bool,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct PromptsCapability {
118 pub list_changed: bool,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ToolDefinition {
124 pub name: String,
125 pub description: String,
126 pub input_schema: Value,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct ResourceDefinition {
132 pub uri: String,
133 pub name: String,
134 pub description: String,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub mime_type: Option<String>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct PromptDefinition {
142 pub name: String,
143 pub description: String,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub arguments: Option<Vec<PromptArgument>>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct PromptArgument {
150 pub name: String,
151 pub description: String,
152 pub required: bool,
153}
154
155pub enum McpTransport {
161 Stdio,
163 Sse { endpoint: String, port: u16 },
165}
166
167pub struct DataSourceClients {
173 pub openalex: Arc<OpenAlexClient>,
174 pub arxiv: Arc<ArxivClient>,
175 pub semantic_scholar: Arc<SemanticScholarClient>,
176 pub crossref: Arc<CrossRefClient>,
177 pub biorxiv: Arc<BiorxivClient>,
178 pub medrxiv: Arc<MedrxivClient>,
179 pub pubmed: Arc<PubMedClient>,
180 pub clinical_trials: Arc<ClinicalTrialsClient>,
181 pub fda: Arc<FdaClient>,
182 pub fred: Arc<FredClient>,
183 pub worldbank: Arc<WorldBankClient>,
184 pub noaa: Arc<NoaaClient>,
185 pub wikipedia: Arc<WikipediaClient>,
186 pub wikidata: Arc<WikidataClient>,
187 pub embedder: Arc<SimpleEmbedder>,
188}
189
190impl DataSourceClients {
191 pub fn new() -> Self {
193 Self {
194 openalex: Arc::new(OpenAlexClient::new(None).expect("Failed to create OpenAlex client")),
195 arxiv: Arc::new(ArxivClient::new()),
196 semantic_scholar: Arc::new(SemanticScholarClient::new(None)),
197 crossref: Arc::new(CrossRefClient::new(None)),
198 biorxiv: Arc::new(BiorxivClient::new()),
199 medrxiv: Arc::new(MedrxivClient::new()),
200 pubmed: Arc::new(PubMedClient::new(None).expect("Failed to create PubMed client")),
201 clinical_trials: Arc::new(ClinicalTrialsClient::new().expect("Failed to create ClinicalTrials client")),
202 fda: Arc::new(FdaClient::new().expect("Failed to create FDA client")),
203 fred: Arc::new(FredClient::new(None).expect("Failed to create FRED client")),
204 worldbank: Arc::new(WorldBankClient::new().expect("Failed to create WorldBank client")),
205 noaa: Arc::new(NoaaClient::new(None).expect("Failed to create NOAA client")),
206 wikipedia: Arc::new(WikipediaClient::new("en".to_string()).expect("Failed to create Wikipedia client")),
207 wikidata: Arc::new(WikidataClient::new().expect("Failed to create Wikidata client")),
208 embedder: Arc::new(SimpleEmbedder::new(384)),
209 }
210 }
211}
212
213impl Default for DataSourceClients {
214 fn default() -> Self {
215 Self::new()
216 }
217}
218
219#[derive(Debug, Clone)]
225pub struct McpServerConfig {
226 pub name: String,
227 pub version: String,
228 pub max_request_size: usize,
229 pub rate_limit_per_minute: u32,
230}
231
232impl Default for McpServerConfig {
233 fn default() -> Self {
234 Self {
235 name: "ruvector-discovery-mcp".to_string(),
236 version: "1.0.0".to_string(),
237 max_request_size: 10_485_760, rate_limit_per_minute: 100,
239 }
240 }
241}
242
243pub struct McpDiscoveryServer {
249 transport: McpTransport,
250 engine: Arc<RwLock<NativeDiscoveryEngine>>,
251 clients: DataSourceClients,
252 config: McpServerConfig,
253 initialized: bool,
254 request_count: Arc<RwLock<HashMap<String, u32>>>,
255}
256
257impl McpDiscoveryServer {
258 pub fn new(transport: McpTransport, engine_config: NativeEngineConfig) -> Self {
260 Self {
261 transport,
262 engine: Arc::new(RwLock::new(NativeDiscoveryEngine::new(engine_config))),
263 clients: DataSourceClients::new(),
264 config: McpServerConfig::default(),
265 initialized: false,
266 request_count: Arc::new(RwLock::new(HashMap::new())),
267 }
268 }
269
270 pub async fn run(&mut self) -> Result<()> {
272 match &self.transport {
273 McpTransport::Stdio => self.run_stdio().await,
274 McpTransport::Sse { endpoint, port } => {
275 self.run_sse(endpoint.clone(), *port).await
276 }
277 }
278 }
279
280 async fn run_stdio(&mut self) -> Result<()> {
282 let stdin = io::stdin();
283 let mut stdout = io::stdout();
284
285 eprintln!("RuVector MCP Server started (STDIO mode)");
286
287 for line in stdin.lock().lines() {
288 let line = line.map_err(|e| FrameworkError::Config(e.to_string()))?;
289
290 let request: JsonRpcRequest = match serde_json::from_str(&line) {
292 Ok(req) => req,
293 Err(e) => {
294 let error_response = JsonRpcResponse {
295 jsonrpc: "2.0".to_string(),
296 id: None,
297 result: None,
298 error: Some(JsonRpcError::parse_error(&e.to_string())),
299 };
300 let response_json = serde_json::to_string(&error_response)
301 .map_err(|e| FrameworkError::Serialization(e))?;
302 writeln!(stdout, "{}", response_json)
303 .map_err(|e| FrameworkError::Config(e.to_string()))?;
304 continue;
305 }
306 };
307
308 let response = self.handle_request(request).await;
310
311 let response_json = serde_json::to_string(&response)
313 .map_err(|e| FrameworkError::Serialization(e))?;
314 writeln!(stdout, "{}", response_json)
315 .map_err(|e| FrameworkError::Config(e.to_string()))?;
316 stdout.flush()
317 .map_err(|e| FrameworkError::Config(e.to_string()))?;
318 }
319
320 Ok(())
321 }
322
323 async fn run_sse(&mut self, endpoint: String, port: u16) -> Result<()> {
325 #[cfg(feature = "sse")]
326 {
327 use warp::Filter;
328
329 eprintln!("RuVector MCP Server starting on {}:{} (SSE mode)", endpoint, port);
330
331 let server = Arc::new(RwLock::new(self));
332
333 let sse_route = warp::path("sse")
335 .and(warp::get())
336 .map(|| {
337 warp::sse::reply(warp::sse::keep_alive().stream(
338 futures::stream::iter(vec![
339 Ok::<_, warp::Error>(warp::sse::Event::default().data("connected"))
340 ])
341 ))
342 });
343
344 let server_clone = server.clone();
346 let message_route = warp::path("message")
347 .and(warp::post())
348 .and(warp::body::json())
349 .and_then(move |request: JsonRpcRequest| {
350 let server = server_clone.clone();
351 async move {
352 let mut server = server.write().await;
353 let response = server.handle_request(request).await;
354 Ok::<_, warp::Rejection>(warp::reply::json(&response))
355 }
356 });
357
358 let routes = sse_route.or(message_route);
359
360 warp::serve(routes)
361 .run(([127, 0, 0, 1], port))
362 .await;
363
364 Ok(())
365 }
366
367 #[cfg(not(feature = "sse"))]
368 {
369 let _ = (endpoint, port);
370 Err(FrameworkError::Config(
371 "SSE transport requires the 'sse' feature. Compile with --features sse".to_string()
372 ))
373 }
374 }
375
376 async fn handle_request(&mut self, request: JsonRpcRequest) -> JsonRpcResponse {
378 if request.jsonrpc != "2.0" {
380 return JsonRpcResponse {
381 jsonrpc: "2.0".to_string(),
382 id: request.id,
383 result: None,
384 error: Some(JsonRpcError::invalid_request("JSON-RPC version must be 2.0")),
385 };
386 }
387
388 let result = match request.method.as_str() {
390 "initialize" => self.handle_initialize(request.params).await,
391 "initialized" => Ok(json!({})),
392 "tools/list" => self.handle_tools_list().await,
393 "tools/call" => self.handle_tool_call(request.params).await,
394 "resources/list" => self.handle_resources_list().await,
395 "resources/read" => self.handle_resource_read(request.params).await,
396 "prompts/list" => self.handle_prompts_list().await,
397 "prompts/get" => self.handle_prompt_get(request.params).await,
398 _ => Err(JsonRpcError::method_not_found(&request.method)),
399 };
400
401 match result {
402 Ok(result) => JsonRpcResponse {
403 jsonrpc: "2.0".to_string(),
404 id: request.id,
405 result: Some(result),
406 error: None,
407 },
408 Err(error) => JsonRpcResponse {
409 jsonrpc: "2.0".to_string(),
410 id: request.id,
411 result: None,
412 error: Some(error),
413 },
414 }
415 }
416
417 async fn handle_initialize(&mut self, _params: Option<Value>) -> std::result::Result<Value, JsonRpcError> {
419 self.initialized = true;
420
421 Ok(json!({
422 "protocolVersion": "2024-11-05",
423 "serverInfo": {
424 "name": self.config.name,
425 "version": self.config.version,
426 },
427 "capabilities": ServerCapabilities {
428 tools: ToolsCapability { list_changed: false },
429 resources: ResourcesCapability { list_changed: false, subscribe: false },
430 prompts: PromptsCapability { list_changed: false },
431 }
432 }))
433 }
434
435 async fn handle_tools_list(&self) -> std::result::Result<Value, JsonRpcError> {
437 let tools = vec![
438 self.tool_search_openalex(),
440 self.tool_search_arxiv(),
441 self.tool_search_semantic_scholar(),
442 self.tool_get_citations(),
443 self.tool_search_crossref(),
444 self.tool_search_biorxiv(),
445 self.tool_search_medrxiv(),
446
447 self.tool_search_pubmed(),
449 self.tool_search_clinical_trials(),
450 self.tool_search_fda_events(),
451
452 self.tool_get_fred_series(),
454 self.tool_get_worldbank_indicator(),
455
456 self.tool_get_noaa_data(),
458
459 self.tool_search_wikipedia(),
461 self.tool_query_wikidata(),
462
463 self.tool_run_discovery(),
465 self.tool_analyze_coherence(),
466 self.tool_detect_patterns(),
467 self.tool_export_graph(),
468 ];
469
470 Ok(json!({ "tools": tools }))
471 }
472
473 async fn handle_tool_call(&mut self, params: Option<Value>) -> std::result::Result<Value, JsonRpcError> {
475 let params = params.ok_or_else(|| JsonRpcError::invalid_params("Missing params"))?;
476
477 let name = params.get("name")
478 .and_then(|v| v.as_str())
479 .ok_or_else(|| JsonRpcError::invalid_params("Missing tool name"))?;
480
481 let arguments = params.get("arguments")
482 .ok_or_else(|| JsonRpcError::invalid_params("Missing arguments"))?;
483
484 self.check_rate_limit(name).await?;
486
487 let result = match name {
489 "search_openalex" => self.execute_search_openalex(arguments).await,
490 "search_arxiv" => self.execute_search_arxiv(arguments).await,
491 "search_semantic_scholar" => self.execute_search_semantic_scholar(arguments).await,
492 "get_citations" => self.execute_get_citations(arguments).await,
493 "search_crossref" => self.execute_search_crossref(arguments).await,
494 "search_biorxiv" => self.execute_search_biorxiv(arguments).await,
495 "search_medrxiv" => self.execute_search_medrxiv(arguments).await,
496 "search_pubmed" => self.execute_search_pubmed(arguments).await,
497 "search_clinical_trials" => self.execute_search_clinical_trials(arguments).await,
498 "search_fda_events" => self.execute_search_fda_events(arguments).await,
499 "get_fred_series" => self.execute_get_fred_series(arguments).await,
500 "get_worldbank_indicator" => self.execute_get_worldbank_indicator(arguments).await,
501 "get_noaa_data" => self.execute_get_noaa_data(arguments).await,
502 "search_wikipedia" => self.execute_search_wikipedia(arguments).await,
503 "query_wikidata" => self.execute_query_wikidata(arguments).await,
504 "run_discovery" => self.execute_run_discovery(arguments).await,
505 "analyze_coherence" => self.execute_analyze_coherence(arguments).await,
506 "detect_patterns" => self.execute_detect_patterns(arguments).await,
507 "export_graph" => self.execute_export_graph(arguments).await,
508 _ => Err(JsonRpcError::method_not_found(name)),
509 };
510
511 result
512 }
513
514 async fn handle_resources_list(&self) -> std::result::Result<Value, JsonRpcError> {
516 let resources = vec![
517 ResourceDefinition {
518 uri: "discovery://patterns".to_string(),
519 name: "Discovered Patterns".to_string(),
520 description: "Current discovered patterns from analysis".to_string(),
521 mime_type: Some("application/json".to_string()),
522 },
523 ResourceDefinition {
524 uri: "discovery://graph".to_string(),
525 name: "Coherence Graph".to_string(),
526 description: "Current coherence graph structure".to_string(),
527 mime_type: Some("application/json".to_string()),
528 },
529 ResourceDefinition {
530 uri: "discovery://history".to_string(),
531 name: "Coherence History".to_string(),
532 description: "Historical coherence signal data".to_string(),
533 mime_type: Some("application/json".to_string()),
534 },
535 ];
536
537 Ok(json!({ "resources": resources }))
538 }
539
540 async fn handle_resource_read(&self, params: Option<Value>) -> std::result::Result<Value, JsonRpcError> {
542 let params = params.ok_or_else(|| JsonRpcError::invalid_params("Missing params"))?;
543
544 let uri = params.get("uri")
545 .and_then(|v| v.as_str())
546 .ok_or_else(|| JsonRpcError::invalid_params("Missing URI"))?;
547
548 let engine = self.engine.read().await;
549
550 let content = match uri {
551 "discovery://patterns" => {
552 let patterns = engine.get_patterns();
553 json!({
554 "uri": uri,
555 "mimeType": "application/json",
556 "text": serde_json::to_string_pretty(&patterns)
557 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
558 })
559 },
560 "discovery://graph" => {
561 let graph = engine.export_graph();
562 json!({
563 "uri": uri,
564 "mimeType": "application/json",
565 "text": serde_json::to_string_pretty(&graph)
566 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
567 })
568 },
569 "discovery://history" => {
570 let history = engine.get_coherence_history();
571 json!({
572 "uri": uri,
573 "mimeType": "application/json",
574 "text": serde_json::to_string_pretty(&history)
575 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
576 })
577 },
578 _ => return Err(JsonRpcError::invalid_params(&format!("Unknown resource URI: {}", uri))),
579 };
580
581 Ok(json!({ "contents": [content] }))
582 }
583
584 async fn handle_prompts_list(&self) -> std::result::Result<Value, JsonRpcError> {
586 let prompts = vec![
587 PromptDefinition {
588 name: "cross_domain_discovery".to_string(),
589 description: "Discover patterns across multiple research domains".to_string(),
590 arguments: Some(vec![
591 PromptArgument {
592 name: "domains".to_string(),
593 description: "Comma-separated list of domains (research,medical,climate,economic)".to_string(),
594 required: true,
595 },
596 PromptArgument {
597 name: "query".to_string(),
598 description: "Search query to apply across domains".to_string(),
599 required: true,
600 },
601 ]),
602 },
603 PromptDefinition {
604 name: "citation_analysis".to_string(),
605 description: "Build and analyze citation networks".to_string(),
606 arguments: Some(vec![
607 PromptArgument {
608 name: "seed_paper_id".to_string(),
609 description: "Starting paper ID (OpenAlex, Semantic Scholar, or DOI)".to_string(),
610 required: true,
611 },
612 PromptArgument {
613 name: "depth".to_string(),
614 description: "Citation depth to traverse (default: 2)".to_string(),
615 required: false,
616 },
617 ]),
618 },
619 PromptDefinition {
620 name: "trend_detection".to_string(),
621 description: "Detect temporal patterns and trends".to_string(),
622 arguments: Some(vec![
623 PromptArgument {
624 name: "source".to_string(),
625 description: "Data source (arxiv, pubmed, biorxiv, etc.)".to_string(),
626 required: true,
627 },
628 PromptArgument {
629 name: "query".to_string(),
630 description: "Search query".to_string(),
631 required: true,
632 },
633 PromptArgument {
634 name: "days".to_string(),
635 description: "Number of days to analyze (default: 30)".to_string(),
636 required: false,
637 },
638 ]),
639 },
640 ];
641
642 Ok(json!({ "prompts": prompts }))
643 }
644
645 async fn handle_prompt_get(&self, params: Option<Value>) -> std::result::Result<Value, JsonRpcError> {
647 let params = params.ok_or_else(|| JsonRpcError::invalid_params("Missing params"))?;
648
649 let name = params.get("name")
650 .and_then(|v| v.as_str())
651 .ok_or_else(|| JsonRpcError::invalid_params("Missing prompt name"))?;
652
653 let arguments = params.get("arguments")
654 .and_then(|v| v.as_object());
655
656 let messages = match name {
657 "cross_domain_discovery" => {
658 let domains = arguments
659 .and_then(|a| a.get("domains"))
660 .and_then(|v| v.as_str())
661 .ok_or_else(|| JsonRpcError::invalid_params("Missing 'domains' argument"))?;
662 let query = arguments
663 .and_then(|a| a.get("query"))
664 .and_then(|v| v.as_str())
665 .ok_or_else(|| JsonRpcError::invalid_params("Missing 'query' argument"))?;
666
667 vec![json!({
668 "role": "user",
669 "content": {
670 "type": "text",
671 "text": format!(
672 "Perform cross-domain discovery across: {}\nQuery: {}\n\n\
673 Steps:\n\
674 1. Search each domain using run_discovery\n\
675 2. Analyze coherence across domains\n\
676 3. Detect emerging patterns\n\
677 4. Export visualization graph",
678 domains, query
679 )
680 }
681 })]
682 },
683 "citation_analysis" => {
684 let paper_id = arguments
685 .and_then(|a| a.get("seed_paper_id"))
686 .and_then(|v| v.as_str())
687 .ok_or_else(|| JsonRpcError::invalid_params("Missing 'seed_paper_id' argument"))?;
688 let depth = arguments
689 .and_then(|a| a.get("depth"))
690 .and_then(|v| v.as_u64())
691 .unwrap_or(2);
692
693 vec![json!({
694 "role": "user",
695 "content": {
696 "type": "text",
697 "text": format!(
698 "Build citation network starting from paper: {}\nDepth: {}\n\n\
699 Steps:\n\
700 1. Get initial paper with get_citations\n\
701 2. Recursively fetch citations up to depth {}\n\
702 3. Build coherence graph\n\
703 4. Analyze citation patterns\n\
704 5. Export as GraphML for visualization",
705 paper_id, depth, depth
706 )
707 }
708 })]
709 },
710 "trend_detection" => {
711 let source = arguments
712 .and_then(|a| a.get("source"))
713 .and_then(|v| v.as_str())
714 .ok_or_else(|| JsonRpcError::invalid_params("Missing 'source' argument"))?;
715 let query = arguments
716 .and_then(|a| a.get("query"))
717 .and_then(|v| v.as_str())
718 .ok_or_else(|| JsonRpcError::invalid_params("Missing 'query' argument"))?;
719 let days = arguments
720 .and_then(|a| a.get("days"))
721 .and_then(|v| v.as_u64())
722 .unwrap_or(30);
723
724 vec![json!({
725 "role": "user",
726 "content": {
727 "type": "text",
728 "text": format!(
729 "Detect trends in {} for query: {}\nTime window: {} days\n\n\
730 Steps:\n\
731 1. Fetch recent data from {}\n\
732 2. Compute temporal coherence signals\n\
733 3. Detect emerging/declining patterns\n\
734 4. Generate trend forecast",
735 source, query, days, source
736 )
737 }
738 })]
739 },
740 _ => return Err(JsonRpcError::method_not_found(name)),
741 };
742
743 Ok(json!({
744 "description": format!("Prompt: {}", name),
745 "messages": messages
746 }))
747 }
748
749 async fn check_rate_limit(&self, tool_name: &str) -> std::result::Result<(), JsonRpcError> {
751 let mut counts = self.request_count.write().await;
752 let count = counts.entry(tool_name.to_string()).or_insert(0);
753 *count += 1;
754
755 if *count > self.config.rate_limit_per_minute {
756 return Err(JsonRpcError::internal_error("Rate limit exceeded"));
757 }
758
759 Ok(())
760 }
761
762 fn tool_search_openalex(&self) -> ToolDefinition {
767 ToolDefinition {
768 name: "search_openalex".to_string(),
769 description: "Search OpenAlex for research papers and scholarly works".to_string(),
770 input_schema: json!({
771 "type": "object",
772 "properties": {
773 "query": { "type": "string", "description": "Search query" },
774 "limit": { "type": "integer", "description": "Maximum results (default: 10)", "default": 10 }
775 },
776 "required": ["query"]
777 }),
778 }
779 }
780
781 fn tool_search_arxiv(&self) -> ToolDefinition {
782 ToolDefinition {
783 name: "search_arxiv".to_string(),
784 description: "Search arXiv preprint repository".to_string(),
785 input_schema: json!({
786 "type": "object",
787 "properties": {
788 "query": { "type": "string", "description": "Search query" },
789 "category": { "type": "string", "description": "arXiv category (e.g., cs.AI, physics.gen-ph)" },
790 "limit": { "type": "integer", "description": "Maximum results", "default": 10 }
791 },
792 "required": ["query"]
793 }),
794 }
795 }
796
797 fn tool_search_semantic_scholar(&self) -> ToolDefinition {
798 ToolDefinition {
799 name: "search_semantic_scholar".to_string(),
800 description: "Search Semantic Scholar academic database".to_string(),
801 input_schema: json!({
802 "type": "object",
803 "properties": {
804 "query": { "type": "string", "description": "Search query" },
805 "limit": { "type": "integer", "description": "Maximum results", "default": 10 }
806 },
807 "required": ["query"]
808 }),
809 }
810 }
811
812 fn tool_get_citations(&self) -> ToolDefinition {
813 ToolDefinition {
814 name: "get_citations".to_string(),
815 description: "Get citations for a paper (Semantic Scholar or OpenAlex)".to_string(),
816 input_schema: json!({
817 "type": "object",
818 "properties": {
819 "paper_id": { "type": "string", "description": "Paper ID or DOI" }
820 },
821 "required": ["paper_id"]
822 }),
823 }
824 }
825
826 fn tool_search_crossref(&self) -> ToolDefinition {
827 ToolDefinition {
828 name: "search_crossref".to_string(),
829 description: "Search CrossRef DOI database".to_string(),
830 input_schema: json!({
831 "type": "object",
832 "properties": {
833 "query": { "type": "string", "description": "Search query" },
834 "limit": { "type": "integer", "description": "Maximum results", "default": 10 }
835 },
836 "required": ["query"]
837 }),
838 }
839 }
840
841 fn tool_search_biorxiv(&self) -> ToolDefinition {
842 ToolDefinition {
843 name: "search_biorxiv".to_string(),
844 description: "Search bioRxiv preprints".to_string(),
845 input_schema: json!({
846 "type": "object",
847 "properties": {
848 "category": { "type": "string", "description": "Category (e.g., neuroscience, bioinformatics)" },
849 "days": { "type": "integer", "description": "Days back to search", "default": 7 }
850 },
851 "required": ["category"]
852 }),
853 }
854 }
855
856 fn tool_search_medrxiv(&self) -> ToolDefinition {
857 ToolDefinition {
858 name: "search_medrxiv".to_string(),
859 description: "Search medRxiv medical preprints".to_string(),
860 input_schema: json!({
861 "type": "object",
862 "properties": {
863 "query": { "type": "string", "description": "Search query" },
864 "days": { "type": "integer", "description": "Days back to search", "default": 7 }
865 },
866 "required": ["query"]
867 }),
868 }
869 }
870
871 fn tool_search_pubmed(&self) -> ToolDefinition {
872 ToolDefinition {
873 name: "search_pubmed".to_string(),
874 description: "Search PubMed medical literature database".to_string(),
875 input_schema: json!({
876 "type": "object",
877 "properties": {
878 "query": { "type": "string", "description": "Search query" },
879 "limit": { "type": "integer", "description": "Maximum results", "default": 10 }
880 },
881 "required": ["query"]
882 }),
883 }
884 }
885
886 fn tool_search_clinical_trials(&self) -> ToolDefinition {
887 ToolDefinition {
888 name: "search_clinical_trials".to_string(),
889 description: "Search ClinicalTrials.gov".to_string(),
890 input_schema: json!({
891 "type": "object",
892 "properties": {
893 "condition": { "type": "string", "description": "Medical condition" },
894 "status": { "type": "string", "description": "Trial status (e.g., recruiting, completed)" }
895 },
896 "required": ["condition"]
897 }),
898 }
899 }
900
901 fn tool_search_fda_events(&self) -> ToolDefinition {
902 ToolDefinition {
903 name: "search_fda_events".to_string(),
904 description: "Search FDA adverse event reports".to_string(),
905 input_schema: json!({
906 "type": "object",
907 "properties": {
908 "drug_name": { "type": "string", "description": "Drug name to search" }
909 },
910 "required": ["drug_name"]
911 }),
912 }
913 }
914
915 fn tool_get_fred_series(&self) -> ToolDefinition {
916 ToolDefinition {
917 name: "get_fred_series".to_string(),
918 description: "Get Federal Reserve Economic Data (FRED) time series".to_string(),
919 input_schema: json!({
920 "type": "object",
921 "properties": {
922 "series_id": { "type": "string", "description": "FRED series ID (e.g., GDP, UNRATE)" }
923 },
924 "required": ["series_id"]
925 }),
926 }
927 }
928
929 fn tool_get_worldbank_indicator(&self) -> ToolDefinition {
930 ToolDefinition {
931 name: "get_worldbank_indicator".to_string(),
932 description: "Get World Bank development indicators".to_string(),
933 input_schema: json!({
934 "type": "object",
935 "properties": {
936 "country": { "type": "string", "description": "Country code (e.g., USA, CHN)" },
937 "indicator": { "type": "string", "description": "Indicator code (e.g., NY.GDP.MKTP.CD)" }
938 },
939 "required": ["country", "indicator"]
940 }),
941 }
942 }
943
944 fn tool_get_noaa_data(&self) -> ToolDefinition {
945 ToolDefinition {
946 name: "get_noaa_data".to_string(),
947 description: "Get NOAA climate data".to_string(),
948 input_schema: json!({
949 "type": "object",
950 "properties": {
951 "station": { "type": "string", "description": "Weather station ID" },
952 "start_date": { "type": "string", "description": "Start date (YYYY-MM-DD)" },
953 "end_date": { "type": "string", "description": "End date (YYYY-MM-DD)" }
954 },
955 "required": ["station", "start_date", "end_date"]
956 }),
957 }
958 }
959
960 fn tool_search_wikipedia(&self) -> ToolDefinition {
961 ToolDefinition {
962 name: "search_wikipedia".to_string(),
963 description: "Search Wikipedia articles".to_string(),
964 input_schema: json!({
965 "type": "object",
966 "properties": {
967 "query": { "type": "string", "description": "Search query" },
968 "language": { "type": "string", "description": "Language code (default: en)", "default": "en" }
969 },
970 "required": ["query"]
971 }),
972 }
973 }
974
975 fn tool_query_wikidata(&self) -> ToolDefinition {
976 ToolDefinition {
977 name: "query_wikidata".to_string(),
978 description: "Query Wikidata knowledge graph using SPARQL".to_string(),
979 input_schema: json!({
980 "type": "object",
981 "properties": {
982 "sparql_query": { "type": "string", "description": "SPARQL query string" }
983 },
984 "required": ["sparql_query"]
985 }),
986 }
987 }
988
989 fn tool_run_discovery(&self) -> ToolDefinition {
990 ToolDefinition {
991 name: "run_discovery".to_string(),
992 description: "Run discovery engine across multiple data sources".to_string(),
993 input_schema: json!({
994 "type": "object",
995 "properties": {
996 "sources": {
997 "type": "array",
998 "items": { "type": "string" },
999 "description": "Data sources to query"
1000 },
1001 "query": { "type": "string", "description": "Discovery query" }
1002 },
1003 "required": ["sources", "query"]
1004 }),
1005 }
1006 }
1007
1008 fn tool_analyze_coherence(&self) -> ToolDefinition {
1009 ToolDefinition {
1010 name: "analyze_coherence".to_string(),
1011 description: "Analyze coherence of vector embeddings".to_string(),
1012 input_schema: json!({
1013 "type": "object",
1014 "properties": {
1015 "vectors": {
1016 "type": "array",
1017 "items": {
1018 "type": "object",
1019 "properties": {
1020 "id": { "type": "string" },
1021 "embedding": { "type": "array", "items": { "type": "number" } }
1022 }
1023 },
1024 "description": "Vector embeddings to analyze"
1025 }
1026 },
1027 "required": ["vectors"]
1028 }),
1029 }
1030 }
1031
1032 fn tool_detect_patterns(&self) -> ToolDefinition {
1033 ToolDefinition {
1034 name: "detect_patterns".to_string(),
1035 description: "Detect patterns in coherence signals".to_string(),
1036 input_schema: json!({
1037 "type": "object",
1038 "properties": {
1039 "signals": {
1040 "type": "array",
1041 "items": {
1042 "type": "object",
1043 "properties": {
1044 "window_id": { "type": "integer" },
1045 "score": { "type": "number" }
1046 }
1047 },
1048 "description": "Coherence signals to analyze"
1049 }
1050 },
1051 "required": ["signals"]
1052 }),
1053 }
1054 }
1055
1056 fn tool_export_graph(&self) -> ToolDefinition {
1057 ToolDefinition {
1058 name: "export_graph".to_string(),
1059 description: "Export coherence graph in various formats".to_string(),
1060 input_schema: json!({
1061 "type": "object",
1062 "properties": {
1063 "format": {
1064 "type": "string",
1065 "enum": ["graphml", "dot", "csv"],
1066 "description": "Export format"
1067 }
1068 },
1069 "required": ["format"]
1070 }),
1071 }
1072 }
1073
1074 async fn execute_search_openalex(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1079 let query = args.get("query")
1080 .and_then(|v| v.as_str())
1081 .ok_or_else(|| JsonRpcError::invalid_params("Missing query"))?;
1082 let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
1083
1084 let results = self.clients.openalex.fetch_works(query, limit).await
1085 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1086
1087 Ok(json!({
1088 "content": [{
1089 "type": "text",
1090 "text": serde_json::to_string_pretty(&results)
1091 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1092 }]
1093 }))
1094 }
1095
1096 async fn execute_search_arxiv(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1097 let query = args.get("query")
1098 .and_then(|v| v.as_str())
1099 .ok_or_else(|| JsonRpcError::invalid_params("Missing query"))?;
1100 let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
1101
1102 let results = self.clients.arxiv.search(query, limit).await
1103 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1104
1105 Ok(json!({
1106 "content": [{
1107 "type": "text",
1108 "text": serde_json::to_string_pretty(&results)
1109 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1110 }]
1111 }))
1112 }
1113
1114 async fn execute_search_semantic_scholar(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1115 let query = args.get("query")
1116 .and_then(|v| v.as_str())
1117 .ok_or_else(|| JsonRpcError::invalid_params("Missing query"))?;
1118 let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
1119
1120 let results = self.clients.semantic_scholar.search_papers(query, limit).await
1121 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1122
1123 Ok(json!({
1124 "content": [{
1125 "type": "text",
1126 "text": serde_json::to_string_pretty(&results)
1127 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1128 }]
1129 }))
1130 }
1131
1132 async fn execute_get_citations(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1133 let paper_id = args.get("paper_id")
1134 .and_then(|v| v.as_str())
1135 .ok_or_else(|| JsonRpcError::invalid_params("Missing paper_id"))?;
1136
1137 let citations = self.clients.semantic_scholar.get_citations(paper_id, 100).await
1138 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1139
1140 Ok(json!({
1141 "content": [{
1142 "type": "text",
1143 "text": serde_json::to_string_pretty(&citations)
1144 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1145 }]
1146 }))
1147 }
1148
1149 async fn execute_search_crossref(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1150 let query = args.get("query")
1151 .and_then(|v| v.as_str())
1152 .ok_or_else(|| JsonRpcError::invalid_params("Missing query"))?;
1153 let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
1154
1155 let results = self.clients.crossref.search_works(query, limit).await
1156 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1157
1158 Ok(json!({
1159 "content": [{
1160 "type": "text",
1161 "text": serde_json::to_string_pretty(&results)
1162 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1163 }]
1164 }))
1165 }
1166
1167 async fn execute_search_biorxiv(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1168 let _category = args.get("category")
1169 .and_then(|v| v.as_str())
1170 .ok_or_else(|| JsonRpcError::invalid_params("Missing category"))?;
1171 let days = args.get("days").and_then(|v| v.as_u64()).unwrap_or(7);
1172 let limit = 10; let results = self.clients.biorxiv.search_recent(days, limit).await
1175 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1176
1177 Ok(json!({
1178 "content": [{
1179 "type": "text",
1180 "text": serde_json::to_string_pretty(&results)
1181 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1182 }]
1183 }))
1184 }
1185
1186 async fn execute_search_medrxiv(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1187 let _query = args.get("query")
1188 .and_then(|v| v.as_str())
1189 .ok_or_else(|| JsonRpcError::invalid_params("Missing query"))?;
1190 let days = args.get("days").and_then(|v| v.as_u64()).unwrap_or(7);
1191 let limit = 10; let results = self.clients.medrxiv.search_recent(days, limit).await
1194 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1195
1196 Ok(json!({
1197 "content": [{
1198 "type": "text",
1199 "text": serde_json::to_string_pretty(&results)
1200 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1201 }]
1202 }))
1203 }
1204
1205 async fn execute_search_pubmed(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1206 let query = args.get("query")
1207 .and_then(|v| v.as_str())
1208 .ok_or_else(|| JsonRpcError::invalid_params("Missing query"))?;
1209 let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
1210
1211 let results = self.clients.pubmed.search_articles(query, limit).await
1212 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1213
1214 Ok(json!({
1215 "content": [{
1216 "type": "text",
1217 "text": serde_json::to_string_pretty(&results)
1218 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1219 }]
1220 }))
1221 }
1222
1223 async fn execute_search_clinical_trials(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1224 let condition = args.get("condition")
1225 .and_then(|v| v.as_str())
1226 .ok_or_else(|| JsonRpcError::invalid_params("Missing condition"))?;
1227 let status = args.get("status").and_then(|v| v.as_str());
1228
1229 let results = self.clients.clinical_trials.search_trials(condition, status).await
1230 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1231
1232 Ok(json!({
1233 "content": [{
1234 "type": "text",
1235 "text": serde_json::to_string_pretty(&results)
1236 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1237 }]
1238 }))
1239 }
1240
1241 async fn execute_search_fda_events(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1242 let drug_name = args.get("drug_name")
1243 .and_then(|v| v.as_str())
1244 .ok_or_else(|| JsonRpcError::invalid_params("Missing drug_name"))?;
1245
1246 let results = self.clients.fda.search_drug_events(drug_name).await
1247 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1248
1249 Ok(json!({
1250 "content": [{
1251 "type": "text",
1252 "text": serde_json::to_string_pretty(&results)
1253 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1254 }]
1255 }))
1256 }
1257
1258 async fn execute_get_fred_series(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1259 let series_id = args.get("series_id")
1260 .and_then(|v| v.as_str())
1261 .ok_or_else(|| JsonRpcError::invalid_params("Missing series_id"))?;
1262 let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(100) as usize;
1263
1264 let results = self.clients.fred.get_series(series_id, Some(limit)).await
1265 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1266
1267 Ok(json!({
1268 "content": [{
1269 "type": "text",
1270 "text": serde_json::to_string_pretty(&results)
1271 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1272 }]
1273 }))
1274 }
1275
1276 async fn execute_get_worldbank_indicator(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1277 let country = args.get("country")
1278 .and_then(|v| v.as_str())
1279 .ok_or_else(|| JsonRpcError::invalid_params("Missing country"))?;
1280 let indicator = args.get("indicator")
1281 .and_then(|v| v.as_str())
1282 .ok_or_else(|| JsonRpcError::invalid_params("Missing indicator"))?;
1283
1284 let results = self.clients.worldbank.get_indicator(country, indicator).await
1285 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1286
1287 Ok(json!({
1288 "content": [{
1289 "type": "text",
1290 "text": serde_json::to_string_pretty(&results)
1291 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1292 }]
1293 }))
1294 }
1295
1296 async fn execute_get_noaa_data(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1297 let station_id = args.get("station_id")
1298 .and_then(|v| v.as_str())
1299 .ok_or_else(|| JsonRpcError::invalid_params("Missing station_id"))?;
1300 let start_date = args.get("start_date")
1301 .and_then(|v| v.as_str())
1302 .ok_or_else(|| JsonRpcError::invalid_params("Missing start_date"))?;
1303 let end_date = args.get("end_date")
1304 .and_then(|v| v.as_str())
1305 .ok_or_else(|| JsonRpcError::invalid_params("Missing end_date"))?;
1306
1307 let results = self.clients.noaa.fetch_climate_data(station_id, start_date, end_date).await
1308 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1309
1310 Ok(json!({
1311 "content": [{
1312 "type": "text",
1313 "text": serde_json::to_string_pretty(&results)
1314 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1315 }]
1316 }))
1317 }
1318
1319 async fn execute_search_wikipedia(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1320 let query = args.get("query")
1321 .and_then(|v| v.as_str())
1322 .ok_or_else(|| JsonRpcError::invalid_params("Missing query"))?;
1323 let limit = args.get("limit").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
1324
1325 let results = self.clients.wikipedia.search(query, limit).await
1326 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1327
1328 Ok(json!({
1329 "content": [{
1330 "type": "text",
1331 "text": serde_json::to_string_pretty(&results)
1332 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1333 }]
1334 }))
1335 }
1336
1337 async fn execute_query_wikidata(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1338 let sparql_query = args.get("sparql_query")
1339 .and_then(|v| v.as_str())
1340 .ok_or_else(|| JsonRpcError::invalid_params("Missing sparql_query"))?;
1341
1342 let results = self.clients.wikidata.sparql_query(sparql_query).await
1343 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?;
1344
1345 Ok(json!({
1346 "content": [{
1347 "type": "text",
1348 "text": serde_json::to_string_pretty(&results)
1349 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1350 }]
1351 }))
1352 }
1353
1354 async fn execute_run_discovery(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1355 let _sources = args.get("sources")
1356 .and_then(|v| v.as_array())
1357 .ok_or_else(|| JsonRpcError::invalid_params("Missing sources array"))?;
1358 let query = args.get("query")
1359 .and_then(|v| v.as_str())
1360 .ok_or_else(|| JsonRpcError::invalid_params("Missing query"))?;
1361
1362 Ok(json!({
1364 "content": [{
1365 "type": "text",
1366 "text": format!("Discovery query '{}' across sources", query)
1367 }]
1368 }))
1369 }
1370
1371 async fn execute_analyze_coherence(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1372 let _vectors = args.get("vectors")
1373 .and_then(|v| v.as_array())
1374 .ok_or_else(|| JsonRpcError::invalid_params("Missing vectors array"))?;
1375
1376 Ok(json!({
1378 "content": [{
1379 "type": "text",
1380 "text": "Coherence analysis complete"
1381 }]
1382 }))
1383 }
1384
1385 async fn execute_detect_patterns(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1386 let _signals = args.get("signals")
1387 .and_then(|v| v.as_array())
1388 .ok_or_else(|| JsonRpcError::invalid_params("Missing signals array"))?;
1389
1390 let engine = self.engine.read().await;
1391 let patterns = engine.get_patterns();
1392
1393 Ok(json!({
1394 "content": [{
1395 "type": "text",
1396 "text": serde_json::to_string_pretty(&patterns)
1397 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?
1398 }]
1399 }))
1400 }
1401
1402 async fn execute_export_graph(&self, args: &Value) -> std::result::Result<Value, JsonRpcError> {
1403 let format = args.get("format")
1404 .and_then(|v| v.as_str())
1405 .ok_or_else(|| JsonRpcError::invalid_params("Missing format"))?;
1406
1407 let engine = self.engine.read().await;
1408 let graph = engine.export_graph();
1409
1410 let exported = match format {
1411 "graphml" => serde_json::to_string_pretty(&json!({ "format": "graphml", "graph": graph }))
1412 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?,
1413 "dot" => serde_json::to_string_pretty(&json!({ "format": "dot", "graph": graph }))
1414 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?,
1415 "csv" => serde_json::to_string_pretty(&json!({ "format": "csv", "graph": graph }))
1416 .map_err(|e| JsonRpcError::internal_error(&e.to_string()))?,
1417 _ => return Err(JsonRpcError::invalid_params("Invalid format")),
1418 };
1419
1420 Ok(json!({
1421 "content": [{
1422 "type": "text",
1423 "text": exported
1424 }]
1425 }))
1426 }
1427}
1428
1429#[cfg(test)]
1430mod tests {
1431 use super::*;
1432
1433 #[test]
1434 fn test_jsonrpc_error_codes() {
1435 let err = JsonRpcError::parse_error("test");
1436 assert_eq!(err.code, JsonRpcError::PARSE_ERROR);
1437
1438 let err = JsonRpcError::method_not_found("test_method");
1439 assert_eq!(err.code, JsonRpcError::METHOD_NOT_FOUND);
1440 assert!(err.message.contains("test_method"));
1441 }
1442
1443 #[test]
1444 fn test_server_capabilities() {
1445 let caps = ServerCapabilities {
1446 tools: ToolsCapability { list_changed: false },
1447 resources: ResourcesCapability { list_changed: false, subscribe: false },
1448 prompts: PromptsCapability { list_changed: false },
1449 };
1450
1451 let json = serde_json::to_value(&caps).unwrap();
1452 assert!(json.get("tools").is_some());
1453 assert!(json.get("resources").is_some());
1454 assert!(json.get("prompts").is_some());
1455 }
1456}