diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index a576251e5a..d65451cd80 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -5659,6 +5659,7 @@ async fn turn_event_counts_completed_tool_items() { action_name: None, }), mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: Some("sample@test".to_string()), read_only_hint: None, result: None, @@ -5912,6 +5913,7 @@ async fn mcp_elicitation_classification_survives_turn_completion_and_preserves_c action_name: None, }), mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs index 026c47b67f..da3452cad8 100644 --- a/codex-rs/analytics/src/reducer.rs +++ b/codex-rs/analytics/src/reducer.rs @@ -3869,6 +3869,7 @@ mod tests { arguments: serde_json::json!({}), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index b4f02ff44c..1bbe08dcd5 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -2693,6 +2693,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpServerEventNotification": { "properties": { "method": { @@ -4917,12 +4940,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 fa0ba67749..d16493b10f 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 @@ -14410,6 +14410,29 @@ "title": "MarketplaceUpgradeResponse", "type": "object" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/v2/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpAuthStatus": { "enum": [ "unknown", @@ -21826,12 +21849,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/v2/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 3b8f1e185f..7e41e242d6 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 @@ -10462,6 +10462,29 @@ "title": "MarketplaceUpgradeResponse", "type": "object" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpAuthStatus": { "enum": [ "unknown", @@ -19540,12 +19563,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 b0757fd2e4..61ea1e89bf 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemCompletedNotification.json @@ -510,6 +510,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1137,12 +1160,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 6018fa212f..8ea3e73125 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ItemStartedNotification.json @@ -510,6 +510,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1137,12 +1160,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 a058b676e6..9f61cb9a2b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ReviewStartResponse.json @@ -650,6 +650,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1325,12 +1348,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 dc0f83ba14..d91152ca01 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadForkResponse.json @@ -754,6 +754,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1939,12 +1962,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadItemsListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadItemsListResponse.json index f8427717dc..1389f19551 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadItemsListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadItemsListResponse.json @@ -510,6 +510,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1137,12 +1160,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 9f4be687e0..61cdd1bed2 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadListResponse.json @@ -676,6 +676,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1730,12 +1753,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 32cfc6fe96..f358d1d8bf 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadMetadataUpdateResponse.json @@ -676,6 +676,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1730,12 +1753,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 a04d78dc33..2c3e187bb1 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadReadResponse.json @@ -676,6 +676,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1730,12 +1753,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 cb8c0052c3..1ddad74a58 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadResumeResponse.json @@ -770,6 +770,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1991,12 +2014,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadRevertResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadRevertResponse.json index 2b57af18c2..0d1a6d4cff 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadRevertResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadRevertResponse.json @@ -676,6 +676,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1730,12 +1753,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 9e0f4155c2..e46750676a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartResponse.json @@ -754,6 +754,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1939,12 +1962,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 6832e8318e..dc40dfe804 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadStartedNotification.json @@ -676,6 +676,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1730,12 +1753,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ThreadTurnsListResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ThreadTurnsListResponse.json index 59ce4d96ba..99ff6281f9 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadTurnsListResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadTurnsListResponse.json @@ -650,6 +650,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1325,12 +1348,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 f2c1ce42c9..2158ee699a 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ThreadUnarchiveResponse.json @@ -676,6 +676,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1730,12 +1753,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 2e24d09644..a219cf31e4 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnCompletedNotification.json @@ -650,6 +650,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1325,12 +1348,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 c715c11287..259f3cf717 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartResponse.json @@ -650,6 +650,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1325,12 +1348,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", 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 0781a4f439..b8bb6240fa 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/TurnStartedNotification.json @@ -650,6 +650,29 @@ "LegacyAppPathString": { "type": "string" }, + "McpAppDisplayMode": { + "enum": [ + "inline", + "fullscreen" + ], + "type": "string" + }, + "McpAppUi": { + "description": "UI resource and display preference for model invocations, captured from the tool descriptor.", + "properties": { + "preferredModelDisplayMode": { + "$ref": "#/definitions/McpAppDisplayMode" + }, + "resourceUri": { + "type": "string" + } + }, + "required": [ + "preferredModelDisplayMode", + "resourceUri" + ], + "type": "object" + }, "McpToolCallAppContext": { "properties": { "actionName": { @@ -1325,12 +1348,23 @@ "type": "string" }, "mcpAppResourceUri": { - "description": "Deprecated: use `appContext.resourceUri` instead.", + "description": "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", "type": [ "string", "null" ] }, + "mcpAppUi": { + "anyOf": [ + { + "$ref": "#/definitions/McpAppUi" + }, + { + "type": "null" + } + ], + "description": "Presentation captured from the invoked descriptor; absent in older history." + }, "pluginId": { "type": [ "string", diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst index 96e4c83090..e0c3d9b552 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst differ diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst index b339e9b4ab..c9d2d872aa 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst differ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpAppDisplayMode.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpAppDisplayMode.ts new file mode 100644 index 0000000000..aa816f5e0f --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpAppDisplayMode.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type McpAppDisplayMode = "inline" | "fullscreen"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/McpAppUi.ts b/codex-rs/app-server-protocol/schema/typescript/v2/McpAppUi.ts new file mode 100644 index 0000000000..cfa781b8d1 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/McpAppUi.ts @@ -0,0 +1,9 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { McpAppDisplayMode } from "./McpAppDisplayMode"; + +/** + * UI resource and display preference for model invocations, captured from the tool descriptor. + */ +export type McpAppUi = { resourceUri: string, preferredModelDisplayMode: McpAppDisplayMode, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts index ae8c00a720..31f1430ebe 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ThreadItem.ts @@ -21,6 +21,7 @@ import type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputCo import type { DynamicToolCallStatus } from "./DynamicToolCallStatus"; import type { FileUpdateChange } from "./FileUpdateChange"; import type { HookPromptFragment } from "./HookPromptFragment"; +import type { McpAppUi } from "./McpAppUi"; import type { McpToolCallAppContext } from "./McpToolCallAppContext"; import type { McpToolCallError } from "./McpToolCallError"; import type { McpToolCallResult } from "./McpToolCallResult"; @@ -70,9 +71,13 @@ exitCode: number | null, */ durationMs: number | null, } | { "type": "fileChange", id: string, changes: Array, status: PatchApplyStatus, } | { "type": "mcpToolCall", id: string, server: string, tool: string, status: McpToolCallStatus, arguments: JsonValue, appContext: McpToolCallAppContext | null, /** - * Deprecated: use `appContext.resourceUri` instead. + * Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available. */ -mcpAppResourceUri?: string, pluginId: string | null, readOnlyHint: boolean | null, result: McpToolCallResult | null, error: McpToolCallError | null, +mcpAppResourceUri?: string, +/** + * Presentation captured from the invoked descriptor; absent in older history. + */ +mcpAppUi: McpAppUi | null, pluginId: string | null, readOnlyHint: boolean | null, result: McpToolCallResult | null, error: McpToolCallError | null, /** * The duration of the MCP tool call in milliseconds. */ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 7e563b483f..64c7420cc0 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -251,6 +251,8 @@ export type { MarketplaceRemoveResponse } from "./MarketplaceRemoveResponse"; export type { MarketplaceUpgradeErrorInfo } from "./MarketplaceUpgradeErrorInfo"; export type { MarketplaceUpgradeParams } from "./MarketplaceUpgradeParams"; export type { MarketplaceUpgradeResponse } from "./MarketplaceUpgradeResponse"; +export type { McpAppDisplayMode } from "./McpAppDisplayMode"; +export type { McpAppUi } from "./McpAppUi"; export type { McpAuthStatus } from "./McpAuthStatus"; export type { McpElicitationArrayType } from "./McpElicitationArrayType"; export type { McpElicitationBooleanSchema } from "./McpElicitationBooleanSchema"; 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 4b6b4168f8..df0a5db810 100644 --- a/codex-rs/app-server-protocol/src/protocol/thread_history.rs +++ b/codex-rs/app-server-protocol/src/protocol/thread_history.rs @@ -784,6 +784,7 @@ impl ThreadHistoryBuilder { action_name: payload.action_name.clone(), }), mcp_app_resource_uri: payload.mcp_app_resource_uri.clone(), + mcp_app_ui: payload.mcp_app_ui.clone(), plugin_id: payload.plugin_id.clone(), read_only_hint: payload.read_only_hint, result: None, @@ -841,6 +842,7 @@ impl ThreadHistoryBuilder { action_name: payload.action_name.clone(), }), mcp_app_resource_uri: payload.mcp_app_resource_uri.clone(), + mcp_app_ui: payload.mcp_app_ui.clone(), plugin_id: payload.plugin_id.clone(), read_only_hint: payload.read_only_hint, result, @@ -3118,6 +3120,7 @@ mod tests { }, connector_id: None, mcp_app_resource_uri: None, + mcp_app_ui: None, link_id: None, app_name: None, action_name: None, @@ -3181,6 +3184,7 @@ mod tests { arguments: serde_json::json!({"id":"123"}), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, @@ -3213,6 +3217,7 @@ mod tests { }, connector_id: Some("calendar".into()), mcp_app_resource_uri: Some("ui://widget/lookup.html".into()), + mcp_app_ui: None, link_id: Some("link_calendar".into()), app_name: Some("Calendar".into()), action_name: Some("lookup".into()), @@ -3255,6 +3260,7 @@ mod tests { action_name: Some("lookup".into()), }), mcp_app_resource_uri: Some("ui://widget/lookup.html".into()), + mcp_app_ui: None, plugin_id: Some("sample@test".into()), read_only_hint: Some(false), result: Some(Box::new(McpToolCallResult { @@ -3646,6 +3652,7 @@ mod tests { arguments: serde_json::json!({"query": "incident"}), connector_id: None, mcp_app_resource_uri: None, + mcp_app_ui: None, link_id: None, app_name: None, action_name: None, 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 de18c65fe7..06f1bc7117 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/item.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/item.rs @@ -31,6 +31,8 @@ use codex_protocol::items::CollabAgentTool as CoreCollabAgentTool; use codex_protocol::items::CollabAgentToolCallStatus as CoreCollabAgentToolCallStatus; use codex_protocol::items::CommandExecutionStatus as CoreCommandExecutionStatus; use codex_protocol::items::DynamicToolCallStatus as CoreDynamicToolCallStatus; +pub use codex_protocol::items::McpAppDisplayMode; +pub use codex_protocol::items::McpAppUi; use codex_protocol::items::McpToolCallStatus as CoreMcpToolCallStatus; use codex_protocol::items::TurnItem as CoreTurnItem; use codex_protocol::memory_citation::MemoryCitation as CoreMemoryCitation; @@ -337,8 +339,10 @@ pub enum ThreadItem { app_context: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] - /// Deprecated: use `appContext.resourceUri` instead. + /// Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available. mcp_app_resource_uri: Option, + /// Presentation captured from the invoked descriptor; absent in older history. + mcp_app_ui: Option, plugin_id: Option, read_only_hint: Option, result: Option>, @@ -1038,6 +1042,7 @@ impl From for ThreadItem { action_name: mcp.action_name, }), mcp_app_resource_uri: mcp.mcp_app_resource_uri, + mcp_app_ui: mcp.mcp_app_ui, plugin_id: mcp.plugin_id, read_only_hint: mcp.read_only_hint, result: mcp.result.map(McpToolCallResult::from).map(Box::new), 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 29cac91c98..478903f940 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -3463,6 +3463,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { arguments: json!({"arg": "value"}), connector_id: Some("calendar".to_string()), mcp_app_resource_uri: Some("app://connector".to_string()), + mcp_app_ui: None, link_id: Some("link_calendar".to_string()), app_name: Some("Calendar".to_string()), action_name: Some("create_event".to_string()), @@ -3490,6 +3491,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { action_name: Some("create_event".to_string()), }), mcp_app_resource_uri: Some("app://connector".to_string()), + mcp_app_ui: None, plugin_id: Some("sample@test".to_string()), read_only_hint: Some(true), result: None, @@ -3505,6 +3507,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { arguments: JsonValue::Null, connector_id: None, mcp_app_resource_uri: None, + mcp_app_ui: None, link_id: None, app_name: None, action_name: None, @@ -3531,6 +3534,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() { arguments: JsonValue::Null, app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: Some(false), result: Some(Box::new(McpToolCallResult { @@ -3560,6 +3564,7 @@ fn mcp_tool_call_app_context_serializes_connector_id() { action_name: Some("create_event".to_string()), }), mcp_app_resource_uri: Some("app://connector".to_string()), + mcp_app_ui: None, plugin_id: None, read_only_hint: Some(false), result: None, @@ -3584,6 +3589,7 @@ fn mcp_tool_call_app_context_serializes_connector_id() { "actionName": "create_event", }, "mcpAppResourceUri": "app://connector", + "mcpAppUi": null, "pluginId": null, "readOnlyHint": false, "result": null, diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 362b1fe968..8308b962be 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1,3 +1,15 @@ +# MCP App UI + +`mcpToolCall.mcpAppUi` records the invoked descriptor's `resourceUri` +and `preferredModelDisplayMode` (`inline` or `fullscreen`). Descriptors with a widget +URI default to `inline` when the preference is missing or unsupported. The +UI information is preserved in tool-call events and saved history so clients can +render without waiting for the full MCP catalog. + +The field is null for older history and tools that declare widgets only in +result metadata; clients retain catalog discovery for those calls. Existing +resource URI fields remain available for older clients. + # Initial Daybreak choice (experimental) Persistent threads accept `daybreakEnabled` on `thread/start` with the 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 e9c2880f3d..62b10cf0b9 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 @@ -90,6 +90,7 @@ mod tests { action_name: Some("lookup".to_string()), }), mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()), + mcp_app_ui: None, plugin_id: Some("sample@test".to_string()), read_only_hint: None, result: Some(Box::new(McpToolCallResult { @@ -146,6 +147,7 @@ mod tests { action_name: Some("lookup".to_string()), }), mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()), + mcp_app_ui: None, plugin_id: Some("sample@test".to_string()), read_only_hint: None, result: Some(Box::new(redacted_mcp_tool_call_result())), @@ -165,6 +167,7 @@ mod tests { arguments: serde_json::json!({"secret":"argument"}), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, @@ -186,6 +189,7 @@ mod tests { arguments: JsonValue::String(REDACTED_PAYLOAD.to_string()), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, diff --git a/codex-rs/app-server/tests/suite/v2/mcp_tool.rs b/codex-rs/app-server/tests/suite/v2/mcp_tool.rs index 8c884a61dc..a0c313b7e2 100644 --- a/codex-rs/app-server/tests/suite/v2/mcp_tool.rs +++ b/codex-rs/app-server/tests/suite/v2/mcp_tool.rs @@ -1087,6 +1087,7 @@ async fn mcp_tool_call_completion_notification_contains_truncated_large_result() arguments: json!({ "message": LARGE_RESPONSE_MESSAGE }), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: Some(result), @@ -1198,6 +1199,7 @@ async fn mcp_tool_call_hint_survives_mid_call_thread_read_and_resume() -> Result arguments: json!({ "message": ELICITATION_TRIGGER_MESSAGE }), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: Some(true), result: None, 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 822d21c0ef..d53aaa6734 100644 --- a/codex-rs/app-server/tests/suite/v2/thread_resume.rs +++ b/codex-rs/app-server/tests/suite/v2/thread_resume.rs @@ -2770,6 +2770,7 @@ fn append_resume_redaction_history( }, connector_id: Some("calendar".to_string()), mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()), + mcp_app_ui: None, link_id: Some("link_calendar".to_string()), app_name: Some("Calendar".to_string()), action_name: Some("lookup".to_string()), diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 27e6a77f82..4d031adfba 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -46,6 +46,8 @@ use codex_mcp::build_auth_elicitation_plan; use codex_mcp::is_connector_auth_failure_from_tool_result; use codex_mcp::mcp_permission_prompt_is_auto_approved; use codex_protocol::approvals::ElicitationRequest; +use codex_protocol::items::McpAppDisplayMode; +use codex_protocol::items::McpAppUi; use codex_protocol::items::McpToolCallError; use codex_protocol::items::McpToolCallItem; use codex_protocol::items::McpToolCallStatus; @@ -390,7 +392,7 @@ pub(crate) struct HandledMcpToolCall { struct McpToolCallItemMetadata { connector_id: Option, link_id: Option, - mcp_app_resource_uri: Option, + mcp_app_ui: Option, app_name: Option, action_name: Option, plugin_id: Option, @@ -408,8 +410,7 @@ impl McpToolCallItemMetadata { connector_id: trusted_mcp_app_metadata .and_then(|metadata| metadata.connector_id.clone()), 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()), + mcp_app_ui: metadata.and_then(|metadata| metadata.mcp_app_ui.clone()), app_name: trusted_mcp_app_metadata.and_then(|metadata| metadata.connector_name.clone()), action_name: trusted_mcp_app_metadata .and_then(|metadata| metadata.codex_apps_meta.as_ref()) @@ -1004,7 +1005,11 @@ async fn notify_mcp_tool_call_started( tool, arguments: arguments.unwrap_or(JsonValue::Null), connector_id: item_metadata.connector_id, - mcp_app_resource_uri: item_metadata.mcp_app_resource_uri, + mcp_app_resource_uri: item_metadata + .mcp_app_ui + .as_ref() + .map(|ui| ui.resource_uri.clone()), + mcp_app_ui: item_metadata.mcp_app_ui, link_id: item_metadata.link_id, app_name: item_metadata.app_name, action_name: item_metadata.action_name, @@ -1049,7 +1054,11 @@ async fn notify_mcp_tool_call_completed( tool, arguments: arguments.unwrap_or(JsonValue::Null), connector_id: item_metadata.connector_id, - mcp_app_resource_uri: item_metadata.mcp_app_resource_uri, + mcp_app_resource_uri: item_metadata + .mcp_app_ui + .as_ref() + .map(|ui| ui.resource_uri.clone()), + mcp_app_ui: item_metadata.mcp_app_ui, link_id: item_metadata.link_id, app_name: item_metadata.app_name, action_name: item_metadata.action_name, @@ -1171,7 +1180,7 @@ pub(crate) struct McpToolApprovalMetadata { plugin_id: Option, tool_title: Option, tool_description: Option, - mcp_app_resource_uri: Option, + mcp_app_ui: Option, codex_apps_meta: Option>, openai_file_input_optional_fields: Option>>, } @@ -1633,7 +1642,7 @@ pub(crate) async fn request_mcp_tool_user_approval( plugin_id: None, tool_title: tool_title.clone(), tool_description: tool_description.clone(), - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; @@ -1783,6 +1792,24 @@ fn mcp_tool_metadata( None }; + let mcp_app_ui = get_mcp_app_resource_uri(tool_info.tool.meta.as_deref()).map(|resource_uri| { + let preferred_model_display_mode = match tool_info + .tool + .meta + .as_ref() + .and_then(|meta| meta.get("openai/ui")) + .and_then(|ui| ui.get("preferredModelDisplayMode")) + .and_then(serde_json::Value::as_str) + { + Some("fullscreen") => McpAppDisplayMode::Fullscreen, + _ => McpAppDisplayMode::Inline, + }; + McpAppUi { + resource_uri, + preferred_model_display_mode, + } + }); + Ok(McpToolApprovalMetadata { annotations: tool_info.tool.annotations, connector_id: tool_info.connector_id, @@ -1793,7 +1820,7 @@ fn mcp_tool_metadata( plugin_id: plugin_id.map(str::to_string), 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()), + mcp_app_ui, codex_apps_meta, // Disallow custom MCPs from uploading files via fileParams. openai_file_input_optional_fields: openai_file_input_optional_fields_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 35d360202d..bbc236b3a3 100644 --- a/codex-rs/core/src/mcp_tool_call_tests.rs +++ b/codex-rs/core/src/mcp_tool_call_tests.rs @@ -90,7 +90,7 @@ fn approval_metadata( plugin_id: None, tool_title: tool_title.map(str::to_string), tool_description: tool_description.map(str::to_string), - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, } @@ -1436,7 +1436,7 @@ fn mcp_tool_call_item_metadata_only_trusts_codex_apps_identity() { McpToolCallItemMetadata { connector_id: Some("asdk_app_0123456789abcdef0123456789abcdef".to_string()), link_id: Some("link_fedcba9876543210fedcba9876543210".to_string()), - mcp_app_resource_uri: None, + mcp_app_ui: None, app_name: Some("Calendar".to_string()), action_name: Some("create_event".to_string()), plugin_id: None, @@ -1448,7 +1448,7 @@ fn mcp_tool_call_item_metadata_only_trusts_codex_apps_identity() { McpToolCallItemMetadata { connector_id: None, link_id: None, - mcp_app_resource_uri: None, + mcp_app_ui: None, app_name: None, action_name: None, plugin_id: None, @@ -1473,7 +1473,7 @@ async fn mcp_tool_call_item_includes_app_identity() { McpToolCallItemMetadata { connector_id: Some("asdk_app_0123456789abcdef0123456789abcdef".to_string()), link_id: Some("link_fedcba9876543210fedcba9876543210".to_string()), - mcp_app_resource_uri: None, + mcp_app_ui: None, app_name: Some("Calendar".to_string()), action_name: Some("create_event".to_string()), plugin_id: Some("sample@test".to_string()), @@ -1523,7 +1523,7 @@ async fn codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps plugin_id: None, tool_title: Some("Create Event".to_string()), tool_description: Some("Create a calendar event.".to_string()), - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: Some( serde_json::json!({ "resource_uri": "connector://calendar/tools/calendar_create_event", @@ -1974,7 +1974,7 @@ fn guardian_mcp_review_request_includes_annotations_when_present() { plugin_id: None, tool_title: None, tool_description: None, - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; @@ -2738,7 +2738,7 @@ async fn approve_mode_skips_when_annotations_do_not_require_approval() { plugin_id: None, tool_title: Some("Read Only Tool".to_string()), tool_description: None, - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; @@ -2819,7 +2819,7 @@ async fn guardian_mode_skips_auto_when_annotations_do_not_require_approval() { plugin_id: None, tool_title: Some("Read Only Tool".to_string()), tool_description: None, - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; @@ -2882,7 +2882,7 @@ async fn permission_request_hook_allows_mcp_tool_call() { plugin_id: None, tool_title: Some("Create entities".to_string()), tool_description: None, - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; @@ -3033,7 +3033,7 @@ async fn permission_request_hook_runs_after_remembered_mcp_approval() { plugin_id: None, tool_title: Some("Create entities".to_string()), tool_description: None, - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; @@ -3134,7 +3134,7 @@ async fn strict_auto_review_forces_guardian_for_mcp_policy_skip() { plugin_id: None, tool_title: Some("Dangerous Tool".to_string()), tool_description: Some("Reads calendar data.".to_string()), - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; @@ -3213,7 +3213,7 @@ async fn assert_mcp_user_approval_persistence( plugin_id: None, tool_title: Some("Create entities".to_string()), tool_description: None, - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; @@ -3299,7 +3299,7 @@ async fn prompt_mode_waits_for_approval_when_annotations_do_not_require_approval plugin_id: None, tool_title: Some("Read Only Tool".to_string()), tool_description: None, - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; @@ -3364,7 +3364,7 @@ async fn full_access_mode_skips_mcp_tool_approval_for_all_approval_modes() { plugin_id: None, tool_title: Some("Dangerous Tool".to_string()), tool_description: Some("Performs a risky action.".to_string()), - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; @@ -3423,7 +3423,7 @@ async fn approve_mode_skips_guardian_in_every_permission_mode() { plugin_id: None, tool_title: Some("Dangerous Tool".to_string()), tool_description: Some("Performs a risky action.".to_string()), - mcp_app_resource_uri: None, + mcp_app_ui: None, codex_apps_meta: None, openai_file_input_optional_fields: None, }; diff --git a/codex-rs/core/src/realtime_history_tests.rs b/codex-rs/core/src/realtime_history_tests.rs index 55e0019b21..a617917c10 100644 --- a/codex-rs/core/src/realtime_history_tests.rs +++ b/codex-rs/core/src/realtime_history_tests.rs @@ -95,6 +95,7 @@ fn mcp_tool_call(id: &str, server: &str, status: McpToolCallStatus) -> TurnItem arguments: serde_json::Value::Null, connector_id: None, mcp_app_resource_uri: None, + mcp_app_ui: None, link_id: None, app_name: None, action_name: None, diff --git a/codex-rs/core/src/tools/handlers/mcp_resource.rs b/codex-rs/core/src/tools/handlers/mcp_resource.rs index d6577f30d6..205954ec7e 100644 --- a/codex-rs/core/src/tools/handlers/mcp_resource.rs +++ b/codex-rs/core/src/tools/handlers/mcp_resource.rs @@ -222,6 +222,7 @@ async fn emit_tool_call_begin( arguments: arguments.unwrap_or(Value::Null), connector_id: None, mcp_app_resource_uri: None, + mcp_app_ui: None, link_id: None, app_name: None, action_name: None, @@ -266,6 +267,7 @@ async fn emit_tool_call_end( arguments: arguments.unwrap_or(Value::Null), connector_id: None, mcp_app_resource_uri: None, + mcp_app_ui: None, link_id: None, app_name: None, action_name: None, diff --git a/codex-rs/core/tests/suite/mcp_turn_metadata.rs b/codex-rs/core/tests/suite/mcp_turn_metadata.rs index a41884709b..7e01e1381b 100644 --- a/codex-rs/core/tests/suite/mcp_turn_metadata.rs +++ b/codex-rs/core/tests/suite/mcp_turn_metadata.rs @@ -14,6 +14,8 @@ use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; +use codex_protocol::items::McpAppDisplayMode; +use codex_protocol::items::McpAppUi; use codex_protocol::items::McpToolCallStatus; use codex_protocol::items::TurnItem; use codex_protocol::models::NetworkPermissions; @@ -23,6 +25,8 @@ use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::ElicitationAction; use codex_protocol::protocol::EventMsg; +use codex_protocol::protocol::ItemCompletedEvent; +use codex_protocol::protocol::ItemStartedEvent; use codex_protocol::protocol::Op; use codex_protocol::protocol::ThreadSettingsOverrides; use codex_protocol::protocol::TurnEnvironmentSelections; @@ -71,6 +75,142 @@ use wiremock::matchers::body_partial_json; use wiremock::matchers::method; use wiremock::matchers::path_regex; +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +#[test_case(json!({"ui": {"resourceUri": "ui://calendar/widget"}, "openai/ui": {"preferredModelDisplayMode": "fullscreen"}}), Some(McpAppDisplayMode::Fullscreen); "fullscreen")] +#[test_case(json!({"ui": {"resourceUri": "ui://calendar/widget"}}), Some(McpAppDisplayMode::Inline); "missing preference")] +#[test_case(json!({"ui": {"resourceUri": "ui://calendar/widget"}, "openai/ui": {"preferredModelDisplayMode": "unsupported"}}), Some(McpAppDisplayMode::Inline); "unsupported preference")] +#[test_case(json!({"openai/outputTemplate": "ui://calendar/widget"}), Some(McpAppDisplayMode::Inline); "legacy uri")] +#[test_case(json!({}), None; "result only ui")] +async fn mcp_app_ui_survives_tool_events_and_resume( + mut metadata: Value, + expected_mode: Option, +) -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let apps_server = AppsTestServer::mount(&server).await?; + metadata["connector_id"] = json!("calendar"); + metadata["connector_name"] = json!("Calendar"); + Mock::given(method("POST")) + .and(path_regex("^/api/codex/ps/mcp/?$")) + .and(body_partial_json(json!({"method": "tools/list"}))) + .respond_with(move |request: &Request| { + let body: Value = serde_json::from_slice(&request.body).expect("valid tools/list"); + ResponseTemplate::new(/*status*/ 200).set_body_json(json!({ + "jsonrpc": "2.0", + "id": body["id"], + "result": {"tools": [{ + "name": "calendar_create_event", + "description": "Create a calendar event.", + "inputSchema": {"type": "object", "properties": {}}, + "annotations": {"readOnlyHint": true}, + "_meta": metadata + }]} + })) + }) + .with_priority(/*priority*/ 1) + .mount(&server) + .await; + Mock::given(method("POST")) + .and(path_regex("^/api/codex/ps/mcp/?$")) + .and(body_partial_json(json!({"method": "tools/call"}))) + .respond_with(|request: &Request| { + let body: Value = serde_json::from_slice(&request.body).expect("valid tools/call"); + ResponseTemplate::new(/*status*/ 200).set_body_json(json!({ + "jsonrpc": "2.0", + "id": body["id"], + "result": { + "content": [{"type": "text", "text": "Calendar opened."}], + "_meta": {"openai/outputTemplate": "ui://calendar/result-widget"}, + "isError": false + } + })) + }) + .with_priority(/*priority*/ 1) + .mount(&server) + .await; + let call_id = "calendar-ui"; + let responses = mount_sse_sequence( + &server, + vec![ + sse(vec![ + ev_response_created("resp-calendar"), + ev_function_call_with_namespace( + call_id, + SEARCH_CALENDAR_NAMESPACE, + SEARCH_CALENDAR_CREATE_TOOL, + "{}", + ), + ev_completed("resp-calendar"), + ]), + sse(vec![ + ev_assistant_message("msg-done", "done"), + ev_completed("resp-done"), + ]), + ], + ) + .await; + let mut builder = search_capable_apps_builder(apps_server.chatgpt_base_url); + let test = builder.build_with_auto_env(&server).await?; + submit_user_turn( + &test, + "Use [$calendar](app://calendar) to open the calendar widget.", + AskForApproval::Never, + PermissionProfile::Disabled, + /*collaboration_mode*/ None, + ) + .await?; + + let expected_ui = expected_mode.map(|preferred_model_display_mode| McpAppUi { + resource_uri: "ui://calendar/widget".to_string(), + preferred_model_display_mode, + }); + let expected_uri = expected_ui.as_ref().map(|ui| ui.resource_uri.clone()); + let mut observed = Vec::new(); + let mut completed = None; + wait_for_event(&test.codex, |event| { + match event { + EventMsg::ItemStarted(ItemStartedEvent { + item: TurnItem::McpToolCall(item), + .. + }) + | EventMsg::ItemCompleted(ItemCompletedEvent { + item: TurnItem::McpToolCall(item), + .. + }) => { + observed.push((item.mcp_app_ui.clone(), item.mcp_app_resource_uri.clone())); + } + EventMsg::McpToolCallBegin(begin) => { + observed.push((begin.mcp_app_ui.clone(), begin.mcp_app_resource_uri.clone())); + } + EventMsg::McpToolCallEnd(end) => { + assert!(end.result.is_ok()); + observed.push((end.mcp_app_ui.clone(), end.mcp_app_resource_uri.clone())); + completed = Some(event.clone()); + } + _ => {} + } + matches!(event, EventMsg::TurnComplete(_)) + }) + .await; + assert_eq!(observed, vec![(expected_ui, expected_uri); 4]); + assert_eq!(responses.requests().len(), 2); + + let resumed = builder.restart(&server, &test).await?; + let completed_history = resumed + .session_configured + .initial_messages + .expect("resumed history") + .into_iter() + .filter(|event| matches!(event, EventMsg::McpToolCallEnd(end) if end.call_id == call_id)) + .collect::>(); + assert_eq!( + serde_json::to_value(completed_history)?, + serde_json::to_value(vec![completed.expect("completed MCP call")])? + ); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn auto_session_approval_is_scoped_to_tool_link_id() -> Result<()> { skip_if_no_network!(Ok(())); diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index eaa89dd658..1b2d49554a 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -2529,6 +2529,7 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> { }, connector_id: None, mcp_app_resource_uri: None, + mcp_app_ui: None, link_id: None, app_name: None, action_name: None, diff --git a/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs b/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs index 4159cc9ddd..5109848a6e 100644 --- a/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs +++ b/codex-rs/exec/src/event_processor_with_jsonl_output_tests.rs @@ -103,6 +103,7 @@ fn mcp_tool_call_result_preserves_meta_in_jsonl_event() { arguments: json!({"search_query": [{"q": "OpenAI Codex CLI documentation"}]}), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: Some(Box::new(codex_app_server_protocol::McpToolCallResult { diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs index 8068985fec..1586c9303d 100644 --- a/codex-rs/exec/tests/event_processor_with_json_output.rs +++ b/codex-rs/exec/tests/event_processor_with_json_output.rs @@ -492,6 +492,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() { arguments: json!({ "key": "value" }), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, @@ -512,6 +513,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() { arguments: json!({ "key": "value" }), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: Some(Box::new(McpToolCallResult { @@ -586,6 +588,7 @@ fn mcp_tool_call_failure_sets_failed_status() { arguments: json!({ "param": 42 }), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, @@ -637,6 +640,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() { arguments: serde_json::Value::Null, app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, @@ -657,6 +661,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() { arguments: serde_json::Value::Null, app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: Some(Box::new(McpToolCallResult { diff --git a/codex-rs/protocol/src/items.rs b/codex-rs/protocol/src/items.rs index 2b35643072..ccdf2f2055 100644 --- a/codex-rs/protocol/src/items.rs +++ b/codex-rs/protocol/src/items.rs @@ -425,6 +425,25 @@ pub struct FileChangeItem { pub stderr: Option, } +/// UI resource and display preference for model invocations, captured from the tool descriptor. +#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +#[ts(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct McpAppUi { + pub resource_uri: String, + pub preferred_model_display_mode: McpAppDisplayMode, +} + +#[derive(Debug, Clone, Copy, Deserialize, Serialize, TS, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +#[ts(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub enum McpAppDisplayMode { + Inline, + Fullscreen, +} + #[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)] #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase")] @@ -436,11 +455,15 @@ pub struct McpToolCallItem { #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub connector_id: Option, + /// Legacy compatibility field; prefer `mcp_app_ui.resource_uri` when available. #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] pub mcp_app_resource_uri: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] + pub mcp_app_ui: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] pub link_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] diff --git a/codex-rs/protocol/src/legacy_events.rs b/codex-rs/protocol/src/legacy_events.rs index b8f73e0641..fb59d1a0ad 100644 --- a/codex-rs/protocol/src/legacy_events.rs +++ b/codex-rs/protocol/src/legacy_events.rs @@ -508,6 +508,7 @@ impl McpToolCallItem { }, connector_id: self.connector_id.clone(), mcp_app_resource_uri: self.mcp_app_resource_uri.clone(), + mcp_app_ui: self.mcp_app_ui.clone(), link_id: self.link_id.clone(), app_name: self.app_name.clone(), action_name: self.action_name.clone(), @@ -532,6 +533,7 @@ impl McpToolCallItem { arguments: (!self.arguments.is_null()).then(|| self.arguments.clone()), }, mcp_app_resource_uri: self.mcp_app_resource_uri.clone(), + mcp_app_ui: self.mcp_app_ui.clone(), connector_id: self.connector_id.clone(), link_id: self.link_id.clone(), app_name: self.app_name.clone(), diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index b08780eb49..4f38880b9f 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -2670,6 +2670,9 @@ pub struct McpToolCallBeginEvent { pub mcp_app_resource_uri: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] + pub mcp_app_ui: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] pub link_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -2702,6 +2705,9 @@ pub struct McpToolCallEndEvent { pub mcp_app_resource_uri: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] + pub mcp_app_ui: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional)] pub link_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[ts(optional)] @@ -5392,6 +5398,7 @@ mod tests { arguments: json!({"arg": "value"}), connector_id: Some("connector".into()), mcp_app_resource_uri: Some("app://connector".into()), + mcp_app_ui: None, link_id: Some("link_123".into()), app_name: Some("Calendar".into()), action_name: Some("create_event".into()), @@ -5513,6 +5520,7 @@ mod tests { arguments: json!({"arg": "value"}), connector_id: Some("connector".into()), mcp_app_resource_uri: Some("app://connector".into()), + mcp_app_ui: None, link_id: Some("link_123".into()), app_name: Some("Calendar".into()), action_name: Some("create_event".into()), diff --git a/codex-rs/thread-store/src/local/rollout_migration/legacy_event.rs b/codex-rs/thread-store/src/local/rollout_migration/legacy_event.rs index 734327261a..ee0b5e82d8 100644 --- a/codex-rs/thread-store/src/local/rollout_migration/legacy_event.rs +++ b/codex-rs/thread-store/src/local/rollout_migration/legacy_event.rs @@ -185,6 +185,7 @@ pub(super) fn completed_item( .unwrap_or(serde_json::Value::Null), connector_id: event.connector_id.clone(), mcp_app_resource_uri: event.mcp_app_resource_uri.clone(), + mcp_app_ui: event.mcp_app_ui.clone(), link_id: event.link_id.clone(), app_name: event.app_name.clone(), action_name: event.action_name.clone(), diff --git a/codex-rs/tui/src/app/tests/buffered_replay.rs b/codex-rs/tui/src/app/tests/buffered_replay.rs index ee2bd6c48f..708efcacb6 100644 --- a/codex-rs/tui/src/app/tests/buffered_replay.rs +++ b/codex-rs/tui/src/app/tests/buffered_replay.rs @@ -654,6 +654,7 @@ async fn misalignment_buffered_replay_preserves_input_after_continuation() { arguments: serde_json::json!({}), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, diff --git a/codex-rs/tui/src/chatwidget/tests/computer_activity_tests.rs b/codex-rs/tui/src/chatwidget/tests/computer_activity_tests.rs index ccb91dad0c..32b5672813 100644 --- a/codex-rs/tui/src/chatwidget/tests/computer_activity_tests.rs +++ b/codex-rs/tui/src/chatwidget/tests/computer_activity_tests.rs @@ -12,6 +12,7 @@ fn computer_item( arguments: json!({"title": format!("Inspect page {id}"), "code": "await cua.getState()"}), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: Some(Box::new(codex_app_server_protocol::McpToolCallResult { diff --git a/codex-rs/tui/src/chatwidget/tests/history_replay.rs b/codex-rs/tui/src/chatwidget/tests/history_replay.rs index 5979f32662..8765054da2 100644 --- a/codex-rs/tui/src/chatwidget/tests/history_replay.rs +++ b/codex-rs/tui/src/chatwidget/tests/history_replay.rs @@ -1223,6 +1223,7 @@ async fn replayed_in_progress_mcp_tool_call_stays_active() { arguments: json!({"action": "wait"}), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, @@ -1258,6 +1259,7 @@ async fn failed_repl_mcp_tool_call_preserves_status_and_result() { arguments: json!({"title": "Inspect workspace"}), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: Some(Box::new(codex_app_server_protocol::McpToolCallResult { @@ -1326,6 +1328,7 @@ async fn deferred_mcp_lifecycle_events_keep_fifo_after_stream_finishes() { arguments: json!({"action": "wait"}), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: None, @@ -1343,6 +1346,7 @@ async fn deferred_mcp_lifecycle_events_keep_fifo_after_stream_finishes() { arguments: json!({"action": "wait"}), app_context: None, mcp_app_resource_uri: None, + mcp_app_ui: None, plugin_id: None, read_only_hint: None, result: Some(Box::new(codex_app_server_protocol::McpToolCallResult { diff --git a/sdk/python/src/openai_codex/generated/v2_all.py b/sdk/python/src/openai_codex/generated/v2_all.py index 8f500002c2..9250b97ae2 100644 --- a/sdk/python/src/openai_codex/generated/v2_all.py +++ b/sdk/python/src/openai_codex/generated/v2_all.py @@ -2461,6 +2461,21 @@ class MarketplaceUpgradeResponse(BaseModel): upgraded_roots: Annotated[list[AbsolutePathBuf], Field(alias="upgradedRoots")] +class McpAppDisplayMode(Enum): + inline = "inline" + fullscreen = "fullscreen" + + +class McpAppUi(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + preferred_model_display_mode: Annotated[ + McpAppDisplayMode, Field(alias="preferredModelDisplayMode") + ] + resource_uri: Annotated[str, Field(alias="resourceUri")] + + class McpAuthStatus(Enum): unknown = "unknown" unsupported = "unsupported" @@ -5303,7 +5318,14 @@ class McpToolCallThreadItem(BaseModel): str | None, Field( alias="mcpAppResourceUri", - description="Deprecated: use `appContext.resourceUri` instead.", + description="Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", + ), + ] = None + mcp_app_ui: Annotated[ + McpAppUi | None, + Field( + alias="mcpAppUi", + description="Presentation captured from the invoked descriptor; absent in older history.", ), ] = None plugin_id: Annotated[str | None, Field(alias="pluginId")] = None