Skip to main content

codemem_mcp/
types.rs

1//! Protocol types for the MCP JSON-RPC server.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6// ── JSON-RPC Types ──────────────────────────────────────────────────────────
7
8/// JSON-RPC 2.0 request.
9#[derive(Debug, Deserialize)]
10pub struct JsonRpcRequest {
11    pub jsonrpc: String,
12    /// Absent for notifications (no response expected).
13    pub id: Option<Value>,
14    pub method: String,
15    #[serde(default)]
16    pub params: Option<Value>,
17}
18
19/// JSON-RPC 2.0 response.
20#[derive(Debug, Serialize)]
21pub struct JsonRpcResponse {
22    pub jsonrpc: String,
23    pub id: Value,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub result: Option<Value>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub error: Option<JsonRpcError>,
28}
29
30/// JSON-RPC 2.0 error object.
31#[derive(Debug, Serialize)]
32pub struct JsonRpcError {
33    pub code: i64,
34    pub message: String,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub data: Option<Value>,
37}
38
39impl JsonRpcResponse {
40    pub(crate) fn success(id: Value, result: Value) -> Self {
41        Self {
42            jsonrpc: "2.0".to_string(),
43            id,
44            result: Some(result),
45            error: None,
46        }
47    }
48
49    pub(crate) fn error(id: Value, code: i64, message: impl Into<String>) -> Self {
50        Self {
51            jsonrpc: "2.0".to_string(),
52            id,
53            result: None,
54            error: Some(JsonRpcError {
55                code,
56                message: message.into(),
57                data: None,
58            }),
59        }
60    }
61}
62
63// ── Tool Result Types ───────────────────────────────────────────────────────
64
65/// MCP tool result (content array + isError flag).
66#[derive(Debug, Serialize)]
67pub struct ToolResult {
68    pub content: Vec<ToolContent>,
69    #[serde(rename = "isError")]
70    pub is_error: bool,
71}
72
73/// A single content block in a tool result.
74#[derive(Debug, Serialize)]
75pub struct ToolContent {
76    #[serde(rename = "type")]
77    pub content_type: String,
78    pub text: String,
79}
80
81impl ToolResult {
82    pub(crate) fn text(msg: impl Into<String>) -> Self {
83        Self {
84            content: vec![ToolContent {
85                content_type: "text".to_string(),
86                text: msg.into(),
87            }],
88            is_error: false,
89        }
90    }
91
92    pub(crate) fn tool_error(msg: impl Into<String>) -> Self {
93        Self {
94            content: vec![ToolContent {
95                content_type: "text".to_string(),
96                text: msg.into(),
97            }],
98            is_error: true,
99        }
100    }
101}
102
103// ── Index Cache ─────────────────────────────────────────────────────────────
104
105/// Cached code-index results for structural queries.
106pub(crate) struct IndexCache {
107    pub(crate) symbols: Vec<codemem_index::Symbol>,
108    pub(crate) root_path: String,
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn parse_json_rpc_request() {
117        let json = r#"{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}"#;
118        let req: JsonRpcRequest = serde_json::from_str(json).unwrap();
119        assert_eq!(req.method, "initialize");
120        assert!(req.id.is_some());
121    }
122
123    #[test]
124    fn parse_notification_no_id() {
125        let json = r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#;
126        let req: JsonRpcRequest = serde_json::from_str(json).unwrap();
127        assert!(req.id.is_none());
128    }
129
130    #[test]
131    fn tool_result_serialization() {
132        let result = ToolResult::text("hello");
133        let json = serde_json::to_value(&result).unwrap();
134        assert_eq!(json["content"][0]["type"], "text");
135        assert_eq!(json["content"][0]["text"], "hello");
136        assert_eq!(json["isError"], false);
137    }
138
139    #[test]
140    fn tool_error_serialization() {
141        let result = ToolResult::tool_error("something went wrong");
142        let json = serde_json::to_value(&result).unwrap();
143        assert_eq!(json["isError"], true);
144    }
145}