codex_memory/mcp_server/
mod.rs1pub mod handlers;
3pub mod tools;
4pub mod transport;
5
6pub use handlers::MCPHandlers;
8
9use crate::config::Config;
10use crate::error::Result;
11use crate::storage::Storage;
12use std::sync::Arc;
13use tokio::io::{AsyncReadExt, AsyncWriteExt};
14use tracing::{error, info};
15
16pub struct MCPServer {
18 _config: Config,
19 handlers: Arc<MCPHandlers>,
20}
21
22impl MCPServer {
23 pub fn new(config: Config, storage: Arc<Storage>) -> Self {
25 let handlers = Arc::new(MCPHandlers::new(storage));
26 Self {
27 _config: config,
28 handlers,
29 }
30 }
31
32 pub async fn run_stdio(&self) -> Result<()> {
34 info!("MCP server running in stdio mode");
35
36 let stdin = tokio::io::stdin();
37 let stdout = tokio::io::stdout();
38 let mut stdin = stdin;
39 let mut stdout = stdout;
40
41 let mut buffer = String::new();
42 let mut temp_buf = [0u8; 8192]; loop {
45 match stdin.read(&mut temp_buf).await {
47 Ok(0) => {
48 info!("Received EOF, shutting down MCP server");
49 break; }
51 Ok(n) => {
52 let chunk = String::from_utf8_lossy(&temp_buf[..n]);
54 info!(
55 "Read {} bytes: {:?}",
56 n,
57 &chunk[..std::cmp::min(100, chunk.len())]
58 );
59 buffer.push_str(&chunk);
60
61 while let Some(json_end) = self.find_complete_json(&buffer) {
63 let json_str = buffer[..json_end].trim().to_string();
64 buffer.drain(..json_end);
65
66 if !json_str.is_empty() {
67 info!("Processing JSON request ({} chars)", json_str.len());
68 let response = self.handle_request(&json_str).await;
69 stdout.write_all(response.as_bytes()).await?;
70 stdout.write_all(b"\n").await?;
71 stdout.flush().await?;
72 }
73 }
74 }
75 Err(e) => {
76 error!("Error reading input: {}", e);
77 break;
78 }
79 }
80 }
81
82 info!("MCP server shutting down gracefully");
83 Ok(())
85 }
86
87 fn find_complete_json(&self, buffer: &str) -> Option<usize> {
89 let mut brace_count = 0;
90 let mut in_string = false;
91 let mut escape_next = false;
92 let mut start_found = false;
93
94 for (i, ch) in buffer.char_indices() {
95 if escape_next {
96 escape_next = false;
97 continue;
98 }
99
100 match ch {
101 '\\' if in_string => escape_next = true,
102 '"' => in_string = !in_string,
103 '{' if !in_string => {
104 brace_count += 1;
105 start_found = true;
106 }
107 '}' if !in_string => {
108 brace_count -= 1;
109 if start_found && brace_count == 0 {
110 return Some(i + 1);
111 }
112 }
113 _ => {}
114 }
115 }
116
117 None
118 }
119
120 async fn handle_request(&self, request: &str) -> String {
121 let request: serde_json::Value = match serde_json::from_str(request) {
122 Ok(v) => v,
123 Err(e) => {
124 return format!(
125 r#"{{"jsonrpc":"2.0","error":{{"code":-32700,"message":"Parse error: {}"}}}}"#,
126 e
127 );
128 }
129 };
130
131 let method = request["method"].as_str().unwrap_or("");
132 let params = request.get("params").cloned().unwrap_or_default();
133 let id = request.get("id").cloned();
134
135 let result = match method {
136 "initialize" => Ok(serde_json::json!({
137 "protocolVersion": "2024-11-05",
138 "capabilities": {
139 "tools": {
140 "list": tools::MCPTools::get_tools_list()
141 }
142 },
143 "serverInfo": {
144 "name": "codex-store",
145 "version": env!("CARGO_PKG_VERSION")
146 }
147 })),
148 "tools/list" => Ok(serde_json::json!({
149 "tools": tools::MCPTools::get_tools_list()
150 })),
151 "tools/call" => {
152 let tool_name = params["name"].as_str().unwrap_or("");
153 let tool_params = params.get("arguments").cloned().unwrap_or_default();
154 self.handlers.handle_tool_call(tool_name, tool_params).await
155 }
156 _ => Err(crate::error::Error::Other(format!(
157 "Unknown method: {}",
158 method
159 ))),
160 };
161
162 match result {
163 Ok(value) => {
164 if let Some(id) = id {
165 format!(r#"{{"jsonrpc":"2.0","id":{},"result":{}}}"#, id, value)
166 } else {
167 format!(r#"{{"jsonrpc":"2.0","result":{}}}"#, value)
168 }
169 }
170 Err(e) => {
171 if let Some(id) = id {
172 format!(
173 r#"{{"jsonrpc":"2.0","id":{},"error":{{"code":-32000,"message":"{}"}}}}"#,
174 id, e
175 )
176 } else {
177 format!(
178 r#"{{"jsonrpc":"2.0","error":{{"code":-32000,"message":"{}"}}}}"#,
179 e
180 )
181 }
182 }
183 }
184 }
185}