mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Allow parallel MCP tool calls when annotated readOnly (#23750)
## Summary - Treat MCP tools with `readOnlyHint: true` as parallel-safe even when `supports_parallel_tool_calls` is unset or `false`. - Keep server-level `supports_parallel_tool_calls` as an additive override for non-read-only tools. - Add focused unit coverage for the MCP handler eligibility decision. - Update RMCP integration coverage to keep the serial baseline on a mutable tool, verify read-only concurrency without server opt-in, and preserve the server opt-in concurrency path separately. ## Testing - `just fmt` - `cargo test -p codex-core --lib tools::handlers::mcp::tests::` - `cargo test -p codex-core --test all stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in` - `cargo test -p codex-core --test all stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently` - `cargo test -p codex-rmcp-client`
This commit is contained in:
committed by
GitHub
Unverified
parent
464ab40dfa
commit
c83ba22359
@@ -49,7 +49,16 @@ impl ToolExecutor<ToolInvocation> for McpHandler {
|
||||
}
|
||||
|
||||
fn supports_parallel_tool_calls(&self) -> bool {
|
||||
// Correctly implemented MCP servers should tolerate parallel calls to
|
||||
// tools that advertise themselves as read-only.
|
||||
self.tool_info.supports_parallel_tool_calls
|
||||
|| self
|
||||
.tool_info
|
||||
.tool
|
||||
.annotations
|
||||
.as_ref()
|
||||
.and_then(|annotations| annotations.read_only_hint)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
@@ -443,6 +452,44 @@ mod tests {
|
||||
assert_eq!(mcp_hook_tool_input(" "), json!({}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_read_only_hint_supports_parallel_calls_without_server_opt_in() {
|
||||
let mut read_only_info = tool_info("foo", "mcp__foo__", "read");
|
||||
read_only_info.tool.annotations = Some(rmcp::model::ToolAnnotations::new().read_only(true));
|
||||
|
||||
assert!(
|
||||
McpHandler::new(read_only_info)
|
||||
.expect("MCP tool spec should build")
|
||||
.supports_parallel_tool_calls()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_parallel_calls_require_read_only_hint_or_server_opt_in() {
|
||||
let missing_hint_info = tool_info("foo", "mcp__foo__", "unannotated");
|
||||
assert!(
|
||||
!McpHandler::new(missing_hint_info)
|
||||
.expect("MCP tool spec should build")
|
||||
.supports_parallel_tool_calls()
|
||||
);
|
||||
|
||||
let mut writable_info = tool_info("foo", "mcp__foo__", "write");
|
||||
writable_info.tool.annotations = Some(rmcp::model::ToolAnnotations::new().read_only(false));
|
||||
assert!(
|
||||
!McpHandler::new(writable_info)
|
||||
.expect("MCP tool spec should build")
|
||||
.supports_parallel_tool_calls()
|
||||
);
|
||||
|
||||
let mut server_opt_in_info = tool_info("foo", "mcp__foo__", "server_opt_in");
|
||||
server_opt_in_info.supports_parallel_tool_calls = true;
|
||||
assert!(
|
||||
McpHandler::new(server_opt_in_info)
|
||||
.expect("MCP tool spec should build")
|
||||
.supports_parallel_tool_calls()
|
||||
);
|
||||
}
|
||||
|
||||
fn tool_info(server_name: &str, callable_namespace: &str, tool_name: &str) -> ToolInfo {
|
||||
ToolInfo {
|
||||
server_name: server_name.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user