1use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::io::{BufRead, BufReader, Write};
6
7use crate::error::{EngramError, Result};
8
9#[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#[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#[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 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 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 pub fn from_error(id: Option<Value>, err: EngramError) -> Self {
67 Self::error(id, err.code(), err.to_string())
68 }
69}
70
71pub struct McpServer<H>
73where
74 H: McpHandler,
75{
76 handler: H,
77}
78
79pub trait McpHandler: Send + Sync {
81 fn handle_request(&self, request: McpRequest) -> McpResponse;
82}
83
84impl<T: McpHandler> McpHandler for std::sync::Arc<T> {
85 fn handle_request(&self, request: McpRequest) -> McpResponse {
86 (**self).handle_request(request)
87 }
88}
89
90impl<H: McpHandler> McpServer<H> {
91 pub fn new(handler: H) -> Self {
93 Self { handler }
94 }
95
96 pub fn run(&self) -> Result<()> {
98 let stdin = std::io::stdin();
99 let stdout = std::io::stdout();
100 let mut reader = BufReader::new(stdin.lock());
101 let mut writer = stdout.lock();
102
103 let mut line = String::new();
104
105 loop {
106 line.clear();
107 match reader.read_line(&mut line) {
108 Ok(0) => break, Ok(_) => {
110 let trimmed = line.trim();
111 if trimmed.is_empty() {
112 continue;
113 }
114
115 match serde_json::from_str::<McpRequest>(trimmed) {
116 Ok(request) => {
117 let is_notification = request.id.is_none();
120 let response = self.handler.handle_request(request);
121 if is_notification {
122 continue;
123 }
124 let response_json = serde_json::to_string(&response)?;
125 writeln!(writer, "{}", response_json)?;
126 writer.flush()?;
127 }
128 Err(e) => {
129 let response =
130 McpResponse::error(None, -32700, format!("Parse error: {}", e));
131 let response_json = serde_json::to_string(&response)?;
132 writeln!(writer, "{}", response_json)?;
133 writer.flush()?;
134 }
135 }
136 }
137 Err(e) => {
138 tracing::error!("Error reading stdin: {}", e);
139 break;
140 }
141 }
142 }
143
144 Ok(())
145 }
146}
147
148pub mod methods {
150 pub const INITIALIZE: &str = "initialize";
151 pub const INITIALIZED: &str = "notifications/initialized";
152 pub const LIST_TOOLS: &str = "tools/list";
153 pub const CALL_TOOL: &str = "tools/call";
154 pub const LIST_RESOURCES: &str = "resources/list";
155 pub const READ_RESOURCE: &str = "resources/read";
156 pub const LIST_PROMPTS: &str = "prompts/list";
157 pub const GET_PROMPT: &str = "prompts/get";
158}
159
160pub const MCP_PROTOCOL_VERSION: &str = "2025-11-25";
162pub const MCP_PROTOCOL_VERSION_LEGACY: &str = "2024-11-05";
164
165#[derive(Debug, Clone, Serialize, Deserialize, Default)]
169pub struct ToolAnnotations {
170 #[serde(rename = "readOnlyHint", skip_serializing_if = "Option::is_none")]
172 pub read_only_hint: Option<bool>,
173
174 #[serde(rename = "destructiveHint", skip_serializing_if = "Option::is_none")]
176 pub destructive_hint: Option<bool>,
177
178 #[serde(rename = "idempotentHint", skip_serializing_if = "Option::is_none")]
181 pub idempotent_hint: Option<bool>,
182
183 #[serde(rename = "openWorldHint", skip_serializing_if = "Option::is_none")]
186 pub open_world_hint: Option<bool>,
187}
188
189impl ToolAnnotations {
190 pub const fn read_only() -> Self {
192 Self {
193 read_only_hint: Some(true),
194 destructive_hint: None,
195 idempotent_hint: None,
196 open_world_hint: None,
197 }
198 }
199
200 pub const fn destructive() -> Self {
202 Self {
203 read_only_hint: None,
204 destructive_hint: Some(true),
205 idempotent_hint: None,
206 open_world_hint: None,
207 }
208 }
209
210 pub const fn idempotent() -> Self {
212 Self {
213 read_only_hint: None,
214 destructive_hint: None,
215 idempotent_hint: Some(true),
216 open_world_hint: None,
217 }
218 }
219
220 pub const fn mutating() -> Self {
222 Self {
223 read_only_hint: None,
224 destructive_hint: None,
225 idempotent_hint: None,
226 open_world_hint: None,
227 }
228 }
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
233pub struct ToolDefinition {
234 pub name: String,
235 pub description: String,
236 #[serde(rename = "inputSchema")]
237 pub input_schema: Value,
238 #[serde(skip_serializing_if = "Option::is_none")]
239 pub annotations: Option<ToolAnnotations>,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct InitializeResult {
245 #[serde(rename = "protocolVersion")]
246 pub protocol_version: String,
247 pub capabilities: ServerCapabilities,
248 #[serde(rename = "serverInfo")]
249 pub server_info: ServerInfo,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct ServerCapabilities {
255 #[serde(skip_serializing_if = "Option::is_none")]
256 pub tools: Option<ToolsCapability>,
257 #[serde(skip_serializing_if = "Option::is_none")]
258 pub resources: Option<ResourceCapabilities>,
259 #[serde(skip_serializing_if = "Option::is_none")]
260 pub prompts: Option<PromptCapabilities>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct ToolsCapability {
265 #[serde(rename = "listChanged")]
266 pub list_changed: bool,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct ResourceCapabilities {
272 pub subscribe: bool,
273 #[serde(rename = "listChanged")]
274 pub list_changed: bool,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct PromptCapabilities {
280 #[serde(rename = "listChanged")]
281 pub list_changed: bool,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct ServerInfo {
287 pub name: String,
288 pub version: String,
289}
290
291impl Default for InitializeResult {
292 fn default() -> Self {
293 Self {
294 protocol_version: MCP_PROTOCOL_VERSION.to_string(),
295 capabilities: ServerCapabilities {
296 tools: Some(ToolsCapability {
297 list_changed: false,
298 }),
299 resources: Some(ResourceCapabilities {
300 subscribe: false,
301 list_changed: false,
302 }),
303 prompts: Some(PromptCapabilities {
304 list_changed: false,
305 }),
306 },
307 server_info: ServerInfo {
308 name: "engram".to_string(),
309 version: env!("CARGO_PKG_VERSION").to_string(),
310 },
311 }
312 }
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct ToolCallResult {
318 pub content: Vec<ToolContent>,
319 #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
320 pub is_error: Option<bool>,
321}
322
323#[derive(Debug, Clone, Serialize, Deserialize)]
324#[serde(tag = "type")]
325pub enum ToolContent {
326 #[serde(rename = "text")]
327 Text { text: String },
328 #[serde(rename = "image")]
329 Image { data: String, mime_type: String },
330 #[serde(rename = "resource")]
331 Resource { resource: ResourceContent },
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct ResourceContent {
336 pub uri: String,
337 pub text: Option<String>,
338 pub blob: Option<String>,
339 #[serde(rename = "mimeType")]
340 pub mime_type: Option<String>,
341}
342
343impl ToolCallResult {
344 pub fn text(text: impl Into<String>) -> Self {
346 Self {
347 content: vec![ToolContent::Text { text: text.into() }],
348 is_error: None,
349 }
350 }
351
352 pub fn json(value: &impl Serialize) -> Self {
354 let text = serde_json::to_string_pretty(value).unwrap_or_default();
355 Self::text(text)
356 }
357
358 pub fn error(message: impl Into<String>) -> Self {
360 Self {
361 content: vec![ToolContent::Text {
362 text: message.into(),
363 }],
364 is_error: Some(true),
365 }
366 }
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
371pub struct ResourceDefinition {
372 pub uri: String,
373 pub name: String,
374 #[serde(skip_serializing_if = "Option::is_none")]
375 pub description: Option<String>,
376 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
377 pub mime_type: Option<String>,
378}
379
380#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct ResourceTemplate {
383 #[serde(rename = "uriTemplate")]
384 pub uri_template: String,
385 pub name: String,
386 #[serde(skip_serializing_if = "Option::is_none")]
387 pub description: Option<String>,
388 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
389 pub mime_type: Option<String>,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct PromptDefinition {
395 pub name: String,
396 #[serde(skip_serializing_if = "Option::is_none")]
397 pub description: Option<String>,
398 #[serde(skip_serializing_if = "Option::is_none")]
399 pub arguments: Option<Vec<PromptArgument>>,
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct PromptArgument {
405 pub name: String,
406 #[serde(skip_serializing_if = "Option::is_none")]
407 pub description: Option<String>,
408 #[serde(skip_serializing_if = "Option::is_none")]
409 pub required: Option<bool>,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
414pub struct PromptMessage {
415 pub role: String,
417 pub content: PromptContent,
418}
419
420#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct PromptContent {
423 #[serde(rename = "type")]
424 pub content_type: String,
425 pub text: String,
426}