Skip to main content

engram/mcp/
protocol.rs

1//! MCP JSON-RPC protocol implementation
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::io::{BufRead, BufReader, Write};
6
7use crate::error::{EngramError, Result};
8
9/// MCP JSON-RPC request
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct McpRequest {
12    pub jsonrpc: String,
13    pub id: Option<Value>,
14    pub method: String,
15    #[serde(default)]
16    pub params: Value,
17}
18
19/// MCP JSON-RPC response
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct McpResponse {
22    pub jsonrpc: String,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub id: Option<Value>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub result: Option<Value>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub error: Option<McpError>,
29}
30
31/// MCP error object
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct McpError {
34    pub code: i64,
35    pub message: String,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub data: Option<Value>,
38}
39
40impl McpResponse {
41    /// Create a success response
42    pub fn success(id: Option<Value>, result: Value) -> Self {
43        Self {
44            jsonrpc: "2.0".to_string(),
45            id,
46            result: Some(result),
47            error: None,
48        }
49    }
50
51    /// Create an error response
52    pub fn error(id: Option<Value>, code: i64, message: String) -> Self {
53        Self {
54            jsonrpc: "2.0".to_string(),
55            id,
56            result: None,
57            error: Some(McpError {
58                code,
59                message,
60                data: None,
61            }),
62        }
63    }
64
65    /// Create error from EngramError
66    pub fn from_error(id: Option<Value>, err: EngramError) -> Self {
67        Self::error(id, err.code(), err.to_string())
68    }
69}
70
71/// MCP Server handling stdio communication
72pub struct McpServer<H>
73where
74    H: McpHandler,
75{
76    handler: H,
77}
78
79/// Trait for handling MCP requests
80pub trait McpHandler: Send + Sync {
81    fn handle_request(&self, request: McpRequest) -> McpResponse;
82}
83
84impl<H: McpHandler> McpServer<H> {
85    /// Create a new MCP server
86    pub fn new(handler: H) -> Self {
87        Self { handler }
88    }
89
90    /// Run the server, reading from stdin and writing to stdout
91    pub fn run(&self) -> Result<()> {
92        let stdin = std::io::stdin();
93        let stdout = std::io::stdout();
94        let mut reader = BufReader::new(stdin.lock());
95        let mut writer = stdout.lock();
96
97        let mut line = String::new();
98
99        loop {
100            line.clear();
101            match reader.read_line(&mut line) {
102                Ok(0) => break, // EOF
103                Ok(_) => {
104                    let trimmed = line.trim();
105                    if trimmed.is_empty() {
106                        continue;
107                    }
108
109                    match serde_json::from_str::<McpRequest>(trimmed) {
110                        Ok(request) => {
111                            let response = self.handler.handle_request(request);
112                            let response_json = serde_json::to_string(&response)?;
113                            writeln!(writer, "{}", response_json)?;
114                            writer.flush()?;
115                        }
116                        Err(e) => {
117                            let response =
118                                McpResponse::error(None, -32700, format!("Parse error: {}", e));
119                            let response_json = serde_json::to_string(&response)?;
120                            writeln!(writer, "{}", response_json)?;
121                            writer.flush()?;
122                        }
123                    }
124                }
125                Err(e) => {
126                    tracing::error!("Error reading stdin: {}", e);
127                    break;
128                }
129            }
130        }
131
132        Ok(())
133    }
134}
135
136/// Standard MCP methods
137pub mod methods {
138    pub const INITIALIZE: &str = "initialize";
139    pub const INITIALIZED: &str = "notifications/initialized";
140    pub const LIST_TOOLS: &str = "tools/list";
141    pub const CALL_TOOL: &str = "tools/call";
142    pub const LIST_RESOURCES: &str = "resources/list";
143    pub const READ_RESOURCE: &str = "resources/read";
144}
145
146/// MCP tool definition
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct ToolDefinition {
149    pub name: String,
150    pub description: String,
151    #[serde(rename = "inputSchema")]
152    pub input_schema: Value,
153}
154
155/// MCP initialize result
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct InitializeResult {
158    #[serde(rename = "protocolVersion")]
159    pub protocol_version: String,
160    pub capabilities: ServerCapabilities,
161    #[serde(rename = "serverInfo")]
162    pub server_info: ServerInfo,
163}
164
165/// Server capabilities
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct ServerCapabilities {
168    pub tools: Option<ToolsCapability>,
169    pub resources: Option<ResourcesCapability>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct ToolsCapability {
174    #[serde(rename = "listChanged")]
175    pub list_changed: bool,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct ResourcesCapability {
180    pub subscribe: bool,
181    #[serde(rename = "listChanged")]
182    pub list_changed: bool,
183}
184
185/// Server info
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ServerInfo {
188    pub name: String,
189    pub version: String,
190}
191
192impl Default for InitializeResult {
193    fn default() -> Self {
194        Self {
195            protocol_version: "2024-11-05".to_string(),
196            capabilities: ServerCapabilities {
197                tools: Some(ToolsCapability {
198                    list_changed: false,
199                }),
200                resources: Some(ResourcesCapability {
201                    subscribe: false,
202                    list_changed: false,
203                }),
204            },
205            server_info: ServerInfo {
206                name: "engram".to_string(),
207                version: env!("CARGO_PKG_VERSION").to_string(),
208            },
209        }
210    }
211}
212
213/// Tool call result
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct ToolCallResult {
216    pub content: Vec<ToolContent>,
217    #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
218    pub is_error: Option<bool>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
222#[serde(tag = "type")]
223pub enum ToolContent {
224    #[serde(rename = "text")]
225    Text { text: String },
226    #[serde(rename = "image")]
227    Image { data: String, mime_type: String },
228    #[serde(rename = "resource")]
229    Resource { resource: ResourceContent },
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct ResourceContent {
234    pub uri: String,
235    pub text: Option<String>,
236    pub blob: Option<String>,
237    #[serde(rename = "mimeType")]
238    pub mime_type: Option<String>,
239}
240
241impl ToolCallResult {
242    /// Create a text result
243    pub fn text(text: impl Into<String>) -> Self {
244        Self {
245            content: vec![ToolContent::Text { text: text.into() }],
246            is_error: None,
247        }
248    }
249
250    /// Create a JSON result
251    pub fn json(value: &impl Serialize) -> Self {
252        let text = serde_json::to_string_pretty(value).unwrap_or_default();
253        Self::text(text)
254    }
255
256    /// Create an error result
257    pub fn error(message: impl Into<String>) -> Self {
258        Self {
259            content: vec![ToolContent::Text {
260                text: message.into(),
261            }],
262            is_error: Some(true),
263        }
264    }
265}