mirror of
https://github.com/openai/codex.git
synced 2026-09-13 11:47:17 +00:00
Plumb MCP turn metadata through _meta
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -119,7 +119,8 @@ pub(crate) async fn handle_mcp_tool_call(
|
||||
);
|
||||
return CallToolResult::from_result(result);
|
||||
}
|
||||
let request_meta = build_mcp_tool_call_request_meta(&server, metadata.as_ref());
|
||||
let request_meta =
|
||||
build_mcp_tool_call_request_meta(turn_context.as_ref(), &server, metadata.as_ref());
|
||||
|
||||
let tool_call_begin_event = EventMsg::McpToolCallBegin(McpToolCallBeginEvent {
|
||||
call_id: call_id.clone(),
|
||||
@@ -390,18 +391,30 @@ pub(crate) struct McpToolApprovalMetadata {
|
||||
const MCP_TOOL_CODEX_APPS_META_KEY: &str = "_codex_apps";
|
||||
|
||||
fn build_mcp_tool_call_request_meta(
|
||||
turn_context: &TurnContext,
|
||||
server: &str,
|
||||
metadata: Option<&McpToolApprovalMetadata>,
|
||||
) -> Option<serde_json::Value> {
|
||||
if server != CODEX_APPS_MCP_SERVER_NAME {
|
||||
return None;
|
||||
let mut request_meta = serde_json::Map::new();
|
||||
|
||||
if let Some(turn_metadata) = turn_context.turn_metadata_state.current_meta_value() {
|
||||
request_meta.insert(
|
||||
crate::X_CODEX_TURN_METADATA_HEADER.to_string(),
|
||||
turn_metadata,
|
||||
);
|
||||
}
|
||||
|
||||
let codex_apps_meta = metadata.and_then(|metadata| metadata.codex_apps_meta.as_ref())?;
|
||||
if server == CODEX_APPS_MCP_SERVER_NAME
|
||||
&& let Some(codex_apps_meta) =
|
||||
metadata.and_then(|metadata| metadata.codex_apps_meta.clone())
|
||||
{
|
||||
request_meta.insert(
|
||||
MCP_TOOL_CODEX_APPS_META_KEY.to_string(),
|
||||
serde_json::Value::Object(codex_apps_meta),
|
||||
);
|
||||
}
|
||||
|
||||
Some(serde_json::json!({
|
||||
MCP_TOOL_CODEX_APPS_META_KEY: codex_apps_meta,
|
||||
}))
|
||||
(!request_meta.is_empty()).then_some(serde_json::Value::Object(request_meta))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -439,8 +439,28 @@ fn sanitize_mcp_tool_result_for_model_preserves_image_when_supported() {
|
||||
assert_eq!(got, original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_apps_tool_call_request_meta_includes_codex_apps_meta() {
|
||||
#[tokio::test]
|
||||
async fn mcp_tool_call_request_meta_includes_turn_metadata_for_custom_server() {
|
||||
let (_session, turn_context) = make_session_and_context().await;
|
||||
|
||||
let meta =
|
||||
build_mcp_tool_call_request_meta(&turn_context, "custom_server", /*metadata*/ None)
|
||||
.expect("custom servers should receive turn metadata");
|
||||
|
||||
assert_eq!(
|
||||
meta,
|
||||
serde_json::json!({
|
||||
crate::X_CODEX_TURN_METADATA_HEADER: {
|
||||
"turn_id": turn_context.sub_id,
|
||||
"sandbox": "workspace-write",
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps_meta() {
|
||||
let (_session, turn_context) = make_session_and_context().await;
|
||||
let metadata = McpToolApprovalMetadata {
|
||||
annotations: None,
|
||||
connector_id: Some("calendar".to_string()),
|
||||
@@ -461,8 +481,16 @@ fn codex_apps_tool_call_request_meta_includes_codex_apps_meta() {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
build_mcp_tool_call_request_meta(CODEX_APPS_MCP_SERVER_NAME, Some(&metadata)),
|
||||
build_mcp_tool_call_request_meta(
|
||||
&turn_context,
|
||||
CODEX_APPS_MCP_SERVER_NAME,
|
||||
Some(&metadata),
|
||||
),
|
||||
Some(serde_json::json!({
|
||||
crate::X_CODEX_TURN_METADATA_HEADER: {
|
||||
"turn_id": turn_context.sub_id,
|
||||
"sandbox": "workspace-write",
|
||||
},
|
||||
MCP_TOOL_CODEX_APPS_META_KEY: {
|
||||
"resource_uri": "connector://calendar/tools/calendar_create_event",
|
||||
"contains_mcp_source": true,
|
||||
|
||||
@@ -168,6 +168,11 @@ impl TurnMetadataState {
|
||||
Some(self.base_header.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn current_meta_value(&self) -> Option<serde_json::Value> {
|
||||
self.current_header_value()
|
||||
.and_then(|header| serde_json::from_str(&header).ok())
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_git_enrichment_task(&self) {
|
||||
if self.repo_root.is_none() {
|
||||
return;
|
||||
|
||||
@@ -424,6 +424,36 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() -
|
||||
let requests = mock.requests();
|
||||
assert_eq!(requests.len(), 3);
|
||||
|
||||
let apps_tool_call = server
|
||||
.received_requests()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.find_map(|request| {
|
||||
let body: Value = serde_json::from_slice(&request.body)
|
||||
.expect("apps request body should be valid json");
|
||||
(request.url.path() == "/api/codex/apps"
|
||||
&& body.get("method").and_then(Value::as_str) == Some("tools/call"))
|
||||
.then_some(body)
|
||||
})
|
||||
.expect("apps tools/call request should be recorded");
|
||||
|
||||
assert_eq!(
|
||||
apps_tool_call.pointer("/params/_meta/_codex_apps"),
|
||||
Some(&json!({
|
||||
"resource_uri": CALENDAR_CREATE_EVENT_RESOURCE_URI,
|
||||
"contains_mcp_source": true,
|
||||
"connector_id": "calendar",
|
||||
}))
|
||||
);
|
||||
assert!(
|
||||
apps_tool_call
|
||||
.pointer("/params/_meta/x-codex-turn-metadata/turn_id")
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(|turn_id| !turn_id.is_empty()),
|
||||
"apps tools/call should include turn metadata turn_id: {apps_tool_call:?}"
|
||||
);
|
||||
|
||||
let first_request_tools = tool_names(&requests[0].body_json());
|
||||
assert!(
|
||||
first_request_tools
|
||||
|
||||
Reference in New Issue
Block a user