1use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::time::{Duration, Instant};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct JsonRpcRequest {
12 pub jsonrpc: String,
14 pub method: String,
16 #[serde(default)]
18 pub params: Option<Value>,
19 pub id: Option<Value>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct JsonRpcResponse {
26 pub jsonrpc: String,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub id: Option<Value>,
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub result: Option<Value>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub error: Option<JsonRpcError>,
37}
38
39impl JsonRpcResponse {
40 pub fn success(id: Option<Value>, result: Value) -> Self {
42 Self {
43 jsonrpc: "2.0".to_string(),
44 id,
45 result: Some(result),
46 error: None,
47 }
48 }
49
50 pub fn error(id: Option<Value>, code: i32, message: impl Into<String>) -> Self {
52 Self {
53 jsonrpc: "2.0".to_string(),
54 id,
55 result: None,
56 error: Some(JsonRpcError {
57 code,
58 message: message.into(),
59 data: None,
60 }),
61 }
62 }
63
64 pub fn parse_error() -> Self {
66 Self::error(None, -32700, "Parse error")
67 }
68
69 pub fn invalid_request(id: Option<Value>) -> Self {
71 Self::error(id, -32600, "Invalid Request")
72 }
73
74 pub fn method_not_found(id: Option<Value>, method: &str) -> Self {
76 Self::error(id, -32601, format!("Method not found: {}", method))
77 }
78
79 pub fn invalid_params(id: Option<Value>, msg: &str) -> Self {
81 Self::error(id, -32602, format!("Invalid params: {}", msg))
82 }
83
84 pub fn internal_error(id: Option<Value>, msg: &str) -> Self {
86 Self::error(id, -32603, format!("Internal error: {}", msg))
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct JsonRpcError {
93 pub code: i32,
95 pub message: String,
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub data: Option<Value>,
100}
101
102#[derive(Debug, Clone, Default, Serialize, Deserialize)]
104pub struct McpCapabilities {
105 #[serde(default)]
107 pub tools: ToolsCapability,
108 #[serde(default, skip_serializing_if = "Option::is_none")]
110 pub resources: Option<ResourcesCapability>,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
113 pub prompts: Option<PromptsCapability>,
114}
115
116#[derive(Debug, Clone, Default, Serialize, Deserialize)]
118pub struct ToolsCapability {
119 #[serde(default, rename = "listChanged")]
121 pub list_changed: bool,
122}
123
124#[derive(Debug, Clone, Default, Serialize, Deserialize)]
126pub struct ResourcesCapability {}
127
128#[derive(Debug, Clone, Default, Serialize, Deserialize)]
130pub struct PromptsCapability {}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct McpServerInfo {
135 pub name: String,
137 pub version: String,
139}
140
141impl Default for McpServerInfo {
142 fn default() -> Self {
143 Self {
144 name: "reasonkit-web".to_string(),
145 version: env!("CARGO_PKG_VERSION").to_string(),
146 }
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct McpToolDefinition {
153 pub name: String,
155 pub description: String,
157 #[serde(rename = "inputSchema")]
159 pub input_schema: Value,
160}
161
162#[derive(Debug, Clone, Deserialize)]
164pub struct ToolCallParams {
165 pub name: String,
167 #[serde(default)]
169 pub arguments: Value,
170}
171
172#[derive(Debug, Clone, Serialize)]
174pub struct ToolCallResult {
175 #[serde(rename = "isError", skip_serializing_if = "std::ops::Not::not")]
177 pub is_error: bool,
178 pub content: Vec<ToolContent>,
180}
181
182impl ToolCallResult {
183 pub fn text(text: impl Into<String>) -> Self {
185 Self {
186 is_error: false,
187 content: vec![ToolContent::text(text)],
188 }
189 }
190
191 pub fn image(data: String, mime_type: impl Into<String>) -> Self {
193 Self {
194 is_error: false,
195 content: vec![ToolContent::image(data, mime_type)],
196 }
197 }
198
199 pub fn error(message: impl Into<String>) -> Self {
201 Self {
202 is_error: true,
203 content: vec![ToolContent::text(message)],
204 }
205 }
206
207 pub fn multi(content: Vec<ToolContent>) -> Self {
209 Self {
210 is_error: false,
211 content,
212 }
213 }
214}
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218#[serde(tag = "type")]
219pub enum ToolContent {
220 #[serde(rename = "text")]
222 Text {
223 text: String,
225 },
226 #[serde(rename = "image")]
228 Image {
229 data: String,
231 #[serde(rename = "mimeType")]
233 mime_type: String,
234 },
235 #[serde(rename = "resource")]
237 Resource {
238 uri: String,
240 resource: ResourceContent,
242 },
243}
244
245impl ToolContent {
246 pub fn text(text: impl Into<String>) -> Self {
248 Self::Text { text: text.into() }
249 }
250
251 pub fn image(data: String, mime_type: impl Into<String>) -> Self {
253 Self::Image {
254 data,
255 mime_type: mime_type.into(),
256 }
257 }
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct ResourceContent {
263 #[serde(rename = "mimeType")]
265 pub mime_type: String,
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub text: Option<String>,
269 #[serde(skip_serializing_if = "Option::is_none")]
271 pub blob: Option<String>,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct ServerStatus {
277 pub name: String,
279 pub version: String,
281 pub uptime_secs: u64,
283 pub healthy: bool,
285 pub memory_bytes: Option<u64>,
287 pub active_connections: u32,
289 pub total_requests: u64,
291}
292
293impl ServerStatus {
294 pub fn new(start_time: Instant) -> Self {
296 Self {
297 name: "reasonkit-web".to_string(),
298 version: env!("CARGO_PKG_VERSION").to_string(),
299 uptime_secs: start_time.elapsed().as_secs(),
300 healthy: true,
301 memory_bytes: None,
302 active_connections: 0,
303 total_requests: 0,
304 }
305 }
306
307 pub fn uptime_formatted(&self) -> String {
309 let secs = self.uptime_secs;
310 if secs < 60 {
311 format!("{}s", secs)
312 } else if secs < 3600 {
313 format!("{}m {}s", secs / 60, secs % 60)
314 } else if secs < 86400 {
315 format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
316 } else {
317 format!("{}d {}h", secs / 86400, (secs % 86400) / 3600)
318 }
319 }
320
321 pub fn memory_formatted(&self) -> Option<String> {
323 self.memory_bytes.map(|bytes| {
324 if bytes < 1024 {
325 format!("{} B", bytes)
326 } else if bytes < 1024 * 1024 {
327 format!("{:.1} KB", bytes as f64 / 1024.0)
328 } else if bytes < 1024 * 1024 * 1024 {
329 format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
330 } else {
331 format!("{:.2} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
332 }
333 })
334 }
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct FeedEvent {
340 #[serde(rename = "type")]
342 pub event_type: FeedEventType,
343 pub timestamp: u64,
345 pub data: Value,
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
351#[serde(rename_all = "lowercase")]
352pub enum FeedEventType {
353 Heartbeat,
355 Status,
357 ToolStart,
359 ToolComplete,
361 Error,
363 Shutdown,
365}
366
367impl FeedEvent {
368 pub fn heartbeat() -> Self {
370 Self {
371 event_type: FeedEventType::Heartbeat,
372 timestamp: std::time::SystemTime::now()
373 .duration_since(std::time::UNIX_EPOCH)
374 .unwrap_or(Duration::ZERO)
375 .as_secs(),
376 data: serde_json::json!({"status": "ok"}),
377 }
378 }
379
380 pub fn status(status: &ServerStatus) -> Self {
382 Self {
383 event_type: FeedEventType::Status,
384 timestamp: std::time::SystemTime::now()
385 .duration_since(std::time::UNIX_EPOCH)
386 .unwrap_or(Duration::ZERO)
387 .as_secs(),
388 data: serde_json::to_value(status).unwrap_or(Value::Null),
389 }
390 }
391
392 pub fn tool_start(tool_name: &str) -> Self {
394 Self {
395 event_type: FeedEventType::ToolStart,
396 timestamp: std::time::SystemTime::now()
397 .duration_since(std::time::UNIX_EPOCH)
398 .unwrap_or(Duration::ZERO)
399 .as_secs(),
400 data: serde_json::json!({"tool": tool_name}),
401 }
402 }
403
404 pub fn tool_complete(tool_name: &str, success: bool, duration_ms: u64) -> Self {
406 Self {
407 event_type: FeedEventType::ToolComplete,
408 timestamp: std::time::SystemTime::now()
409 .duration_since(std::time::UNIX_EPOCH)
410 .unwrap_or(Duration::ZERO)
411 .as_secs(),
412 data: serde_json::json!({
413 "tool": tool_name,
414 "success": success,
415 "duration_ms": duration_ms
416 }),
417 }
418 }
419
420 pub fn error(message: &str) -> Self {
422 Self {
423 event_type: FeedEventType::Error,
424 timestamp: std::time::SystemTime::now()
425 .duration_since(std::time::UNIX_EPOCH)
426 .unwrap_or(Duration::ZERO)
427 .as_secs(),
428 data: serde_json::json!({"error": message}),
429 }
430 }
431}
432
433pub struct HeartbeatConfig {
435 pub interval: Duration,
437 pub max_missed: u32,
439}
440
441impl Default for HeartbeatConfig {
442 fn default() -> Self {
443 Self {
444 interval: Duration::from_secs(30),
445 max_missed: 3,
446 }
447 }
448}
449
450impl HeartbeatConfig {
451 pub fn with_interval(interval_secs: u64) -> Self {
453 Self {
454 interval: Duration::from_secs(interval_secs),
455 max_missed: 3,
456 }
457 }
458
459 pub fn interval_ms(&self) -> u64 {
461 self.interval.as_millis() as u64
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
474 fn test_jsonrpc_request_deserialize() {
475 let json = r#"{"jsonrpc":"2.0","method":"test","id":1}"#;
476 let req: JsonRpcRequest = serde_json::from_str(json).unwrap();
477 assert_eq!(req.method, "test");
478 assert_eq!(req.id, Some(serde_json::json!(1)));
479 }
480
481 #[test]
482 fn test_jsonrpc_request_with_params() {
483 let json = r#"{"jsonrpc":"2.0","method":"test","params":{"foo":"bar"},"id":1}"#;
484 let req: JsonRpcRequest = serde_json::from_str(json).unwrap();
485 assert!(req.params.is_some());
486 assert_eq!(req.params.unwrap()["foo"], "bar");
487 }
488
489 #[test]
490 fn test_jsonrpc_request_notification() {
491 let json = r#"{"jsonrpc":"2.0","method":"notify"}"#;
492 let req: JsonRpcRequest = serde_json::from_str(json).unwrap();
493 assert!(req.id.is_none());
494 assert!(req.params.is_none());
495 }
496
497 #[test]
502 fn test_jsonrpc_response_success() {
503 let resp =
504 JsonRpcResponse::success(Some(serde_json::json!(1)), serde_json::json!({"ok": true}));
505 let json = serde_json::to_string(&resp).unwrap();
506 assert!(json.contains("\"result\""));
507 assert!(!json.contains("\"error\""));
508 }
509
510 #[test]
511 fn test_jsonrpc_response_error() {
512 let resp = JsonRpcResponse::error(Some(serde_json::json!(1)), -32600, "Invalid");
513 let json = serde_json::to_string(&resp).unwrap();
514 assert!(json.contains("\"error\""));
515 assert!(json.contains("-32600"));
516 }
517
518 #[test]
519 fn test_jsonrpc_response_parse_error() {
520 let resp = JsonRpcResponse::parse_error();
521 assert!(resp.error.is_some());
522 assert_eq!(resp.error.as_ref().unwrap().code, -32700);
523 }
524
525 #[test]
526 fn test_jsonrpc_response_method_not_found() {
527 let resp = JsonRpcResponse::method_not_found(Some(serde_json::json!(1)), "unknown");
528 assert!(resp.error.is_some());
529 assert_eq!(resp.error.as_ref().unwrap().code, -32601);
530 assert!(resp.error.as_ref().unwrap().message.contains("unknown"));
531 }
532
533 #[test]
534 fn test_jsonrpc_response_invalid_params() {
535 let resp = JsonRpcResponse::invalid_params(Some(serde_json::json!(1)), "missing url");
536 assert!(resp.error.is_some());
537 assert_eq!(resp.error.as_ref().unwrap().code, -32602);
538 }
539
540 #[test]
541 fn test_jsonrpc_response_internal_error() {
542 let resp = JsonRpcResponse::internal_error(Some(serde_json::json!(1)), "boom");
543 assert!(resp.error.is_some());
544 assert_eq!(resp.error.as_ref().unwrap().code, -32603);
545 }
546
547 #[test]
552 fn test_tool_call_result_text() {
553 let result = ToolCallResult::text("Hello, world!");
554 assert!(!result.is_error);
555 assert_eq!(result.content.len(), 1);
556 }
557
558 #[test]
559 fn test_tool_call_result_error() {
560 let result = ToolCallResult::error("Something went wrong");
561 assert!(result.is_error);
562 }
563
564 #[test]
565 fn test_tool_call_result_image() {
566 let result = ToolCallResult::image("base64data".to_string(), "image/png");
567 assert!(!result.is_error);
568 assert_eq!(result.content.len(), 1);
569 }
570
571 #[test]
572 fn test_tool_call_result_multi() {
573 let content = vec![ToolContent::text("Hello"), ToolContent::text("World")];
574 let result = ToolCallResult::multi(content);
575 assert!(!result.is_error);
576 assert_eq!(result.content.len(), 2);
577 }
578
579 #[test]
584 fn test_tool_content_serialize() {
585 let content = ToolContent::text("Hello");
586 let json = serde_json::to_string(&content).unwrap();
587 assert!(json.contains("\"type\":\"text\""));
588 assert!(json.contains("\"text\":\"Hello\""));
589 }
590
591 #[test]
592 fn test_tool_content_image() {
593 let content = ToolContent::image("data".to_string(), "image/png");
594 let json = serde_json::to_string(&content).unwrap();
595 assert!(json.contains("\"type\":\"image\""));
596 assert!(json.contains("\"mimeType\":\"image/png\""));
597 }
598
599 #[test]
604 fn test_mcp_capabilities() {
605 let caps = McpCapabilities::default();
606 assert!(!caps.tools.list_changed);
607 assert!(caps.resources.is_none());
608 }
609
610 #[test]
611 fn test_mcp_server_info_default() {
612 let info = McpServerInfo::default();
613 assert_eq!(info.name, "reasonkit-web");
614 assert!(!info.version.is_empty());
615 }
616
617 #[test]
622 fn test_uptime_calculation() {
623 let start = Instant::now();
624 std::thread::sleep(std::time::Duration::from_millis(10));
625 let status = ServerStatus::new(start);
626
627 let _ = status.uptime_secs;
629 assert!(status.healthy);
630 }
631
632 #[test]
633 fn test_uptime_formatted_seconds() {
634 let mut status = ServerStatus::new(Instant::now());
635 status.uptime_secs = 45;
636 assert_eq!(status.uptime_formatted(), "45s");
637 }
638
639 #[test]
640 fn test_uptime_formatted_minutes() {
641 let mut status = ServerStatus::new(Instant::now());
642 status.uptime_secs = 125; assert_eq!(status.uptime_formatted(), "2m 5s");
644 }
645
646 #[test]
647 fn test_uptime_formatted_hours() {
648 let mut status = ServerStatus::new(Instant::now());
649 status.uptime_secs = 3725; assert_eq!(status.uptime_formatted(), "1h 2m");
651 }
652
653 #[test]
654 fn test_uptime_formatted_days() {
655 let mut status = ServerStatus::new(Instant::now());
656 status.uptime_secs = 90061; assert_eq!(status.uptime_formatted(), "1d 1h");
658 }
659
660 #[test]
661 fn test_memory_usage_format_bytes() {
662 let mut status = ServerStatus::new(Instant::now());
663 status.memory_bytes = Some(512);
664 assert_eq!(status.memory_formatted(), Some("512 B".to_string()));
665 }
666
667 #[test]
668 fn test_memory_usage_format_kilobytes() {
669 let mut status = ServerStatus::new(Instant::now());
670 status.memory_bytes = Some(2048);
671 assert_eq!(status.memory_formatted(), Some("2.0 KB".to_string()));
672 }
673
674 #[test]
675 fn test_memory_usage_format_megabytes() {
676 let mut status = ServerStatus::new(Instant::now());
677 status.memory_bytes = Some(52_428_800); assert_eq!(status.memory_formatted(), Some("50.0 MB".to_string()));
679 }
680
681 #[test]
682 fn test_memory_usage_format_gigabytes() {
683 let mut status = ServerStatus::new(Instant::now());
684 status.memory_bytes = Some(2_147_483_648); assert_eq!(status.memory_formatted(), Some("2.00 GB".to_string()));
686 }
687
688 #[test]
689 fn test_memory_usage_format_none() {
690 let status = ServerStatus::new(Instant::now());
691 assert!(status.memory_formatted().is_none());
692 }
693
694 #[test]
695 fn test_status_response_serialization() {
696 let status = ServerStatus {
697 name: "test-server".to_string(),
698 version: "1.0.0".to_string(),
699 uptime_secs: 3600,
700 healthy: true,
701 memory_bytes: Some(1048576),
702 active_connections: 5,
703 total_requests: 100,
704 };
705
706 let json = serde_json::to_string(&status).unwrap();
707 assert!(json.contains("\"name\":\"test-server\""));
708 assert!(json.contains("\"healthy\":true"));
709 assert!(json.contains("\"uptime_secs\":3600"));
710 }
711
712 #[test]
717 fn test_feed_event_serialization() {
718 let event = FeedEvent::heartbeat();
719 let json = serde_json::to_string(&event).unwrap();
720 assert!(json.contains("\"type\":\"heartbeat\""));
721 assert!(json.contains("\"timestamp\""));
722 }
723
724 #[test]
725 fn test_feed_event_heartbeat() {
726 let event = FeedEvent::heartbeat();
727 assert_eq!(event.event_type, FeedEventType::Heartbeat);
728 assert!(event.timestamp > 0);
729 }
730
731 #[test]
732 fn test_feed_event_tool_start() {
733 let event = FeedEvent::tool_start("web_navigate");
734 assert_eq!(event.event_type, FeedEventType::ToolStart);
735 assert_eq!(event.data["tool"], "web_navigate");
736 }
737
738 #[test]
739 fn test_feed_event_tool_complete() {
740 let event = FeedEvent::tool_complete("web_screenshot", true, 500);
741 assert_eq!(event.event_type, FeedEventType::ToolComplete);
742 assert_eq!(event.data["tool"], "web_screenshot");
743 assert_eq!(event.data["success"], true);
744 assert_eq!(event.data["duration_ms"], 500);
745 }
746
747 #[test]
748 fn test_feed_event_error() {
749 let event = FeedEvent::error("Connection failed");
750 assert_eq!(event.event_type, FeedEventType::Error);
751 assert_eq!(event.data["error"], "Connection failed");
752 }
753
754 #[test]
755 fn test_feed_event_type_serialization() {
756 assert_eq!(
757 serde_json::to_string(&FeedEventType::Heartbeat).unwrap(),
758 "\"heartbeat\""
759 );
760 assert_eq!(
761 serde_json::to_string(&FeedEventType::Status).unwrap(),
762 "\"status\""
763 );
764 assert_eq!(
765 serde_json::to_string(&FeedEventType::ToolStart).unwrap(),
766 "\"toolstart\""
767 );
768 assert_eq!(
769 serde_json::to_string(&FeedEventType::ToolComplete).unwrap(),
770 "\"toolcomplete\""
771 );
772 assert_eq!(
773 serde_json::to_string(&FeedEventType::Error).unwrap(),
774 "\"error\""
775 );
776 assert_eq!(
777 serde_json::to_string(&FeedEventType::Shutdown).unwrap(),
778 "\"shutdown\""
779 );
780 }
781
782 #[test]
787 fn test_heartbeat_interval_default() {
788 let config = HeartbeatConfig::default();
789 assert_eq!(config.interval, Duration::from_secs(30));
790 assert_eq!(config.max_missed, 3);
791 }
792
793 #[test]
794 fn test_heartbeat_interval_custom() {
795 let config = HeartbeatConfig::with_interval(60);
796 assert_eq!(config.interval, Duration::from_secs(60));
797 assert_eq!(config.interval_ms(), 60000);
798 }
799
800 #[test]
801 fn test_heartbeat_interval_ms() {
802 let config = HeartbeatConfig::default();
803 assert_eq!(config.interval_ms(), 30000);
804 }
805
806 #[test]
811 fn test_tool_definition_serialization() {
812 let tool = McpToolDefinition {
813 name: "test_tool".to_string(),
814 description: "A test tool".to_string(),
815 input_schema: serde_json::json!({
816 "type": "object",
817 "properties": {
818 "url": {"type": "string"}
819 }
820 }),
821 };
822
823 let json = serde_json::to_string(&tool).unwrap();
824 assert!(json.contains("\"name\":\"test_tool\""));
825 assert!(json.contains("\"inputSchema\""));
826 }
827
828 #[test]
833 fn test_jsonrpc_response_null_id() {
834 let resp = JsonRpcResponse::success(None, serde_json::json!("ok"));
835 let json = serde_json::to_string(&resp).unwrap();
836 assert!(!json.contains("\"id\""));
838 }
839
840 #[test]
841 fn test_server_status_zero_uptime() {
842 let mut status = ServerStatus::new(Instant::now());
843 status.uptime_secs = 0;
844 assert_eq!(status.uptime_formatted(), "0s");
845 }
846
847 #[test]
848 fn test_feed_event_status() {
849 let status = ServerStatus {
850 name: "test".to_string(),
851 version: "1.0".to_string(),
852 uptime_secs: 100,
853 healthy: true,
854 memory_bytes: None,
855 active_connections: 0,
856 total_requests: 50,
857 };
858 let event = FeedEvent::status(&status);
859 assert_eq!(event.event_type, FeedEventType::Status);
860 }
861}