feat: initial work by Codex to create Codex MCP tool call

This commit is contained in:
Michael Bolin
2025-05-04 17:05:25 -07:00
parent 5d924d44cf
commit 0c56a7826a
11 changed files with 584 additions and 44 deletions
+76 -29
View File
@@ -1,6 +1,7 @@
//! Very small proof-of-concept request router for the MCP prototype server.
use mcp_types::CallToolRequestParams;
use mcp_types::CallToolResult;
use mcp_types::CallToolResultContent;
use mcp_types::ClientRequest;
use mcp_types::JSONRPCBatchRequest;
@@ -17,10 +18,16 @@ use mcp_types::RequestId;
use mcp_types::ServerCapabilitiesTools;
use mcp_types::ServerNotification;
use mcp_types::TextContent;
use mcp_types::Tool;
use mcp_types::ToolInputSchema;
use mcp_types::JSONRPC_VERSION;
use serde_json::json;
use tokio::task;
// Import types from codex-core.
use codex_core::config::Config as CodexConfig;
// Config object accepted by the `codex` tool-call.
use crate::codex_tool_config::create_tool_for_codex_tool_call_param;
use crate::codex_tool_config::CodexToolCallParam;
use tokio::sync::mpsc;
pub(crate) struct MessageProcessor {
@@ -303,21 +310,7 @@ impl MessageProcessor {
) {
tracing::trace!("tools/list -> {params:?}");
let result = ListToolsResult {
tools: vec![Tool {
name: "echo".to_string(),
input_schema: ToolInputSchema {
r#type: "object".to_string(),
properties: Some(json!({
"input": {
"type": "string",
"description": "The input to echo back"
}
})),
required: Some(vec!["input".to_string()]),
},
description: Some("Echoes the request back".to_string()),
annotations: None,
}],
tools: vec![create_tool_for_codex_tool_call_param()],
next_cursor: None,
};
@@ -331,26 +324,80 @@ impl MessageProcessor {
) {
tracing::info!("tools/call -> params: {:?}", params);
let CallToolRequestParams { name, arguments } = params;
match name.as_str() {
"echo" => {
let result = mcp_types::CallToolResult {
// We only support the "codex" tool for now.
if name != "codex" {
// Tool not found – return error result so the LLM can react.
let result = CallToolResult {
content: vec![CallToolResultContent::TextContent(TextContent {
r#type: "text".to_string(),
text: format!("Unknown tool '{name}'"),
annotations: None,
})],
is_error: Some(true),
};
self.send_response::<mcp_types::CallToolRequest>(id, result);
return;
}
let (initial_prompt, config): (String, CodexConfig) = match arguments {
Some(json_val) => match serde_json::from_value::<CodexToolCallParam>(json_val) {
Ok(tool_cfg) => match tool_cfg.into_config() {
Ok(cfg) => cfg,
Err(e) => {
let result = CallToolResult {
content: vec![CallToolResultContent::TextContent(TextContent {
r#type: "text".to_owned(),
text: format!(
"Failed to load Codex configuration from overrides: {e}"
),
annotations: None,
})],
is_error: Some(true),
};
self.send_response::<mcp_types::CallToolRequest>(id, result);
return;
}
},
Err(e) => {
let result = CallToolResult {
content: vec![CallToolResultContent::TextContent(TextContent {
r#type: "text".to_owned(),
text: format!("Failed to parse configuration for Codex tool: {e}"),
annotations: None,
})],
is_error: Some(true),
};
self.send_response::<mcp_types::CallToolRequest>(id, result);
return;
}
},
None => {
let result = CallToolResult {
content: vec![CallToolResultContent::TextContent(TextContent {
r#type: "text".to_string(),
text: format!("Echo: {arguments:?}"),
text:
"Missing arguments for codex tool-call; the `prompt` field is required."
.to_string(),
annotations: None,
})],
is_error: None,
};
self.send_response::<mcp_types::CallToolRequest>(id, result);
}
_ => {
let result = mcp_types::CallToolResult {
content: vec![],
is_error: Some(true),
};
self.send_response::<mcp_types::CallToolRequest>(id, result);
return;
}
}
};
// Clone outgoing sender to move into async task.
let outgoing = self.outgoing.clone();
// Spawn an async task to handle the Codex session so that we do not
// block the synchronous message-processing loop.
task::spawn(async move {
// Run the Codex session and stream events back to the client.
crate::codex_tool_runner::run_codex_tool_session(id, initial_prompt, config, outgoing)
.await;
});
}
fn handle_set_level(