[mcp-server] Add reply tool call (#1643)

## Summary
Adds a new mcp tool call, `codex-reply`, so we can continue existing
sessions. This is a first draft and does not yet support sessions from
previous processes.

## Testing
- [x] tested with mcp client
This commit is contained in:
Dylan
2025-07-21 21:01:56 -07:00
committed by GitHub
parent d49d802b06
commit 18b2b30841
14 changed files with 301 additions and 47 deletions

View File

@@ -2,6 +2,7 @@
//! Tokio task. Separated from `message_processor.rs` to keep that file small
//! and to make future feature-growth easier to manage.
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
@@ -27,7 +28,9 @@ use mcp_types::TextContent;
use serde::Deserialize;
use serde::Serialize;
use serde_json::json;
use tokio::sync::Mutex;
use tracing::error;
use uuid::Uuid;
use crate::outgoing_message::OutgoingMessageSender;
@@ -42,8 +45,9 @@ pub async fn run_codex_tool_session(
initial_prompt: String,
config: CodexConfig,
outgoing: Arc<OutgoingMessageSender>,
session_map: Arc<Mutex<HashMap<Uuid, Arc<Codex>>>>,
) {
let (codex, first_event, _ctrl_c) = match init_codex(config).await {
let (codex, first_event, _ctrl_c, session_id) = match init_codex(config).await {
Ok(res) => res,
Err(e) => {
let result = CallToolResult {
@@ -61,6 +65,11 @@ pub async fn run_codex_tool_session(
};
let codex = Arc::new(codex);
// update the session map so we can retrieve the session in a reply, and then drop it, since
// we no longer need it for this function
session_map.lock().await.insert(session_id, codex.clone());
drop(session_map);
// Send initial SessionConfigured event.
outgoing.send_event_as_notification(&first_event).await;
@@ -85,6 +94,37 @@ pub async fn run_codex_tool_session(
tracing::error!("Failed to submit initial prompt: {e}");
}
run_codex_tool_session_inner(codex, outgoing, id).await;
}
pub async fn run_codex_tool_session_reply(
codex: Arc<Codex>,
outgoing: Arc<OutgoingMessageSender>,
request_id: RequestId,
prompt: String,
) {
if let Err(e) = codex
.submit(Op::UserInput {
items: vec![InputItem::Text { text: prompt }],
})
.await
{
tracing::error!("Failed to submit user input: {e}");
}
run_codex_tool_session_inner(codex, outgoing, request_id).await;
}
async fn run_codex_tool_session_inner(
codex: Arc<Codex>,
outgoing: Arc<OutgoingMessageSender>,
request_id: RequestId,
) {
let sub_id = match &request_id {
RequestId::String(s) => s.clone(),
RequestId::Integer(n) => n.to_string(),
};
// Stream events until the task needs to pause for user interaction or
// completes.
loop {
@@ -128,7 +168,7 @@ pub async fn run_codex_tool_session(
outgoing
.send_error(
id.clone(),
request_id.clone(),
JSONRPCErrorError {
code: INVALID_PARAMS_ERROR_CODE,
message,
@@ -168,7 +208,9 @@ pub async fn run_codex_tool_session(
is_error: None,
structured_content: None,
};
outgoing.send_response(id.clone(), result.into()).await;
outgoing
.send_response(request_id.clone(), result.into())
.await;
// Continue, don't break so the session continues.
continue;
}
@@ -186,7 +228,9 @@ pub async fn run_codex_tool_session(
is_error: None,
structured_content: None,
};
outgoing.send_response(id.clone(), result.into()).await;
outgoing
.send_response(request_id.clone(), result.into())
.await;
break;
}
EventMsg::SessionConfigured(_) => {
@@ -234,7 +278,9 @@ pub async fn run_codex_tool_session(
// structured way.
structured_content: None,
};
outgoing.send_response(id.clone(), result.into()).await;
outgoing
.send_response(request_id.clone(), result.into())
.await;
break;
}
}