// mcp-server — MCP server for Claude Code integration // // Speaks JSON-RPC over stdio. Exposes memory tools and channel // operations. Replaces the Python MCP bridge entirely. // // Protocol: https://modelcontextprotocol.io/specification use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::io::{self, BufRead, Write}; // ── JSON-RPC types ────────────────────────────────────────────── #[derive(Deserialize)] struct Request { jsonrpc: String, method: String, #[serde(default)] params: Value, id: Value, } #[derive(Serialize)] struct Response { jsonrpc: String, result: Value, id: Value, } #[derive(Serialize)] struct ErrorResponse { jsonrpc: String, error: Value, id: Value, } fn respond(id: Value, result: Value) { let resp = Response { jsonrpc: "2.0".into(), result, id }; let json = serde_json::to_string(&resp).unwrap(); let mut stdout = io::stdout().lock(); let _ = writeln!(stdout, "{json}"); let _ = stdout.flush(); } fn respond_error(id: Value, code: i64, message: &str) { let resp = ErrorResponse { jsonrpc: "2.0".into(), error: json!({ "code": code, "message": message }), id, }; let json = serde_json::to_string(&resp).unwrap(); let mut stdout = io::stdout().lock(); let _ = writeln!(stdout, "{json}"); let _ = stdout.flush(); } fn notify(method: &str, params: Value) { let json = serde_json::to_string(&json!({ "jsonrpc": "2.0", "method": method, "params": params, })).unwrap(); let mut stdout = io::stdout().lock(); let _ = writeln!(stdout, "{json}"); let _ = stdout.flush(); } // ── Tool definitions ──────────────────────────────────────────── fn tool_definitions() -> Vec { poc_memory::agent::tools::tools().into_iter() .map(|t| json!({ "name": t.def.function.name, "description": t.def.function.description, "inputSchema": t.def.function.parameters, })) .collect() } // ── Tool dispatch ─────────────────────────────────────────────── fn dispatch_tool(name: &str, args: &Value) -> Result { let tools = poc_memory::agent::tools::tools(); let tool = tools.iter().find(|t| t.def.function.name == name); let Some(tool) = tool else { return Err(format!("unknown tool: {name}")); }; // Run async handler on a blocking runtime let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .map_err(|e| e.to_string())?; let local = tokio::task::LocalSet::new(); local.block_on(&rt, (tool.handler)(None, args.clone())) .map_err(|e| e.to_string()) } // ── Main loop ─────────────────────────────────────────────────── fn main() { let stdin = io::stdin(); let reader = stdin.lock(); for line in reader.lines() { let line = match line { Ok(l) if !l.is_empty() => l, _ => continue, }; let req: Request = match serde_json::from_str(&line) { Ok(r) => r, Err(_) => continue, }; match req.method.as_str() { "initialize" => { respond(req.id, json!({ "protocolVersion": "2024-11-05", "capabilities": { "tools": {} }, "serverInfo": { "name": "consciousness", "version": "0.4.0" } })); } "notifications/initialized" => { // Client ack — no response needed } "tools/list" => { let tools = tool_definitions(); respond(req.id, json!({ "tools": tools })); } "tools/call" => { let name = req.params.get("name") .and_then(|v| v.as_str()) .unwrap_or(""); let args = req.params.get("arguments") .cloned() .unwrap_or(json!({})); match dispatch_tool(name, &args) { Ok(text) => { respond(req.id, json!({ "content": [{"type": "text", "text": text}] })); } Err(e) => { respond(req.id, json!({ "content": [{"type": "text", "text": e}], "isError": true })); } } } _ => { respond_error(req.id, -32601, &format!("unknown method: {}", req.method)); } } } }