mirror of
https://github.com/openai/codex.git
synced 2026-11-12 22:42:36 +00:00
Move MCP tool naming mode into manager (#21576)
## Why The `non_prefixed_mcp_tool_names` feature should be applied where MCP tools become model-visible, not by remapping names later in core. Keeping the decision in `McpConnectionManager` construction makes `ToolInfo` the single shaped view that spec building, deferred tool search, routing, and unavailable-tool placeholders can consume directly. This also preserves the existing external behavior while the feature is off, and keeps the feature-on behavior for code mode and hooks explicit at the manager boundary. ## What Changed - Add `McpToolNameMode` to `codex-mcp` and flow it through `McpConfig` into `McpConnectionManager::new`. - Normalize MCP `ToolInfo` names in the manager using either legacy-prefixed namespaces or non-prefixed namespaces; the legacy path adds `mcp__` without restoring the old trailing namespace suffix. - Remove the core-side MCP name remapping path so specs, tool search, session resolution, and unavailable-tool placeholder construction use the manager-provided `ToolName` values directly. - Keep code mode flattening on the `__` namespace separator. - Preserve hook compatibility by giving non-prefixed MCP hook names legacy `mcp__...` matcher aliases. - Add/adjust integration and unit coverage for non-prefixed code-mode behavior, hook matching with the feature on and off, and manager-level legacy prefixing. ## Testing - `cargo test -p codex-mcp --lib` - `cargo test -p codex-core --lib tools::spec::tests -- --nocapture` - `cargo test -p codex-core --lib mcp_tools -- --nocapture` - `cargo test -p codex-core --lib mcp_tool_exposure -- --nocapture` - `cargo test -p codex-core --test all mcp_tool -- --nocapture` - `cargo test -p codex-core --test all search_tool -- --nocapture` - `cargo test -p codex-core --test all hooks_mcp -- --nocapture` - `cargo test -p codex-core --test all code_mode_uses_non_prefixed_mcp_tool_names_when_feature_enabled -- --nocapture` - `cargo test -p codex-tools` - `cargo test -p codex-features`
This commit is contained in:
1 parent
b637fd26aa
commit
ff7513cd83
30 files changed
+611
-146
No files matched your search
@@ -200,7 +200,11 @@ async fn run_code_mode_turn_with_rmcp_model(
|
||||
code: &str,
|
||||
model: &'static str,
|
||||
) -> Result<(TestCodex, ResponseMock)> {
|
||||
run_code_mode_turn_with_rmcp_config(server, prompt, code, model, /*code_mode_only*/ false).await
|
||||
run_code_mode_turn_with_rmcp_config(
|
||||
server, prompt, code, model, /*code_mode_only*/ false,
|
||||
/*non_prefixed_mcp_tool_names*/ false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn run_code_mode_turn_with_rmcp_mode(
|
||||
@@ -209,8 +213,15 @@ async fn run_code_mode_turn_with_rmcp_mode(
|
||||
code: &str,
|
||||
code_mode_only: bool,
|
||||
) -> Result<(TestCodex, ResponseMock)> {
|
||||
run_code_mode_turn_with_rmcp_config(server, prompt, code, "test-gpt-5.1-codex", code_mode_only)
|
||||
.await
|
||||
run_code_mode_turn_with_rmcp_config(
|
||||
server,
|
||||
prompt,
|
||||
code,
|
||||
"test-gpt-5.1-codex",
|
||||
code_mode_only,
|
||||
/*non_prefixed_mcp_tool_names*/ false,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn run_code_mode_turn_with_rmcp_config(
|
||||
@@ -219,6 +230,7 @@ async fn run_code_mode_turn_with_rmcp_config(
|
||||
code: &str,
|
||||
model: &'static str,
|
||||
code_mode_only: bool,
|
||||
non_prefixed_mcp_tool_names: bool,
|
||||
) -> Result<(TestCodex, ResponseMock)> {
|
||||
let rmcp_test_server_bin = stdio_server_bin()?;
|
||||
let mut builder = test_codex().with_model(model).with_config(move |config| {
|
||||
@@ -227,6 +239,9 @@ async fn run_code_mode_turn_with_rmcp_config(
|
||||
} else {
|
||||
config.features.enable(Feature::CodeMode)
|
||||
};
|
||||
if non_prefixed_mcp_tool_names {
|
||||
let _ = config.features.enable(Feature::NonPrefixedMcpToolNames);
|
||||
}
|
||||
|
||||
let mut servers = config.mcp_servers.get().clone();
|
||||
servers.insert(
|
||||
@@ -2598,6 +2613,50 @@ contentLength=0"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn code_mode_uses_non_prefixed_mcp_tool_names_when_feature_enabled() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let code = r#"
|
||||
const result = await tools.rmcp__echo({ message: "ping" });
|
||||
text(JSON.stringify({
|
||||
hasNonPrefixedEcho: typeof tools.rmcp__echo === "function",
|
||||
hasPrefixedEcho: typeof tools.mcp__rmcp__echo === "function",
|
||||
echo: result.structuredContent?.echo ?? "missing",
|
||||
}));
|
||||
"#;
|
||||
|
||||
let (_test, second_mock) = run_code_mode_turn_with_rmcp_config(
|
||||
&server,
|
||||
"use exec to inspect non-prefixed MCP names",
|
||||
code,
|
||||
"test-gpt-5.1-codex",
|
||||
/*code_mode_only*/ false,
|
||||
/*non_prefixed_mcp_tool_names*/ true,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let req = second_mock.single_request();
|
||||
let (output, success) = custom_tool_output_body_and_success(&req, "call-1");
|
||||
assert_ne!(
|
||||
success,
|
||||
Some(false),
|
||||
"exec non-prefixed rmcp access failed unexpectedly: {output}"
|
||||
);
|
||||
let parsed: Value = serde_json::from_str(&output)?;
|
||||
assert_eq!(
|
||||
parsed,
|
||||
serde_json::json!({
|
||||
"hasNonPrefixedEcho": true,
|
||||
"hasPrefixedEcho": false,
|
||||
"echo": "ECHOING: ping",
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn code_mode_exposes_namespaced_mcp_tools_on_global_tools_object() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
@@ -9,6 +9,7 @@ use codex_config::types::AppToolApproval;
|
||||
use codex_config::types::McpServerConfig;
|
||||
use codex_config::types::McpServerTransportConfig;
|
||||
use codex_core::config::Config;
|
||||
use codex_features::Feature;
|
||||
use core_test_support::hooks::trust_discovered_hooks;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
@@ -26,11 +27,18 @@ use serde_json::Value;
|
||||
use serde_json::json;
|
||||
|
||||
const RMCP_SERVER: &str = "rmcp";
|
||||
const RMCP_NAMESPACE: &str = "mcp__rmcp__";
|
||||
const RMCP_PREFIXED_NAMESPACE: &str = "mcp__rmcp";
|
||||
const RMCP_UNPREFIXED_NAMESPACE: &str = "rmcp";
|
||||
const RMCP_ECHO_TOOL_NAME: &str = "mcp__rmcp__echo";
|
||||
const RMCP_HOOK_MATCHER: &str = "mcp__rmcp__.*";
|
||||
const RMCP_HOOK_MATCHER: &str = RMCP_ECHO_TOOL_NAME;
|
||||
const RMCP_ECHO_MESSAGE: &str = "hook e2e ping";
|
||||
|
||||
fn enable_mcp_tool_name_features(config: &mut Config, prefix_mcp_tool_names: bool) {
|
||||
if !prefix_mcp_tool_names {
|
||||
let _ = config.features.enable(Feature::NonPrefixedMcpToolNames);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pre_tool_use_hook(home: &Path, reason: &str) -> Result<()> {
|
||||
let script_path = home.join("pre_tool_use_hook.py");
|
||||
let log_path = home.join("pre_tool_use_hook_log.jsonl");
|
||||
@@ -207,13 +215,35 @@ fn enable_hooks_and_rmcp_server(
|
||||
config: &mut Config,
|
||||
rmcp_test_server_bin: String,
|
||||
approval_mode: AppToolApproval,
|
||||
prefix_mcp_tool_names: bool,
|
||||
) {
|
||||
trust_discovered_hooks(config);
|
||||
enable_mcp_tool_name_features(config, prefix_mcp_tool_names);
|
||||
insert_rmcp_test_server(config, rmcp_test_server_bin, approval_mode);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn pre_tool_use_blocks_mcp_tool_before_execution() -> Result<()> {
|
||||
async fn pre_tool_use_blocks_mcp_tool_before_execution_with_legacy_prefixed_names() -> Result<()> {
|
||||
pre_tool_use_blocks_mcp_tool_before_execution(
|
||||
/*prefix_mcp_tool_names*/ true,
|
||||
RMCP_PREFIXED_NAMESPACE,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn pre_tool_use_blocks_mcp_tool_before_execution_with_non_prefixed_names() -> Result<()> {
|
||||
pre_tool_use_blocks_mcp_tool_before_execution(
|
||||
/*prefix_mcp_tool_names*/ false,
|
||||
RMCP_UNPREFIXED_NAMESPACE,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn pre_tool_use_blocks_mcp_tool_before_execution(
|
||||
prefix_mcp_tool_names: bool,
|
||||
mcp_namespace: &'static str,
|
||||
) -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
@@ -224,7 +254,7 @@ async fn pre_tool_use_blocks_mcp_tool_before_execution() -> Result<()> {
|
||||
vec![
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_function_call_with_namespace(call_id, RMCP_NAMESPACE, "echo", &arguments),
|
||||
ev_function_call_with_namespace(call_id, mcp_namespace, "echo", &arguments),
|
||||
ev_completed("resp-1"),
|
||||
]),
|
||||
sse(vec![
|
||||
@@ -245,7 +275,12 @@ async fn pre_tool_use_blocks_mcp_tool_before_execution() -> Result<()> {
|
||||
}
|
||||
})
|
||||
.with_config(move |config| {
|
||||
enable_hooks_and_rmcp_server(config, rmcp_test_server_bin, AppToolApproval::Approve);
|
||||
enable_hooks_and_rmcp_server(
|
||||
config,
|
||||
rmcp_test_server_bin,
|
||||
AppToolApproval::Approve,
|
||||
prefix_mcp_tool_names,
|
||||
);
|
||||
})
|
||||
.build(&server)
|
||||
.await?;
|
||||
@@ -256,10 +291,9 @@ async fn pre_tool_use_blocks_mcp_tool_before_execution() -> Result<()> {
|
||||
let requests = responses.requests();
|
||||
assert_eq!(requests.len(), 2);
|
||||
let output_item = requests[1].function_call_output(call_id);
|
||||
let output = output_item
|
||||
.get("output")
|
||||
.and_then(Value::as_str)
|
||||
.expect("blocked MCP tool output string");
|
||||
let Some(output) = output_item.get("output").and_then(Value::as_str) else {
|
||||
panic!("blocked MCP tool output should be a string: {output_item:?}");
|
||||
};
|
||||
assert!(
|
||||
output.contains(&format!(
|
||||
"Tool call blocked by PreToolUse hook: {block_reason}. Tool: {RMCP_ECHO_TOOL_NAME}"
|
||||
@@ -283,9 +317,12 @@ async fn pre_tool_use_blocks_mcp_tool_before_execution() -> Result<()> {
|
||||
"tool_input": { "message": RMCP_ECHO_MESSAGE },
|
||||
})
|
||||
);
|
||||
let transcript_path = hook_inputs[0]["transcript_path"]
|
||||
.as_str()
|
||||
.expect("pre tool use hook transcript_path");
|
||||
let Some(transcript_path) = hook_inputs[0]["transcript_path"].as_str() else {
|
||||
panic!(
|
||||
"pre tool use hook transcript_path should be a string: {:?}",
|
||||
hook_inputs[0]["transcript_path"]
|
||||
);
|
||||
};
|
||||
assert!(
|
||||
Path::new(transcript_path).exists(),
|
||||
"pre tool use hook transcript_path should be materialized on disk",
|
||||
@@ -306,7 +343,7 @@ async fn pre_tool_use_rewrites_mcp_tool_before_execution() -> Result<()> {
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_function_call_with_namespace(call_id, RMCP_NAMESPACE, "echo", &arguments),
|
||||
ev_function_call_with_namespace(call_id, RMCP_PREFIXED_NAMESPACE, "echo", &arguments),
|
||||
ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
@@ -329,7 +366,12 @@ async fn pre_tool_use_rewrites_mcp_tool_before_execution() -> Result<()> {
|
||||
}
|
||||
})
|
||||
.with_config(move |config| {
|
||||
enable_hooks_and_rmcp_server(config, rmcp_test_server_bin, AppToolApproval::Approve);
|
||||
enable_hooks_and_rmcp_server(
|
||||
config,
|
||||
rmcp_test_server_bin,
|
||||
AppToolApproval::Approve,
|
||||
/*prefix_mcp_tool_names*/ true,
|
||||
);
|
||||
})
|
||||
.build(&server)
|
||||
.await?;
|
||||
@@ -339,10 +381,9 @@ async fn pre_tool_use_rewrites_mcp_tool_before_execution() -> Result<()> {
|
||||
|
||||
let final_request = final_mock.single_request();
|
||||
let output_item = final_request.function_call_output(call_id);
|
||||
let output = output_item
|
||||
.get("output")
|
||||
.and_then(Value::as_str)
|
||||
.expect("MCP tool output string");
|
||||
let Some(output) = output_item.get("output").and_then(Value::as_str) else {
|
||||
panic!("MCP tool output should be a string: {output_item:?}");
|
||||
};
|
||||
assert!(
|
||||
output.contains(&format!("ECHOING: {rewritten_message}")),
|
||||
"MCP tool should execute the rewritten input",
|
||||
@@ -365,7 +406,29 @@ async fn pre_tool_use_rewrites_mcp_tool_before_execution() -> Result<()> {
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn post_tool_use_records_mcp_tool_payload_and_context() -> Result<()> {
|
||||
async fn post_tool_use_records_mcp_tool_payload_and_context_with_legacy_prefixed_names()
|
||||
-> Result<()> {
|
||||
post_tool_use_records_mcp_tool_payload_and_context(
|
||||
/*prefix_mcp_tool_names*/ true,
|
||||
RMCP_PREFIXED_NAMESPACE,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn post_tool_use_records_mcp_tool_payload_and_context_with_non_prefixed_names() -> Result<()>
|
||||
{
|
||||
post_tool_use_records_mcp_tool_payload_and_context(
|
||||
/*prefix_mcp_tool_names*/ false,
|
||||
RMCP_UNPREFIXED_NAMESPACE,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn post_tool_use_records_mcp_tool_payload_and_context(
|
||||
prefix_mcp_tool_names: bool,
|
||||
mcp_namespace: &'static str,
|
||||
) -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
@@ -375,7 +438,7 @@ async fn post_tool_use_records_mcp_tool_payload_and_context() -> Result<()> {
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_response_created("resp-1"),
|
||||
ev_function_call_with_namespace(call_id, RMCP_NAMESPACE, "echo", &arguments),
|
||||
ev_function_call_with_namespace(call_id, mcp_namespace, "echo", &arguments),
|
||||
ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
@@ -399,7 +462,12 @@ async fn post_tool_use_records_mcp_tool_payload_and_context() -> Result<()> {
|
||||
}
|
||||
})
|
||||
.with_config(move |config| {
|
||||
enable_hooks_and_rmcp_server(config, rmcp_test_server_bin, AppToolApproval::Approve);
|
||||
enable_hooks_and_rmcp_server(
|
||||
config,
|
||||
rmcp_test_server_bin,
|
||||
AppToolApproval::Approve,
|
||||
prefix_mcp_tool_names,
|
||||
);
|
||||
})
|
||||
.build(&server)
|
||||
.await?;
|
||||
@@ -415,10 +483,9 @@ async fn post_tool_use_records_mcp_tool_payload_and_context() -> Result<()> {
|
||||
"follow-up request should include MCP post tool use additional context",
|
||||
);
|
||||
let output_item = final_request.function_call_output(call_id);
|
||||
let output = output_item
|
||||
.get("output")
|
||||
.and_then(Value::as_str)
|
||||
.expect("MCP tool output string");
|
||||
let Some(output) = output_item.get("output").and_then(Value::as_str) else {
|
||||
panic!("MCP tool output should be a string: {output_item:?}");
|
||||
};
|
||||
assert!(
|
||||
output.contains(&format!("ECHOING: {RMCP_ECHO_MESSAGE}")),
|
||||
"MCP tool output should still reach the model",
|
||||
@@ -449,9 +516,12 @@ async fn post_tool_use_records_mcp_tool_payload_and_context() -> Result<()> {
|
||||
},
|
||||
})
|
||||
);
|
||||
let transcript_path = hook_inputs[0]["transcript_path"]
|
||||
.as_str()
|
||||
.expect("post tool use hook transcript_path");
|
||||
let Some(transcript_path) = hook_inputs[0]["transcript_path"].as_str() else {
|
||||
panic!(
|
||||
"post tool use hook transcript_path should be a string: {:?}",
|
||||
hook_inputs[0]["transcript_path"]
|
||||
);
|
||||
};
|
||||
assert!(
|
||||
Path::new(transcript_path).exists(),
|
||||
"post tool use hook transcript_path should be materialized on disk",
|
||||
|
||||
@@ -340,7 +340,7 @@ async fn explicit_plugin_mentions_inject_plugin_guidance() -> Result<()> {
|
||||
"expected plugin app tools to become visible for this turn: {request_tools:?}"
|
||||
);
|
||||
let echo_tool = request
|
||||
.tool_by_name("mcp__sample__", "echo")
|
||||
.tool_by_name("mcp__sample", "echo")
|
||||
.expect("plugin MCP tool should be present");
|
||||
let echo_description = echo_tool
|
||||
.get("description")
|
||||
|
||||
@@ -382,7 +382,7 @@ async fn call_cwd_tool(
|
||||
server_name: &str,
|
||||
call_id: &str,
|
||||
) -> anyhow::Result<Value> {
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
mount_sse_once(
|
||||
server,
|
||||
responses::sse(vec![
|
||||
@@ -467,7 +467,7 @@ async fn stdio_server_round_trip() -> anyhow::Result<()> {
|
||||
|
||||
let call_id = "call-123";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
|
||||
let call_mock = mount_sse_once(
|
||||
&server,
|
||||
@@ -723,7 +723,7 @@ async fn stdio_mcp_tool_call_includes_sandbox_state_meta() -> anyhow::Result<()>
|
||||
|
||||
let call_id = "sandbox-meta-call";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
|
||||
let call_mock = mount_sse_once(
|
||||
&server,
|
||||
@@ -817,7 +817,7 @@ async fn stdio_mcp_parallel_tool_calls_default_false_runs_serially() -> anyhow::
|
||||
let first_call_id = "sync-serial-1";
|
||||
let second_call_id = "sync-serial-2";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
let args = json!({ "sleep_after_ms": 100 }).to_string();
|
||||
|
||||
mount_sse_once(
|
||||
@@ -930,7 +930,7 @@ async fn stdio_mcp_read_only_tool_calls_run_concurrently_without_server_opt_in()
|
||||
let first_call_id = "sync-read-only-1";
|
||||
let second_call_id = "sync-read-only-2";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
// The stdio MCP test server holds each sync call at this barrier until both
|
||||
// calls arrive. A serial scheduler times out inside the server instead of
|
||||
// returning the structured `{ "result": "ok" }` result asserted below.
|
||||
@@ -1025,7 +1025,7 @@ async fn stdio_mcp_parallel_tool_calls_opt_in_runs_concurrently() -> anyhow::Res
|
||||
let first_call_id = "sync-1";
|
||||
let second_call_id = "sync-2";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
let args = json!({
|
||||
"sleep_after_ms": 100,
|
||||
"barrier": {
|
||||
@@ -1110,7 +1110,7 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> {
|
||||
|
||||
let call_id = "img-1";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
|
||||
// First stream: model decides to call the image tool.
|
||||
mount_sse_once(
|
||||
@@ -1247,7 +1247,7 @@ async fn stdio_image_responses_preserve_original_detail_metadata() -> anyhow::Re
|
||||
|
||||
let call_id = "img-original-detail-1";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
@@ -1333,7 +1333,7 @@ async fn stdio_image_responses_are_sanitized_for_text_only_model() -> anyhow::Re
|
||||
|
||||
let call_id = "img-text-only-1";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
let text_only_model_slug = "rmcp-text-only-model";
|
||||
|
||||
let models_mock = mount_models_once(
|
||||
@@ -1480,7 +1480,7 @@ async fn stdio_server_propagates_whitelisted_env_vars() -> anyhow::Result<()> {
|
||||
|
||||
let call_id = "call-1234";
|
||||
let server_name = "rmcp_whitelist";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
@@ -1594,7 +1594,7 @@ async fn stdio_server_propagates_explicit_local_env_var_source() -> anyhow::Resu
|
||||
let server = responses::start_mock_server().await;
|
||||
let call_id = "call-local-source";
|
||||
let server_name = "rmcp_local_source";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
let env_name = "MCP_TEST_LOCAL_SOURCE";
|
||||
let expected_env_value = "propagated-explicit-local-source";
|
||||
|
||||
@@ -1687,7 +1687,7 @@ async fn remote_stdio_env_var_source_does_not_copy_local_env() -> anyhow::Result
|
||||
let server = responses::start_mock_server().await;
|
||||
let call_id = "call-remote-source";
|
||||
let server_name = "rmcp_remote_source";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
let env_name = "MCP_TEST_REMOTE_SOURCE_ONLY";
|
||||
|
||||
mount_sse_once(
|
||||
@@ -1859,7 +1859,7 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> {
|
||||
|
||||
let call_id = "call-456";
|
||||
let server_name = "rmcp_http";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
@@ -2026,7 +2026,7 @@ async fn streamable_http_with_oauth_round_trip_impl() -> anyhow::Result<()> {
|
||||
|
||||
let call_id = "call-789";
|
||||
let server_name = "rmcp_http_oauth";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
|
||||
@@ -1059,13 +1059,13 @@ async fn tool_search_indexes_only_enabled_non_app_mcp_tools() -> Result<()> {
|
||||
"non-app MCP tools should be hidden before search in large-search mode: {first_request_tools:?}"
|
||||
);
|
||||
assert!(
|
||||
!first_request_tools.iter().any(|name| name == "mcp__rmcp__"),
|
||||
!first_request_tools.iter().any(|name| name == "mcp__rmcp"),
|
||||
"non-app MCP namespace should be hidden before search in large-search mode: {first_request_tools:?}"
|
||||
);
|
||||
|
||||
let echo_tools = tool_search_output_tools(&requests[1], echo_call_id);
|
||||
let echo_output = json!({ "tools": echo_tools });
|
||||
let rmcp_echo_tool = namespace_child_tool(&echo_output, "mcp__rmcp__", "echo")
|
||||
let rmcp_echo_tool = namespace_child_tool(&echo_output, "mcp__rmcp", "echo")
|
||||
.expect("tool_search should return rmcp echo as a namespace child tool");
|
||||
assert_eq!(
|
||||
rmcp_echo_tool.get("type").and_then(Value::as_str),
|
||||
@@ -1075,7 +1075,7 @@ async fn tool_search_indexes_only_enabled_non_app_mcp_tools() -> Result<()> {
|
||||
let image_tools = tool_search_output_tools(&requests[1], image_call_id);
|
||||
let found_rmcp_image_tool = image_tools
|
||||
.iter()
|
||||
.filter(|tool| tool.get("name").and_then(Value::as_str) == Some("mcp__rmcp__"))
|
||||
.filter(|tool| tool.get("name").and_then(Value::as_str) == Some("mcp__rmcp"))
|
||||
.flat_map(|namespace| namespace.get("tools").and_then(Value::as_array))
|
||||
.flatten()
|
||||
.any(|tool| tool.get("name").and_then(Value::as_str).is_some());
|
||||
@@ -1111,7 +1111,7 @@ async fn tool_search_surfaced_mcp_tool_errors_are_returned_to_model() -> Result<
|
||||
]),
|
||||
sse(vec![
|
||||
ev_response_created("resp-2"),
|
||||
ev_function_call_with_namespace(tool_call_id, "mcp__rmcp__", "echo", "{}"),
|
||||
ev_function_call_with_namespace(tool_call_id, "mcp__rmcp", "echo", "{}"),
|
||||
ev_completed("resp-2"),
|
||||
]),
|
||||
sse(vec![
|
||||
@@ -1213,12 +1213,12 @@ async fn tool_search_surfaced_mcp_tool_errors_are_returned_to_model() -> Result<
|
||||
"first request should advertise tool_search: {first_request_tools:?}"
|
||||
);
|
||||
assert!(
|
||||
!first_request_tools.iter().any(|name| name == "mcp__rmcp__"),
|
||||
!first_request_tools.iter().any(|name| name == "mcp__rmcp"),
|
||||
"deferred rmcp namespace should not be directly exposed before search: {first_request_tools:?}"
|
||||
);
|
||||
|
||||
assert!(
|
||||
tool_search_output_has_namespace_child(&requests[1], search_call_id, "mcp__rmcp__", "echo"),
|
||||
tool_search_output_has_namespace_child(&requests[1], search_call_id, "mcp__rmcp", "echo"),
|
||||
"tool_search should return the rmcp echo tool"
|
||||
);
|
||||
|
||||
@@ -1324,7 +1324,7 @@ async fn tool_search_uses_non_app_mcp_server_instructions_as_namespace_descripti
|
||||
let tools = tool_search_output_tools(&requests[1], search_call_id);
|
||||
let rmcp_namespace = tools
|
||||
.iter()
|
||||
.find(|tool| tool.get("name").and_then(Value::as_str) == Some("mcp__rmcp__"))
|
||||
.find(|tool| tool.get("name").and_then(Value::as_str) == Some("mcp__rmcp"))
|
||||
.expect("tool_search should return the rmcp namespace");
|
||||
assert_eq!(
|
||||
rmcp_namespace.get("description").and_then(Value::as_str),
|
||||
|
||||
@@ -330,7 +330,7 @@ async fn mcp_call_marks_thread_memory_mode_polluted_when_configured() -> Result<
|
||||
let server = start_mock_server().await;
|
||||
let call_id = "call-123";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![
|
||||
|
||||
@@ -343,7 +343,7 @@ async fn mcp_tool_call_output_exceeds_limit_truncated_for_model() -> Result<()>
|
||||
|
||||
let call_id = "rmcp-truncated";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
|
||||
// Build a very large message to exceed 10KiB once serialized.
|
||||
let large_msg = "long-message-with-newlines-".repeat(6000);
|
||||
@@ -446,7 +446,7 @@ async fn mcp_image_output_preserves_image_and_no_text_summary() -> Result<()> {
|
||||
|
||||
let call_id = "rmcp-image-no-trunc";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
@@ -733,7 +733,7 @@ async fn mcp_tool_call_output_not_truncated_with_custom_limit() -> Result<()> {
|
||||
|
||||
let call_id = "rmcp-untruncated";
|
||||
let server_name = "rmcp";
|
||||
let namespace = format!("mcp__{server_name}__");
|
||||
let namespace = format!("mcp__{server_name}");
|
||||
let large_msg = "a".repeat(80_000);
|
||||
let args_json = serde_json::json!({ "message": large_msg });
|
||||
|
||||
|
||||
Reference in new issue
Block a user