diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 8a8064c0b..43fb66182 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -2438,6 +2438,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -2452,6 +2464,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 56ea39276..277421a85 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -12248,6 +12248,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -12262,6 +12274,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 0f2910bef..39c95053d 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -8652,6 +8652,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -8666,6 +8678,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json index 8f4b533d8..dcf6312c7 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -299,6 +299,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -313,6 +325,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json index 4c930afde..1933225ad 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -299,6 +299,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -313,6 +325,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json index 16da9f205..6851ddb48 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -437,6 +437,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -451,6 +463,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json index 37a78533c..3ca62f17f 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -541,6 +541,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -555,6 +567,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json index 67726c13a..a81f64312 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -463,6 +463,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -477,6 +489,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json index cdd74a735..d007b32d2 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -463,6 +463,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -477,6 +489,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json index c3e388d1f..f45efe370 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -463,6 +463,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -477,6 +489,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json index ad44bf406..09dd3fedb 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -541,6 +541,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -555,6 +567,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json index 5ae94d8d1..8083b52d4 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRollbackResponse.json @@ -463,6 +463,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -477,6 +489,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json index df70c08c4..b33a64a20 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -541,6 +541,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -555,6 +567,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json index 157f6193b..9040457f2 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -463,6 +463,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -477,6 +489,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json index 265578dcd..d4e9fea61 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -463,6 +463,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -477,6 +489,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json index ae1b07cfc..6ffb34359 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -437,6 +437,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -451,6 +463,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json index 5853612dd..805a5099a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -437,6 +437,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -451,6 +463,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json index 7db58289e..1f3f7c703 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -437,6 +437,18 @@ }, "McpToolCallAppContext": { "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, "connectorId": { "type": "string" }, @@ -451,6 +463,12 @@ "string", "null" ] + }, + "templateId": { + "type": [ + "string", + "null" + ] } }, "required": [ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallAppContext.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallAppContext.ts index 0a8343428..e4bc5a11d 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallAppContext.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpToolCallAppContext.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type McpToolCallAppContext = { connectorId: string, linkId: string | null, resourceUri: string | null, }; +export type McpToolCallAppContext = { connectorId: string, linkId: string | null, resourceUri: string | null, appName: string | null, templateId: string | null, actionName: string | null, }; diff --git a/codex-rs/app-server-protocol/src/protocol/thread_history.rs b/codex-rs/app-server-protocol/src/protocol/thread_history.rs index 98702267a..aed87b18a 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -775,6 +775,9 @@ impl ThreadHistoryBuilder { connector_id, link_id: payload.link_id.clone(), resource_uri: payload.mcp_app_resource_uri.clone(), + app_name: payload.app_name.clone(), + template_id: payload.template_id.clone(), + action_name: payload.action_name.clone(), }), mcp_app_resource_uri: payload.mcp_app_resource_uri.clone(), plugin_id: payload.plugin_id.clone(), @@ -825,6 +828,9 @@ impl ThreadHistoryBuilder { connector_id, link_id: payload.link_id.clone(), resource_uri: payload.mcp_app_resource_uri.clone(), + app_name: payload.app_name.clone(), + template_id: payload.template_id.clone(), + action_name: payload.action_name.clone(), }), mcp_app_resource_uri: payload.mcp_app_resource_uri.clone(), plugin_id: payload.plugin_id.clone(), @@ -2418,6 +2424,9 @@ mod tests { connector_id: None, mcp_app_resource_uri: None, link_id: None, + app_name: None, + template_id: None, + action_name: None, plugin_id: None, duration: Duration::from_millis(8), result: Err("boom".into()), @@ -2499,6 +2508,9 @@ mod tests { connector_id: Some("calendar".into()), mcp_app_resource_uri: Some("ui://widget/lookup.html".into()), link_id: Some("link_calendar".into()), + app_name: Some("Calendar".into()), + template_id: Some("calendar_template".into()), + action_name: Some("lookup".into()), plugin_id: Some("sample@test".into()), duration: Duration::from_millis(8), result: Ok(CallToolResult { @@ -2533,6 +2545,9 @@ mod tests { connector_id: "calendar".into(), link_id: Some("link_calendar".into()), resource_uri: Some("ui://widget/lookup.html".into()), + app_name: Some("Calendar".into()), + template_id: Some("calendar_template".into()), + action_name: Some("lookup".into()), }), mcp_app_resource_uri: Some("ui://widget/lookup.html".into()), plugin_id: Some("sample@test".into()), diff --git a/codex-rs/app-server-protocol/src/protocol/v2/item.rs b/codex-rs/app-server-protocol/src/protocol/v2/item.rs index e2a0d4512..751eab824 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/item.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/item.rs @@ -396,6 +396,9 @@ pub struct McpToolCallAppContext { pub connector_id: String, pub link_id: Option, pub resource_uri: Option, + pub app_name: Option, + pub template_id: Option, + pub action_name: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] @@ -895,6 +898,9 @@ impl From for ThreadItem { connector_id, link_id: mcp.link_id, resource_uri: mcp.mcp_app_resource_uri.clone(), + app_name: mcp.app_name, + template_id: mcp.template_id, + action_name: mcp.action_name, }), mcp_app_resource_uri: mcp.mcp_app_resource_uri, plugin_id: mcp.plugin_id, diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index 03c2bcaa5..c6d460c72 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -2677,6 +2677,9 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { connector_id: Some("calendar".to_string()), mcp_app_resource_uri: Some("app://connector".to_string()), link_id: Some("link_calendar".to_string()), + app_name: Some("Calendar".to_string()), + template_id: Some("calendar_template".to_string()), + action_name: Some("create_event".to_string()), plugin_id: Some("sample@test".to_string()), status: CoreMcpToolCallStatus::InProgress, result: None, @@ -2696,6 +2699,9 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { connector_id: "calendar".to_string(), link_id: Some("link_calendar".to_string()), resource_uri: Some("app://connector".to_string()), + app_name: Some("Calendar".to_string()), + template_id: Some("calendar_template".to_string()), + action_name: Some("create_event".to_string()), }), mcp_app_resource_uri: Some("app://connector".to_string()), plugin_id: Some("sample@test".to_string()), @@ -2713,6 +2719,9 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { connector_id: None, mcp_app_resource_uri: None, link_id: None, + app_name: None, + template_id: None, + action_name: None, plugin_id: None, status: CoreMcpToolCallStatus::Completed, result: Some(CallToolResult { @@ -2759,6 +2768,9 @@ fn mcp_tool_call_app_context_serializes_connector_id() { connector_id: "calendar".to_string(), link_id: Some("link_calendar".to_string()), resource_uri: Some("app://connector".to_string()), + app_name: Some("Calendar".to_string()), + template_id: Some("calendar_template".to_string()), + action_name: Some("create_event".to_string()), }), mcp_app_resource_uri: Some("app://connector".to_string()), plugin_id: None, @@ -2780,6 +2792,9 @@ fn mcp_tool_call_app_context_serializes_connector_id() { "connectorId": "calendar", "linkId": "link_calendar", "resourceUri": "app://connector", + "appName": "Calendar", + "templateId": "calendar_template", + "actionName": "create_event", }, "mcpAppResourceUri": "app://connector", "pluginId": null, @@ -2790,6 +2805,29 @@ fn mcp_tool_call_app_context_serializes_connector_id() { ); } +#[test] +fn mcp_tool_call_app_context_serializes_missing_mixed_version_fields_as_null() { + assert_eq!( + serde_json::to_value(McpToolCallAppContext { + connector_id: "calendar".to_string(), + link_id: None, + resource_uri: None, + app_name: None, + template_id: None, + action_name: None, + }) + .expect("MCP tool call app context should serialize"), + json!({ + "connectorId": "calendar", + "linkId": null, + "resourceUri": null, + "appName": null, + "templateId": null, + "actionName": null, + }) + ); +} + #[test] fn user_input_into_core_preserves_image_detail() { assert_eq!( diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index a4b488acd..d291f5c85 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1372,7 +1372,7 @@ Today both notifications carry an empty `items` array even when item events were - `reasoning` — `{id, summary, content}` where `summary` holds streamed reasoning summaries (applicable for most OpenAI models) and `content` holds raw reasoning blocks (applicable for e.g. open source models). - `commandExecution` — `{id, command, cwd, status, commandActions, aggregatedOutput?, exitCode?, durationMs?}` for sandboxed commands; `status` is `inProgress`, `completed`, `failed`, or `declined`. - `fileChange` — `{id, changes, status}` describing proposed edits; `changes` list `{path, kind, diff}` and `status` is `inProgress`, `completed`, `failed`, or `declined`. -- `mcpToolCall` — `{id, server, tool, status, arguments, appContext, mcpAppResourceUri?, pluginId, result?, error?}` describing MCP calls; `appContext` is `{connectorId, linkId, resourceUri}` for calls through a trusted MCP app, where `connectorId` identifies the connector that owns the tool, `linkId` identifies the app link, and `resourceUri` points to the widget template. The top-level `mcpAppResourceUri` is deprecated and temporarily duplicated for client migration. `tool` identifies the invoked action. `status` is `inProgress`, `completed`, or `failed`. +- `mcpToolCall` — `{id, server, tool, status, arguments, appContext, mcpAppResourceUri?, pluginId, result?, error?}` describing MCP calls; `appContext` is `{connectorId, linkId, resourceUri, appName, templateId, actionName}` for calls through a trusted MCP app, where `connectorId` identifies the connector that owns the tool, `linkId` identifies the app link, `resourceUri` points to the widget template, `appName` is the connector's display name, `templateId` identifies the app template, and `actionName` is the stable connector `Action.name`. `appName`, `templateId`, and `actionName` may be null for older rollout entries. The top-level `mcpAppResourceUri` is deprecated and temporarily duplicated for client migration. `tool` identifies the raw MCP tool. `status` is `inProgress`, `completed`, or `failed`. - `collabToolCall` — `{id, tool, status, senderThreadId, receiverThreadId?, newThreadId?, prompt?, agentStatus?}` describing collab tool calls (`spawn_agent`, `send_input`, `resume_agent`, `wait`, `close_agent`); `status` is `inProgress`, `completed`, or `failed`. - `webSearch` — `{id, query, action?}` for a web search request issued by the agent; `action` mirrors the Responses API web_search action payload (`search`, `open_page`, `find_in_page`) and may be omitted until completion. - `imageView` — `{id, path}` emitted when the agent invokes the image viewer tool. diff --git a/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs b/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs index 586828e8d..f75360727 100644 --- a/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs +++ b/codex-rs/app-server/src/request_processors/thread_resume_redaction.rs @@ -83,6 +83,9 @@ mod tests { connector_id: "calendar".to_string(), link_id: Some("link_calendar".to_string()), resource_uri: Some("ui://widget/lookup.html".to_string()), + app_name: Some("Calendar".to_string()), + template_id: Some("calendar_template".to_string()), + action_name: Some("lookup".to_string()), }), mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()), plugin_id: Some("sample@test".to_string()), @@ -130,6 +133,9 @@ mod tests { connector_id: "calendar".to_string(), link_id: Some("link_calendar".to_string()), resource_uri: Some("ui://widget/lookup.html".to_string()), + app_name: Some("Calendar".to_string()), + template_id: Some("calendar_template".to_string()), + action_name: Some("lookup".to_string()), }), mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()), plugin_id: Some("sample@test".to_string()), diff --git a/codex-rs/app-server/tests/suite/v2/thread_resume.rs b/codex-rs/app-server/tests/suite/v2/thread_resume.rs index 42db56f52..67dd24921 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -834,6 +834,9 @@ async fn thread_resume_redacts_payloads_for_chatgpt_remote_clients() -> Result<( connector_id: "calendar".to_string(), link_id: Some("link_calendar".to_string()), resource_uri: Some("ui://widget/lookup.html".to_string()), + app_name: Some("Calendar".to_string()), + template_id: Some("calendar_template".to_string()), + action_name: Some("lookup".to_string()), }) ); let result = result.as_ref().expect("redacted MCP result"); @@ -884,6 +887,9 @@ async fn thread_resume_redacts_payloads_for_chatgpt_remote_clients() -> Result<( connector_id: "calendar".to_string(), link_id: Some("link_calendar".to_string()), resource_uri: Some("ui://widget/lookup.html".to_string()), + app_name: Some("Calendar".to_string()), + template_id: Some("calendar_template".to_string()), + action_name: Some("lookup".to_string()), }) ); let result = result.as_ref().expect("normal MCP result"); @@ -990,6 +996,9 @@ fn append_resume_redaction_history( connector_id: Some("calendar".to_string()), mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()), link_id: Some("link_calendar".to_string()), + app_name: Some("Calendar".to_string()), + template_id: Some("calendar_template".to_string()), + action_name: Some("lookup".to_string()), plugin_id: None, duration: Duration::from_millis(8), result: Ok(CallToolResult { diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 19b7c1a6a..d03f232d1 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -326,6 +326,9 @@ struct McpToolCallItemMetadata { connector_id: Option, link_id: Option, mcp_app_resource_uri: Option, + app_name: Option, + template_id: Option, + action_name: Option, plugin_id: Option, } @@ -342,6 +345,15 @@ impl McpToolCallItemMetadata { link_id: trusted_mcp_app_metadata.and_then(|metadata| metadata.link_id.clone()), mcp_app_resource_uri: metadata .and_then(|metadata| metadata.mcp_app_resource_uri.clone()), + app_name: trusted_mcp_app_metadata.and_then(|metadata| metadata.connector_name.clone()), + template_id: trusted_mcp_app_metadata.and_then(|metadata| metadata.template_id.clone()), + action_name: trusted_mcp_app_metadata + .and_then(|metadata| metadata.codex_apps_meta.as_ref()) + .and_then(|meta| meta.get(MCP_TOOL_RESOURCE_URI_META_KEY)) + .and_then(serde_json::Value::as_str) + .and_then(|resource_uri| resource_uri.trim_matches('/').rsplit('/').next()) + .filter(|action_name| !action_name.is_empty()) + .map(str::to_string), plugin_id: metadata.and_then(|metadata| metadata.plugin_id.clone()), } } @@ -881,6 +893,9 @@ async fn notify_mcp_tool_call_started( connector_id: item_metadata.connector_id, mcp_app_resource_uri: item_metadata.mcp_app_resource_uri, link_id: item_metadata.link_id, + app_name: item_metadata.app_name, + template_id: item_metadata.template_id, + action_name: item_metadata.action_name, plugin_id: item_metadata.plugin_id, status: McpToolCallStatus::InProgress, result: None, @@ -923,6 +938,9 @@ async fn notify_mcp_tool_call_completed( connector_id: item_metadata.connector_id, mcp_app_resource_uri: item_metadata.mcp_app_resource_uri, link_id: item_metadata.link_id, + app_name: item_metadata.app_name, + template_id: item_metadata.template_id, + action_name: item_metadata.action_name, plugin_id: item_metadata.plugin_id, status, result, @@ -999,6 +1017,7 @@ pub(crate) struct McpToolApprovalMetadata { tool_title: Option, tool_description: Option, mcp_app_resource_uri: Option, + template_id: Option, codex_apps_meta: Option>, openai_file_input_params: Option>, } @@ -1009,6 +1028,8 @@ const MCP_TOOL_LINK_ID_META_KEY: &str = "link_id"; const MCP_TOOL_PLUGIN_ID_META_KEY: &str = "plugin_id"; const MCP_TOOL_THREAD_ID_META_KEY: &str = "threadId"; const MCP_TOOL_CONNECTED_ACCOUNT_EMAIL_META_KEY: &str = "connected_account_email"; +const MCP_TOOL_TEMPLATE_ID_META_KEY: &str = "template_id"; +const MCP_TOOL_RESOURCE_URI_META_KEY: &str = "resource_uri"; async fn custom_mcp_tool_approval_mode( sess: &Session, @@ -1531,6 +1552,11 @@ pub(crate) async fn lookup_mcp_tool_metadata( tool_title: tool_info.tool.title, tool_description: tool_info.tool.description.map(std::borrow::Cow::into_owned), mcp_app_resource_uri: get_mcp_app_resource_uri(tool_info.tool.meta.as_deref()), + template_id: codex_apps_meta + .as_ref() + .and_then(|meta| meta.get(MCP_TOOL_TEMPLATE_ID_META_KEY)) + .and_then(serde_json::Value::as_str) + .map(str::to_string), codex_apps_meta, // Disallow custom MCPs from uploading files via fileParams. openai_file_input_params: openai_file_input_params_for_server( diff --git a/codex-rs/core/src/mcp_tool_call_tests.rs b/codex-rs/core/src/mcp_tool_call_tests.rs index 271141613..6b642b1cd 100644 --- a/codex-rs/core/src/mcp_tool_call_tests.rs +++ b/codex-rs/core/src/mcp_tool_call_tests.rs @@ -87,6 +87,7 @@ fn approval_metadata( tool_title: tool_title.map(str::to_string), tool_description: tool_description.map(str::to_string), mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: None, openai_file_input_params: None, } @@ -1195,12 +1196,22 @@ async fn plugin_mcp_tool_call_request_meta_includes_plugin_id() { fn mcp_tool_call_item_metadata_only_trusts_codex_apps_identity() { let mut metadata = approval_metadata( Some("asdk_app_0123456789abcdef0123456789abcdef"), - /*connector_name*/ None, + Some("Calendar"), /*connector_description*/ None, - /*tool_title*/ None, + Some("Create a calendar event"), /*tool_description*/ None, ); metadata.link_id = Some("link_fedcba9876543210fedcba9876543210".to_string()); + metadata.template_id = Some("calendar_template".to_string()); + metadata.codex_apps_meta = Some( + serde_json::json!({ + "resource_uri": "/asdk_app_0123456789abcdef0123456789abcdef/link_fedcba9876543210fedcba9876543210/create_event", + "template_id": "calendar_template", + }) + .as_object() + .cloned() + .expect("_codex_apps metadata should be an object"), + ); assert_eq!( McpToolCallItemMetadata::from_tool_metadata(CODEX_APPS_MCP_SERVER_NAME, Some(&metadata),), @@ -1208,6 +1219,9 @@ fn mcp_tool_call_item_metadata_only_trusts_codex_apps_identity() { connector_id: Some("asdk_app_0123456789abcdef0123456789abcdef".to_string()), link_id: Some("link_fedcba9876543210fedcba9876543210".to_string()), mcp_app_resource_uri: None, + app_name: Some("Calendar".to_string()), + template_id: Some("calendar_template".to_string()), + action_name: Some("create_event".to_string()), plugin_id: None, } ); @@ -1217,6 +1231,9 @@ fn mcp_tool_call_item_metadata_only_trusts_codex_apps_identity() { connector_id: None, link_id: None, mcp_app_resource_uri: None, + app_name: None, + template_id: None, + action_name: None, plugin_id: None, } ); @@ -1239,6 +1256,9 @@ async fn mcp_tool_call_item_includes_app_identity() { connector_id: Some("asdk_app_0123456789abcdef0123456789abcdef".to_string()), link_id: Some("link_fedcba9876543210fedcba9876543210".to_string()), mcp_app_resource_uri: None, + app_name: Some("Calendar".to_string()), + template_id: Some("calendar_template".to_string()), + action_name: Some("create_event".to_string()), plugin_id: Some("sample@test".to_string()), }, ) @@ -1264,6 +1284,8 @@ async fn mcp_tool_call_item_includes_app_identity() { Some("link_fedcba9876543210fedcba9876543210") ); assert_eq!(item.plugin_id.as_deref(), Some("sample@test")); + assert_eq!(item.app_name.as_deref(), Some("Calendar")); + assert_eq!(item.action_name.as_deref(), Some("create_event")); } #[tokio::test] @@ -1284,6 +1306,7 @@ async fn codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps tool_title: Some("Create Event".to_string()), tool_description: Some("Create a calendar event.".to_string()), mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: Some( serde_json::json!({ "resource_uri": "connector://calendar/tools/calendar_create_event", @@ -1771,6 +1794,7 @@ fn guardian_mcp_review_request_includes_annotations_when_present() { tool_title: None, tool_description: None, mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: None, openai_file_input_params: None, }; @@ -2473,6 +2497,7 @@ async fn approve_mode_skips_when_annotations_do_not_require_approval() { tool_title: Some("Read Only Tool".to_string()), tool_description: None, mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: None, openai_file_input_params: None, }; @@ -2549,6 +2574,7 @@ async fn guardian_mode_skips_auto_when_annotations_do_not_require_approval() { tool_title: Some("Read Only Tool".to_string()), tool_description: None, mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: None, openai_file_input_params: None, }; @@ -2608,6 +2634,7 @@ async fn permission_request_hook_allows_mcp_tool_call() { tool_title: Some("Create entities".to_string()), tool_description: None, mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: None, openai_file_input_params: None, }; @@ -2746,6 +2773,7 @@ async fn permission_request_hook_runs_after_remembered_mcp_approval() { tool_title: Some("Create entities".to_string()), tool_description: None, mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: None, openai_file_input_params: None, }; @@ -2835,6 +2863,7 @@ async fn guardian_mode_mcp_denial_returns_rationale_message() { tool_title: Some("Dangerous Tool".to_string()), tool_description: Some("Reads calendar data.".to_string()), mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: None, openai_file_input_params: None, }; @@ -2891,6 +2920,7 @@ async fn prompt_mode_waits_for_approval_when_annotations_do_not_require_approval tool_title: Some("Read Only Tool".to_string()), tool_description: None, mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: None, openai_file_input_params: None, }; @@ -2948,6 +2978,7 @@ async fn full_access_mode_skips_mcp_tool_approval_for_all_approval_modes() { tool_title: Some("Dangerous Tool".to_string()), tool_description: Some("Performs a risky action.".to_string()), mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: None, openai_file_input_params: None, }; @@ -3003,6 +3034,7 @@ async fn approve_mode_skips_guardian_in_every_permission_mode() { tool_title: Some("Dangerous Tool".to_string()), tool_description: Some("Performs a risky action.".to_string()), mcp_app_resource_uri: None, + template_id: None, codex_apps_meta: None, openai_file_input_params: None, }; diff --git a/codex-rs/core/src/tools/handlers/mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource.rs index 231bdf92a..b073b5ee7 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource.rs @@ -224,6 +224,9 @@ async fn emit_tool_call_begin( connector_id: None, mcp_app_resource_uri: None, link_id: None, + app_name: None, + template_id: None, + action_name: None, plugin_id: None, status: McpToolCallStatus::InProgress, result: None, @@ -265,6 +268,9 @@ async fn emit_tool_call_end( connector_id: None, mcp_app_resource_uri: None, link_id: None, + app_name: None, + template_id: None, + action_name: None, plugin_id: None, status, result, diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index fc1a7df18..fae4ab641 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -1359,6 +1359,9 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> { connector_id: None, mcp_app_resource_uri: None, link_id: None, + app_name: None, + template_id: None, + action_name: None, plugin_id: None, }, ); diff --git a/codex-rs/core/tests/suite/search_tool.rs b/codex-rs/core/tests/suite/search_tool.rs index edfc53fdc..afb2e0d08 100644 --- a/codex-rs/core/tests/suite/search_tool.rs +++ b/codex-rs/core/tests/suite/search_tool.rs @@ -545,6 +545,8 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() - unreachable!("event guard guarantees McpToolCallBegin"); }; assert_eq!(begin.call_id, "calendar-call-1"); + assert_eq!(begin.app_name.as_deref(), Some("Calendar")); + assert_eq!(begin.action_name.as_deref(), Some("calendar_create_event")); assert_eq!( begin.mcp_app_resource_uri.as_deref(), Some(CALENDAR_CREATE_EVENT_MCP_APP_RESOURCE_URI) @@ -559,6 +561,8 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() - }; assert_eq!(end.call_id, "calendar-call-1"); assert_eq!(end.connector_id.as_deref(), Some("calendar")); + assert_eq!(end.app_name.as_deref(), Some("Calendar")); + assert_eq!(end.action_name.as_deref(), Some("calendar_create_event")); assert_eq!( end.mcp_app_resource_uri.as_deref(), Some(CALENDAR_CREATE_EVENT_MCP_APP_RESOURCE_URI) diff --git a/codex-rs/protocol/src/items.rs b/codex-rs/protocol/src/items.rs index 508d71d4b..c4ee26d96 100644 --- a/codex-rs/protocol/src/items.rs +++ b/codex-rs/protocol/src/items.rs @@ -202,6 +202,15 @@ pub struct McpToolCallItem { pub link_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] + pub app_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub template_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub action_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] pub plugin_id: Option, pub status: McpToolCallStatus, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -560,6 +569,9 @@ impl McpToolCallItem { connector_id: self.connector_id.clone(), mcp_app_resource_uri: self.mcp_app_resource_uri.clone(), link_id: self.link_id.clone(), + app_name: self.app_name.clone(), + template_id: self.template_id.clone(), + action_name: self.action_name.clone(), plugin_id: self.plugin_id.clone(), }) } @@ -581,6 +593,9 @@ impl McpToolCallItem { mcp_app_resource_uri: self.mcp_app_resource_uri.clone(), connector_id: self.connector_id.clone(), link_id: self.link_id.clone(), + app_name: self.app_name.clone(), + template_id: self.template_id.clone(), + action_name: self.action_name.clone(), plugin_id: self.plugin_id.clone(), duration: self.duration?, result, diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 5f3e75dd8..491d64aa5 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -2357,6 +2357,15 @@ pub struct McpToolCallBeginEvent { pub link_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] + pub app_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub template_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub action_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] pub plugin_id: Option, } @@ -2376,6 +2385,15 @@ pub struct McpToolCallEndEvent { pub link_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] + pub app_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub template_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] + pub action_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] pub plugin_id: Option, #[ts(type = "string")] pub duration: Duration, @@ -5140,6 +5158,9 @@ mod tests { connector_id: Some("connector".into()), mcp_app_resource_uri: Some("app://connector".into()), link_id: Some("link_123".into()), + app_name: Some("Calendar".into()), + template_id: Some("calendar_template".into()), + action_name: Some("create_event".into()), plugin_id: Some("sample@test".into()), status: McpToolCallStatus::InProgress, result: None, @@ -5161,6 +5182,8 @@ mod tests { Some("app://connector") ); assert_eq!(event.link_id.as_deref(), Some("link_123")); + assert_eq!(event.app_name.as_deref(), Some("Calendar")); + assert_eq!(event.action_name.as_deref(), Some("create_event")); assert_eq!(event.plugin_id.as_deref(), Some("sample@test")); } _ => panic!("expected McpToolCallBegin event"), @@ -5251,6 +5274,9 @@ mod tests { connector_id: Some("connector".into()), mcp_app_resource_uri: Some("app://connector".into()), link_id: Some("link_123".into()), + app_name: Some("Calendar".into()), + template_id: Some("calendar_template".into()), + action_name: Some("create_event".into()), plugin_id: Some("sample@test".into()), status: McpToolCallStatus::Completed, result: Some(CallToolResult { @@ -5277,6 +5303,8 @@ mod tests { Some("app://connector") ); assert_eq!(event.link_id.as_deref(), Some("link_123")); + assert_eq!(event.app_name.as_deref(), Some("Calendar")); + assert_eq!(event.action_name.as_deref(), Some("create_event")); assert_eq!(event.plugin_id.as_deref(), Some("sample@test")); assert_eq!(event.duration, Duration::from_millis(42)); assert!(event.is_success());