1use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::sync::Arc;
7use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
8use tokio::process::{Child, ChildStdin, ChildStdout, Command};
9use tokio::sync::Mutex;
10
11pub struct McpClient {
13 process: Arc<Mutex<Child>>,
14 stdin: Arc<Mutex<ChildStdin>>,
15 stdout: Arc<Mutex<BufReader<ChildStdout>>>,
16 request_id: Arc<Mutex<u64>>,
17}
18
19#[derive(Debug, Serialize)]
20struct JsonRpcRequest {
21 jsonrpc: String,
22 id: u64,
23 method: String,
24 params: Option<Value>,
25}
26
27#[derive(Debug, Deserialize)]
28struct JsonRpcResponse {
29 #[allow(dead_code)]
30 jsonrpc: String,
31 #[allow(dead_code)]
32 id: u64,
33 #[serde(default)]
34 result: Option<Value>,
35 #[serde(default)]
36 error: Option<JsonRpcError>,
37}
38
39#[derive(Debug, Deserialize)]
40struct JsonRpcError {
41 code: i32,
42 message: String,
43 #[serde(default)]
44 #[allow(dead_code)]
45 data: Option<Value>,
46}
47
48impl McpClient {
49 pub async fn spawn(command: &str, args: &[&str]) -> anyhow::Result<Self> {
51 let mut child = Command::new(command)
52 .args(args)
53 .kill_on_drop(true)
54 .stdin(std::process::Stdio::piped())
55 .stdout(std::process::Stdio::piped())
56 .stderr(std::process::Stdio::inherit())
57 .spawn()?;
58
59 let stdin = child
60 .stdin
61 .take()
62 .ok_or_else(|| anyhow::anyhow!("Failed to capture stdin"))?;
63 let stdout = child
64 .stdout
65 .take()
66 .ok_or_else(|| anyhow::anyhow!("Failed to capture stdout"))?;
67
68 Ok(Self {
69 process: Arc::new(Mutex::new(child)),
70 stdin: Arc::new(Mutex::new(stdin)),
71 stdout: Arc::new(Mutex::new(BufReader::new(stdout))),
72 request_id: Arc::new(Mutex::new(0)),
73 })
74 }
75
76 async fn next_request_id(&self) -> u64 {
77 let mut counter = self.request_id.lock().await;
78 *counter += 1;
79 *counter
80 }
81
82 async fn send_request(&self, request: &JsonRpcRequest) -> anyhow::Result<()> {
83 let request_json = serde_json::to_string(request)?;
84 let mut stdin = self.stdin.lock().await;
85 stdin.write_all(request_json.as_bytes()).await?;
86 stdin.write_all(b"\n").await?;
87 stdin.flush().await?;
88 Ok(())
89 }
90
91 async fn read_response(&self) -> anyhow::Result<JsonRpcResponse> {
92 let mut stdout = self.stdout.lock().await;
93 let mut line = String::new();
94 stdout.read_line(&mut line).await?;
95 let response: JsonRpcResponse = serde_json::from_str(&line)?;
96 Ok(response)
97 }
98
99 pub async fn call(&self, method: &str, params: Option<Value>) -> anyhow::Result<Value> {
101 let id = self.next_request_id().await;
102
103 let request = JsonRpcRequest {
104 jsonrpc: "2.0".to_string(),
105 id,
106 method: method.to_string(),
107 params,
108 };
109
110 self.send_request(&request).await?;
111 let response = self.read_response().await?;
112
113 if let Some(error) = response.error {
114 anyhow::bail!("MCP error {}: {}", error.code, error.message);
115 }
116
117 response
118 .result
119 .ok_or_else(|| anyhow::anyhow!("No result in MCP response"))
120 }
121
122 pub async fn initialize(&self, client_info: Value) -> anyhow::Result<Value> {
124 self.call(
125 "initialize",
126 Some(serde_json::json!({
127 "protocolVersion": "2024-11-05",
128 "capabilities": {},
129 "clientInfo": client_info
130 })),
131 )
132 .await
133 }
134
135 pub async fn list_tools(&self) -> anyhow::Result<Vec<McpTool>> {
137 let result = self.call("tools/list", None).await?;
138
139 let tools = result
140 .get("tools")
141 .and_then(|t| t.as_array())
142 .ok_or_else(|| anyhow::anyhow!("Invalid tools/list response"))?;
143
144 tools
145 .iter()
146 .map(|t| serde_json::from_value(t.clone()))
147 .collect::<Result<Vec<_>, _>>()
148 .map_err(|e| anyhow::anyhow!("Failed to parse tool: {}", e))
149 }
150
151 pub async fn call_tool(&self, name: &str, arguments: Value) -> anyhow::Result<Value> {
153 self.call(
154 "tools/call",
155 Some(serde_json::json!({
156 "name": name,
157 "arguments": arguments
158 })),
159 )
160 .await
161 }
162
163 pub async fn shutdown(&self) -> anyhow::Result<()> {
165 self.call("shutdown", None).await?;
166
167 let mut process = self.process.lock().await;
168 process.kill().await?;
169
170 Ok(())
171 }
172}
173
174#[derive(Debug, Serialize, Deserialize)]
175pub struct McpTool {
176 pub name: String,
177 pub description: Option<String>,
178 pub input_schema: Value,
179}
180
181pub struct CodexClient {
183 mcp: McpClient,
184}
185
186impl CodexClient {
187 pub async fn spawn() -> anyhow::Result<Self> {
189 let mcp = McpClient::spawn("codex", &["mcp-server"]).await?;
190
191 mcp.initialize(serde_json::json!({
193 "name": "apollo",
194 "version": "0.1.0"
195 }))
196 .await?;
197
198 Ok(Self { mcp })
199 }
200
201 pub async fn run_session(&self, goal: &str, repo_path: &str) -> anyhow::Result<Value> {
203 self.mcp
204 .call_tool(
205 "run",
206 serde_json::json!({
207 "goal": goal,
208 "path": repo_path
209 }),
210 )
211 .await
212 }
213
214 pub async fn list_tools(&self) -> anyhow::Result<Vec<McpTool>> {
216 self.mcp.list_tools().await
217 }
218
219 pub async fn shutdown(&self) -> anyhow::Result<()> {
221 self.mcp.shutdown().await
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 #[tokio::test]
230 #[ignore] async fn test_codex_spawn() {
232 let client = CodexClient::spawn().await;
233 assert!(client.is_ok());
234 }
235}