Emit Trusted MCP App Identity on Tool-Call Items (#27132)

## Summary

- Add optional `appContext` to app-server MCP tool-call items with
trusted `connectorId`, `linkId`, and `mcpAppResourceUri` metadata.
- Preserve that context across tool-call events, persisted history,
reconnects, and thread resume.
- Keep the deprecated top-level `mcpAppResourceUri` temporarily for
client migration.

The consumer contract is `{ appContext: { connectorId, linkId,
mcpAppResourceUri }, tool }`.

## Validation

- Full GitHub Actions suite passes, including CLA, Bazel tests, clippy,
release builds, and argument-comment lint.

---------

Co-authored-by: martinauyeung-oai <280153141+martinauyeung-oai@users.noreply.github.com>
This commit is contained in:
martinauyeung-oai
2026-06-18 14:02:54 -07:00
committed by GitHub
Unverified
parent 9bcc09f9f7
commit 765309d5a6
40 changed files with 899 additions and 15 deletions
@@ -4121,6 +4121,7 @@ async fn turn_event_counts_completed_tool_items() {
tool: "search".to_string(),
status,
arguments: json!({}),
app_context: None,
mcp_app_resource_uri: None,
plugin_id: Some("sample@test".to_string()),
result: None,
@@ -2396,6 +2396,29 @@
],
"type": "object"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -4066,6 +4089,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -4089,6 +4122,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -12119,6 +12119,29 @@
"title": "McpServerToolCallResponse",
"type": "object"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -17511,6 +17534,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/v2/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -17534,6 +17567,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -8543,6 +8543,29 @@
"title": "McpServerToolCallResponse",
"type": "object"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -15290,6 +15313,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -15313,6 +15346,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -297,6 +297,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -784,6 +807,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -807,6 +840,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -297,6 +297,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -784,6 +807,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -807,6 +840,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -434,6 +434,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -928,6 +951,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -951,6 +984,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -539,6 +539,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1420,6 +1443,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -1443,6 +1476,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -460,6 +460,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1235,6 +1258,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -1258,6 +1291,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -460,6 +460,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1235,6 +1258,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -1258,6 +1291,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -460,6 +460,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1235,6 +1258,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -1258,6 +1291,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -539,6 +539,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1420,6 +1443,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -1443,6 +1476,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -460,6 +460,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1235,6 +1258,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -1258,6 +1291,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -539,6 +539,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1420,6 +1443,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -1443,6 +1476,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -460,6 +460,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1235,6 +1258,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -1258,6 +1291,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -460,6 +460,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -1235,6 +1258,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -1258,6 +1291,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -434,6 +434,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -928,6 +951,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -951,6 +984,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -434,6 +434,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -928,6 +951,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -951,6 +984,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -434,6 +434,29 @@
"LegacyAppPathString": {
"type": "string"
},
"McpToolCallAppContext": {
"properties": {
"connectorId": {
"type": "string"
},
"linkId": {
"type": [
"string",
"null"
]
},
"resourceUri": {
"type": [
"string",
"null"
]
}
},
"required": [
"connectorId"
],
"type": "object"
},
"McpToolCallError": {
"properties": {
"message": {
@@ -928,6 +951,16 @@
},
{
"properties": {
"appContext": {
"anyOf": [
{
"$ref": "#/definitions/McpToolCallAppContext"
},
{
"type": "null"
}
]
},
"arguments": true,
"durationMs": {
"description": "The duration of the MCP tool call in milliseconds.",
@@ -951,6 +984,7 @@
"type": "string"
},
"mcpAppResourceUri": {
"description": "Deprecated: use `appContext.resourceUri` instead.",
"type": [
"string",
"null"
@@ -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 McpToolCallAppContext = { connectorId: string, linkId: string | null, resourceUri: string | null, };
@@ -16,6 +16,7 @@ import type { DynamicToolCallOutputContentItem } from "./DynamicToolCallOutputCo
import type { DynamicToolCallStatus } from "./DynamicToolCallStatus";
import type { FileUpdateChange } from "./FileUpdateChange";
import type { HookPromptFragment } from "./HookPromptFragment";
import type { McpToolCallAppContext } from "./McpToolCallAppContext";
import type { McpToolCallError } from "./McpToolCallError";
import type { McpToolCallResult } from "./McpToolCallResult";
import type { McpToolCallStatus } from "./McpToolCallStatus";
@@ -55,7 +56,11 @@ exitCode: number | null,
/**
* The duration of the command execution in milliseconds.
*/
durationMs: number | null, } | { "type": "fileChange", id: string, changes: Array<FileUpdateChange>, status: PatchApplyStatus, } | { "type": "mcpToolCall", id: string, server: string, tool: string, status: McpToolCallStatus, arguments: JsonValue, mcpAppResourceUri?: string, pluginId: string | null, result: McpToolCallResult | null, error: McpToolCallError | null,
durationMs: number | null, } | { "type": "fileChange", id: string, changes: Array<FileUpdateChange>, status: PatchApplyStatus, } | { "type": "mcpToolCall", id: string, server: string, tool: string, status: McpToolCallStatus, arguments: JsonValue, appContext: McpToolCallAppContext | null,
/**
* Deprecated: use `appContext.resourceUri` instead.
*/
mcpAppResourceUri?: string, pluginId: string | null, result: McpToolCallResult | null, error: McpToolCallError | null,
/**
* The duration of the MCP tool call in milliseconds.
*/
@@ -243,6 +243,7 @@ export type { McpServerStatusDetail } from "./McpServerStatusDetail";
export type { McpServerStatusUpdatedNotification } from "./McpServerStatusUpdatedNotification";
export type { McpServerToolCallParams } from "./McpServerToolCallParams";
export type { McpServerToolCallResponse } from "./McpServerToolCallResponse";
export type { McpToolCallAppContext } from "./McpToolCallAppContext";
export type { McpToolCallError } from "./McpToolCallError";
export type { McpToolCallProgressNotification } from "./McpToolCallProgressNotification";
export type { McpToolCallResult } from "./McpToolCallResult";
@@ -10,6 +10,7 @@ use crate::protocol::v2::CollabAgentToolCallStatus;
use crate::protocol::v2::CommandExecutionStatus;
use crate::protocol::v2::DynamicToolCallOutputContentItem;
use crate::protocol::v2::DynamicToolCallStatus;
use crate::protocol::v2::McpToolCallAppContext;
use crate::protocol::v2::McpToolCallError;
use crate::protocol::v2::McpToolCallResult;
use crate::protocol::v2::McpToolCallStatus;
@@ -765,6 +766,14 @@ impl ThreadHistoryBuilder {
.arguments
.clone()
.unwrap_or(serde_json::Value::Null),
app_context: payload
.connector_id
.clone()
.map(|connector_id| McpToolCallAppContext {
connector_id,
link_id: payload.link_id.clone(),
resource_uri: payload.mcp_app_resource_uri.clone(),
}),
mcp_app_resource_uri: payload.mcp_app_resource_uri.clone(),
plugin_id: payload.plugin_id.clone(),
result: None,
@@ -807,6 +816,14 @@ impl ThreadHistoryBuilder {
.arguments
.clone()
.unwrap_or(serde_json::Value::Null),
app_context: payload
.connector_id
.clone()
.map(|connector_id| McpToolCallAppContext {
connector_id,
link_id: payload.link_id.clone(),
resource_uri: payload.mcp_app_resource_uri.clone(),
}),
mcp_app_resource_uri: payload.mcp_app_resource_uri.clone(),
plugin_id: payload.plugin_id.clone(),
result,
@@ -1567,7 +1584,6 @@ mod tests {
use codex_protocol::protocol::DynamicToolCallResponseEvent;
use codex_protocol::protocol::ExecCommandEndEvent;
use codex_protocol::protocol::ExecCommandSource;
use codex_protocol::protocol::ItemCompletedEvent;
use codex_protocol::protocol::ItemStartedEvent;
use codex_protocol::protocol::McpInvocation;
use codex_protocol::protocol::McpToolCallEndEvent;
@@ -2397,7 +2413,9 @@ mod tests {
tool: "lookup".into(),
arguments: Some(serde_json::json!({"id":"123"})),
},
connector_id: None,
mcp_app_resource_uri: None,
link_id: None,
plugin_id: None,
duration: Duration::from_millis(8),
result: Err("boom".into()),
@@ -2447,6 +2465,7 @@ mod tests {
tool: "lookup".into(),
status: McpToolCallStatus::Failed,
arguments: serde_json::json!({"id":"123"}),
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
@@ -2475,7 +2494,9 @@ mod tests {
tool: "lookup".into(),
arguments: Some(serde_json::json!({"id":"123"})),
},
connector_id: Some("calendar".into()),
mcp_app_resource_uri: Some("ui://widget/lookup.html".into()),
link_id: Some("link_calendar".into()),
plugin_id: Some("sample@test".into()),
duration: Duration::from_millis(8),
result: Ok(CallToolResult {
@@ -2506,6 +2527,11 @@ mod tests {
tool: "lookup".into(),
status: McpToolCallStatus::Completed,
arguments: serde_json::json!({"id":"123"}),
app_context: Some(McpToolCallAppContext {
connector_id: "calendar".into(),
link_id: Some("link_calendar".into()),
resource_uri: Some("ui://widget/lookup.html".into()),
}),
mcp_app_resource_uri: Some("ui://widget/lookup.html".into()),
plugin_id: Some("sample@test".into()),
result: Some(Box::new(McpToolCallResult {
@@ -290,8 +290,10 @@ pub enum ThreadItem {
tool: String,
status: McpToolCallStatus,
arguments: JsonValue,
app_context: Option<McpToolCallAppContext>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
/// Deprecated: use `appContext.resourceUri` instead.
mcp_app_resource_uri: Option<String>,
plugin_id: Option<String>,
result: Option<Box<McpToolCallResult>>,
@@ -384,6 +386,15 @@ pub enum ThreadItem {
ContextCompaction { id: String },
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase", export_to = "v2/")]
pub struct McpToolCallAppContext {
pub connector_id: String,
pub link_id: Option<String>,
pub resource_uri: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase", export_to = "v2/")]
@@ -877,6 +888,11 @@ impl From<CoreTurnItem> for ThreadItem {
tool: mcp.tool,
status: McpToolCallStatus::from(mcp.status),
arguments: mcp.arguments,
app_context: mcp.connector_id.map(|connector_id| McpToolCallAppContext {
connector_id,
link_id: mcp.link_id,
resource_uri: mcp.mcp_app_resource_uri.clone(),
}),
mcp_app_resource_uri: mcp.mcp_app_resource_uri,
plugin_id: mcp.plugin_id,
result: mcp.result.map(McpToolCallResult::from).map(Box::new),
@@ -2615,7 +2615,9 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
server: "server".to_string(),
tool: "tool".to_string(),
arguments: json!({"arg": "value"}),
connector_id: Some("calendar".to_string()),
mcp_app_resource_uri: Some("app://connector".to_string()),
link_id: Some("link_calendar".to_string()),
plugin_id: Some("sample@test".to_string()),
status: CoreMcpToolCallStatus::InProgress,
result: None,
@@ -2631,6 +2633,11 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
tool: "tool".to_string(),
status: McpToolCallStatus::InProgress,
arguments: json!({"arg": "value"}),
app_context: Some(McpToolCallAppContext {
connector_id: "calendar".to_string(),
link_id: Some("link_calendar".to_string()),
resource_uri: Some("app://connector".to_string()),
}),
mcp_app_resource_uri: Some("app://connector".to_string()),
plugin_id: Some("sample@test".to_string()),
result: None,
@@ -2644,7 +2651,9 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
server: "server".to_string(),
tool: "tool".to_string(),
arguments: JsonValue::Null,
connector_id: None,
mcp_app_resource_uri: None,
link_id: None,
plugin_id: None,
status: CoreMcpToolCallStatus::Completed,
result: Some(CallToolResult {
@@ -2665,6 +2674,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
tool: "tool".to_string(),
status: McpToolCallStatus::Completed,
arguments: JsonValue::Null,
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: Some(Box::new(McpToolCallResult {
@@ -2678,6 +2688,49 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
);
}
#[test]
fn mcp_tool_call_app_context_serializes_connector_id() {
let item = ThreadItem::McpToolCall {
id: "mcp-1".to_string(),
server: "codex_apps".to_string(),
tool: "calendar.create_event".to_string(),
status: McpToolCallStatus::InProgress,
arguments: json!({}),
app_context: Some(McpToolCallAppContext {
connector_id: "calendar".to_string(),
link_id: Some("link_calendar".to_string()),
resource_uri: Some("app://connector".to_string()),
}),
mcp_app_resource_uri: Some("app://connector".to_string()),
plugin_id: None,
result: None,
error: None,
duration_ms: None,
};
assert_eq!(
serde_json::to_value(item).expect("MCP tool call should serialize"),
json!({
"type": "mcpToolCall",
"id": "mcp-1",
"server": "codex_apps",
"tool": "calendar.create_event",
"status": "inProgress",
"arguments": {},
"appContext": {
"connectorId": "calendar",
"linkId": "link_calendar",
"resourceUri": "app://connector",
},
"mcpAppResourceUri": "app://connector",
"pluginId": null,
"result": null,
"error": null,
"durationMs": null,
})
);
}
#[test]
fn user_input_into_core_preserves_image_detail() {
assert_eq!(
+1 -1
View File
@@ -1365,7 +1365,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, mcpAppResourceUri?, pluginId, result?, error?}` describing MCP calls; `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}` 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`.
- `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.
@@ -52,6 +52,7 @@ fn redacted_mcp_tool_call_result() -> McpToolCallResult {
#[cfg(test)]
mod tests {
use super::*;
use codex_app_server_protocol::McpToolCallAppContext;
use codex_app_server_protocol::McpToolCallError;
use codex_app_server_protocol::McpToolCallStatus;
use codex_app_server_protocol::SessionSource;
@@ -78,6 +79,11 @@ mod tests {
tool: "lookup".to_string(),
status: McpToolCallStatus::Completed,
arguments: serde_json::json!({"secret":"argument"}),
app_context: Some(McpToolCallAppContext {
connector_id: "calendar".to_string(),
link_id: Some("link_calendar".to_string()),
resource_uri: Some("ui://widget/lookup.html".to_string()),
}),
mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()),
plugin_id: Some("sample@test".to_string()),
result: Some(Box::new(McpToolCallResult {
@@ -120,6 +126,11 @@ mod tests {
tool: "lookup".to_string(),
status: McpToolCallStatus::Completed,
arguments: JsonValue::String(REDACTED_PAYLOAD.to_string()),
app_context: Some(McpToolCallAppContext {
connector_id: "calendar".to_string(),
link_id: Some("link_calendar".to_string()),
resource_uri: Some("ui://widget/lookup.html".to_string()),
}),
mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()),
plugin_id: Some("sample@test".to_string()),
result: Some(Box::new(redacted_mcp_tool_call_result())),
@@ -137,6 +148,7 @@ mod tests {
tool: "lookup".to_string(),
status: McpToolCallStatus::Failed,
arguments: serde_json::json!({"secret":"argument"}),
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
@@ -156,6 +168,7 @@ mod tests {
tool: "lookup".to_string(),
status: McpToolCallStatus::Failed,
arguments: JsonValue::String(REDACTED_PAYLOAD.to_string()),
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
@@ -513,6 +513,7 @@ url = "{mcp_server_url}/mcp"
tool,
status,
arguments: json!({ "message": LARGE_RESPONSE_MESSAGE }),
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: Some(result),
@@ -23,6 +23,7 @@ use codex_app_server_protocol::FileChangeRequestApprovalResponse;
use codex_app_server_protocol::ItemStartedNotification;
use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::McpToolCallAppContext;
use codex_app_server_protocol::PatchApplyStatus;
use codex_app_server_protocol::PatchChangeKind;
use codex_app_server_protocol::RequestId;
@@ -729,6 +730,7 @@ async fn thread_resume_redacts_payloads_for_chatgpt_remote_clients() -> Result<(
.expect("remote resume should include redacted MCP item");
let ThreadItem::McpToolCall {
arguments,
app_context,
result,
error,
..
@@ -737,6 +739,14 @@ async fn thread_resume_redacts_payloads_for_chatgpt_remote_clients() -> Result<(
unreachable!("matched MCP item");
};
assert_eq!(arguments, &json!("[redacted]"));
assert_eq!(
app_context,
&Some(McpToolCallAppContext {
connector_id: "calendar".to_string(),
link_id: Some("link_calendar".to_string()),
resource_uri: Some("ui://widget/lookup.html".to_string()),
})
);
let result = result.as_ref().expect("redacted MCP result");
assert_eq!(
result.content,
@@ -770,12 +780,23 @@ async fn thread_resume_redacts_payloads_for_chatgpt_remote_clients() -> Result<(
.find(|item| matches!(item, ThreadItem::McpToolCall { .. }))
.expect("normal resume should include MCP item");
let ThreadItem::McpToolCall {
arguments, result, ..
arguments,
app_context,
result,
..
} = normal_mcp_item
else {
unreachable!("matched MCP item");
};
assert_eq!(arguments, &json!({"secret":"argument"}));
assert_eq!(
app_context,
&Some(McpToolCallAppContext {
connector_id: "calendar".to_string(),
link_id: Some("link_calendar".to_string()),
resource_uri: Some("ui://widget/lookup.html".to_string()),
})
);
let result = result.as_ref().expect("normal MCP result");
assert_eq!(
result.content,
@@ -877,7 +898,9 @@ fn append_resume_redaction_history(
tool: "lookup".to_string(),
arguments: Some(json!({"secret":"argument"})),
},
connector_id: Some("calendar".to_string()),
mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()),
link_id: Some("link_calendar".to_string()),
plugin_id: None,
duration: Duration::from_millis(8),
result: Ok(CallToolResult {
+35 -9
View File
@@ -143,14 +143,7 @@ pub(crate) async fn handle_mcp_tool_call(
let metadata =
lookup_mcp_tool_metadata(sess.as_ref(), turn_context.as_ref(), &server, &tool_name).await;
let item_metadata = McpToolCallItemMetadata {
mcp_app_resource_uri: metadata
.as_ref()
.and_then(|metadata| metadata.mcp_app_resource_uri.clone()),
plugin_id: metadata
.as_ref()
.and_then(|metadata| metadata.plugin_id.clone()),
};
let item_metadata = McpToolCallItemMetadata::from_tool_metadata(&server, metadata.as_ref());
let app_tool_policy = if server == CODEX_APPS_MCP_SERVER_NAME {
let annotations = metadata
.as_ref()
@@ -312,12 +305,32 @@ pub(crate) struct HandledMcpToolCall {
pub(crate) tool_input: JsonValue,
}
#[derive(Clone)]
#[derive(Clone, Debug, PartialEq, Eq)]
struct McpToolCallItemMetadata {
connector_id: Option<String>,
link_id: Option<String>,
mcp_app_resource_uri: Option<String>,
plugin_id: Option<String>,
}
impl McpToolCallItemMetadata {
fn from_tool_metadata(server: &str, metadata: Option<&McpToolApprovalMetadata>) -> Self {
let trusted_mcp_app_metadata = if server == CODEX_APPS_MCP_SERVER_NAME {
metadata
} else {
None
};
Self {
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()),
plugin_id: metadata.and_then(|metadata| metadata.plugin_id.clone()),
}
}
}
async fn handle_approved_mcp_tool_call(
sess: &Session,
turn_context: &TurnContext,
@@ -892,7 +905,9 @@ async fn notify_mcp_tool_call_started(
server,
tool,
arguments: arguments.unwrap_or(JsonValue::Null),
connector_id: item_metadata.connector_id,
mcp_app_resource_uri: item_metadata.mcp_app_resource_uri,
link_id: item_metadata.link_id,
plugin_id: item_metadata.plugin_id,
status: McpToolCallStatus::InProgress,
result: None,
@@ -932,7 +947,9 @@ async fn notify_mcp_tool_call_completed(
server,
tool,
arguments: arguments.unwrap_or(JsonValue::Null),
connector_id: item_metadata.connector_id,
mcp_app_resource_uri: item_metadata.mcp_app_resource_uri,
link_id: item_metadata.link_id,
plugin_id: item_metadata.plugin_id,
status,
result,
@@ -998,6 +1015,7 @@ enum McpToolApprovalDecision {
pub(crate) struct McpToolApprovalMetadata {
annotations: Option<ToolAnnotations>,
connector_id: Option<String>,
link_id: Option<String>,
connector_name: Option<String>,
connector_description: Option<String>,
plugin_id: Option<String>,
@@ -1010,6 +1028,7 @@ pub(crate) struct McpToolApprovalMetadata {
const MCP_TOOL_OPENAI_OUTPUT_TEMPLATE_META_KEY: &str = "openai/outputTemplate";
const MCP_TOOL_UI_RESOURCE_URI_META_KEY: &str = "ui/resourceUri";
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";
@@ -1493,6 +1512,13 @@ pub(crate) async fn lookup_mcp_tool_metadata(
Some(McpToolApprovalMetadata {
annotations: tool_info.tool.annotations,
connector_id: tool_info.connector_id,
link_id: tool_info
.tool
.meta
.as_ref()
.and_then(|meta| meta.get(MCP_TOOL_LINK_ID_META_KEY))
.and_then(serde_json::Value::as_str)
.map(str::to_string),
connector_name: tool_info.connector_name,
connector_description,
plugin_id,
+54 -2
View File
@@ -78,6 +78,7 @@ fn approval_metadata(
McpToolApprovalMetadata {
annotations: None,
connector_id: connector_id.map(str::to_string),
link_id: None,
connector_name: connector_name.map(str::to_string),
connector_description: connector_description.map(str::to_string),
plugin_id: None,
@@ -1149,8 +1150,39 @@ async fn plugin_mcp_tool_call_request_meta_includes_plugin_id() {
);
}
#[test]
fn mcp_tool_call_item_metadata_only_trusts_codex_apps_identity() {
let mut metadata = approval_metadata(
Some("asdk_app_0123456789abcdef0123456789abcdef"),
/*connector_name*/ None,
/*connector_description*/ None,
/*tool_title*/ None,
/*tool_description*/ None,
);
metadata.link_id = Some("link_fedcba9876543210fedcba9876543210".to_string());
assert_eq!(
McpToolCallItemMetadata::from_tool_metadata(CODEX_APPS_MCP_SERVER_NAME, Some(&metadata),),
McpToolCallItemMetadata {
connector_id: Some("asdk_app_0123456789abcdef0123456789abcdef".to_string()),
link_id: Some("link_fedcba9876543210fedcba9876543210".to_string()),
mcp_app_resource_uri: None,
plugin_id: None,
}
);
assert_eq!(
McpToolCallItemMetadata::from_tool_metadata("custom_server", Some(&metadata)),
McpToolCallItemMetadata {
connector_id: None,
link_id: None,
mcp_app_resource_uri: None,
plugin_id: None,
}
);
}
#[tokio::test]
async fn mcp_tool_call_item_includes_plugin_id() {
async fn mcp_tool_call_item_includes_app_identity() {
let (session, turn_context, rx_event) = make_session_and_context_with_rx().await;
notify_mcp_tool_call_started(
@@ -1158,11 +1190,13 @@ async fn mcp_tool_call_item_includes_plugin_id() {
&turn_context,
"call-plugin",
McpInvocation {
server: "sample".to_string(),
server: CODEX_APPS_MCP_SERVER_NAME.to_string(),
tool: "echo".to_string(),
arguments: None,
},
McpToolCallItemMetadata {
connector_id: Some("asdk_app_0123456789abcdef0123456789abcdef".to_string()),
link_id: Some("link_fedcba9876543210fedcba9876543210".to_string()),
mcp_app_resource_uri: None,
plugin_id: Some("sample@test".to_string()),
},
@@ -1180,6 +1214,14 @@ async fn mcp_tool_call_item_includes_plugin_id() {
panic!("expected MCP tool call item");
};
assert_eq!(
item.connector_id.as_deref(),
Some("asdk_app_0123456789abcdef0123456789abcdef")
);
assert_eq!(
item.link_id.as_deref(),
Some("link_fedcba9876543210fedcba9876543210")
);
assert_eq!(item.plugin_id.as_deref(), Some("sample@test"));
}
@@ -1193,6 +1235,7 @@ async fn codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps
let metadata = McpToolApprovalMetadata {
annotations: None,
connector_id: Some("calendar".to_string()),
link_id: None,
connector_name: Some("Calendar".to_string()),
connector_description: Some("Manage events".to_string()),
plugin_id: None,
@@ -1661,6 +1704,7 @@ fn guardian_mcp_review_request_includes_annotations_when_present() {
let metadata = McpToolApprovalMetadata {
annotations: Some(annotations(Some(false), Some(true), Some(true))),
connector_id: None,
link_id: None,
connector_name: None,
connector_description: None,
plugin_id: None,
@@ -2326,6 +2370,7 @@ async fn approve_mode_skips_when_annotations_do_not_require_approval() {
/*open_world*/ None,
)),
connector_id: None,
link_id: None,
connector_name: None,
connector_description: None,
plugin_id: None,
@@ -2400,6 +2445,7 @@ async fn guardian_mode_skips_auto_when_annotations_do_not_require_approval() {
/*open_world*/ None,
)),
connector_id: None,
link_id: None,
connector_name: None,
connector_description: None,
plugin_id: None,
@@ -2457,6 +2503,7 @@ async fn permission_request_hook_allows_mcp_tool_call() {
/*open_world*/ None,
)),
connector_id: None,
link_id: None,
connector_name: None,
connector_description: None,
plugin_id: None,
@@ -2593,6 +2640,7 @@ async fn permission_request_hook_runs_after_remembered_mcp_approval() {
/*open_world*/ None,
)),
connector_id: None,
link_id: None,
connector_name: None,
connector_description: None,
plugin_id: None,
@@ -2680,6 +2728,7 @@ async fn guardian_mode_mcp_denial_returns_rationale_message() {
let metadata = McpToolApprovalMetadata {
annotations: Some(annotations(Some(false), Some(true), Some(true))),
connector_id: None,
link_id: None,
connector_name: None,
connector_description: None,
plugin_id: None,
@@ -2734,6 +2783,7 @@ async fn prompt_mode_waits_for_approval_when_annotations_do_not_require_approval
/*open_world*/ None,
)),
connector_id: None,
link_id: None,
connector_name: None,
connector_description: None,
plugin_id: None,
@@ -2789,6 +2839,7 @@ async fn full_access_mode_skips_mcp_tool_approval_for_all_approval_modes() {
let metadata = McpToolApprovalMetadata {
annotations: Some(annotations(Some(false), Some(true), Some(true))),
connector_id: Some("calendar".to_string()),
link_id: None,
connector_name: Some("Calendar".to_string()),
connector_description: Some("Manage events".to_string()),
plugin_id: None,
@@ -2842,6 +2893,7 @@ async fn approve_mode_skips_guardian_in_every_permission_mode() {
let metadata = McpToolApprovalMetadata {
annotations: Some(annotations(Some(false), Some(true), Some(true))),
connector_id: Some("calendar".to_string()),
link_id: None,
connector_name: Some("Calendar".to_string()),
connector_description: Some("Manage events".to_string()),
plugin_id: None,
@@ -203,7 +203,9 @@ async fn emit_tool_call_begin(
server,
tool,
arguments: arguments.unwrap_or(Value::Null),
connector_id: None,
mcp_app_resource_uri: None,
link_id: None,
plugin_id: None,
status: McpToolCallStatus::InProgress,
result: None,
@@ -242,7 +244,9 @@ async fn emit_tool_call_end(
server,
tool,
arguments: arguments.unwrap_or(Value::Null),
connector_id: None,
mcp_app_resource_uri: None,
link_id: None,
plugin_id: None,
status,
result,
@@ -17,6 +17,7 @@ use wiremock::matchers::path;
use wiremock::matchers::path_regex;
const CONNECTOR_ID: &str = "calendar";
pub const LINK_ID: &str = "link_calendar";
const CONNECTOR_NAME: &str = "Calendar";
const DISCOVERABLE_CALENDAR_ID: &str = "connector_2128aebfecb84f64a069897515042a44";
const DISCOVERABLE_GMAIL_ID: &str = "connector_68df038e0ba48191908c8434991bbac2";
@@ -352,6 +353,7 @@ impl Respond for CodexAppsJsonRpcResponder {
},
"_meta": {
"connector_id": CONNECTOR_ID,
"link_id": LINK_ID,
"connector_name": self.connector_name.clone(),
"connector_description": self.connector_description.clone(),
"openai/outputTemplate": CALENDAR_CREATE_EVENT_MCP_APP_RESOURCE_URI,
@@ -378,6 +380,7 @@ impl Respond for CodexAppsJsonRpcResponder {
},
"_meta": {
"connector_id": CONNECTOR_ID,
"link_id": LINK_ID,
"connector_name": self.connector_name.clone(),
"connector_description": self.connector_description.clone(),
"_codex_apps": {
@@ -410,6 +413,7 @@ impl Respond for CodexAppsJsonRpcResponder {
},
"_meta": {
"connector_id": CONNECTOR_ID,
"link_id": LINK_ID,
"connector_name": self.connector_name.clone(),
"connector_description": self.connector_description.clone(),
"openai/fileParams": ["file"],
+2
View File
@@ -1337,7 +1337,9 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> {
tool: "image".to_string(),
arguments: Some(json!({})),
},
connector_id: None,
mcp_app_resource_uri: None,
link_id: None,
plugin_id: None,
},
);
+3
View File
@@ -25,6 +25,7 @@ use core_test_support::apps_test_server::CALENDAR_CREATE_EVENT_MCP_APP_RESOURCE_
use core_test_support::apps_test_server::CALENDAR_CREATE_EVENT_RESOURCE_URI;
use core_test_support::apps_test_server::DIRECT_CALENDAR_CREATE_EVENT_TOOL as CALENDAR_CREATE_TOOL;
use core_test_support::apps_test_server::DIRECT_CALENDAR_LIST_EVENTS_TOOL as CALENDAR_LIST_TOOL;
use core_test_support::apps_test_server::LINK_ID;
use core_test_support::apps_test_server::SEARCH_CALENDAR_APP_ONLY_TOOL;
use core_test_support::apps_test_server::SEARCH_CALENDAR_CREATE_TOOL;
use core_test_support::apps_test_server::SEARCH_CALENDAR_LIST_TOOL;
@@ -559,10 +560,12 @@ async fn tool_search_returns_deferred_tools_without_follow_up_tool_injection() -
unreachable!("event guard guarantees McpToolCallEnd");
};
assert_eq!(end.call_id, "calendar-call-1");
assert_eq!(end.connector_id.as_deref(), Some("calendar"));
assert_eq!(
end.mcp_app_resource_uri.as_deref(),
Some(CALENDAR_CREATE_EVENT_MCP_APP_RESOURCE_URI)
);
assert_eq!(end.link_id.as_deref(), Some(LINK_ID));
assert_eq!(
end.invocation,
McpInvocation {
@@ -98,6 +98,7 @@ fn mcp_tool_call_result_preserves_meta_in_jsonl_event() {
tool: "web_run".to_string(),
status: McpToolCallStatus::Completed,
arguments: json!({"search_query": [{"q": "OpenAI Codex CLI documentation"}]}),
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: Some(Box::new(codex_app_server_protocol::McpToolCallResult {
@@ -478,6 +478,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() {
tool: "tool_x".to_string(),
status: ApiMcpToolCallStatus::InProgress,
arguments: json!({ "key": "value" }),
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
@@ -496,6 +497,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() {
tool: "tool_x".to_string(),
status: ApiMcpToolCallStatus::Completed,
arguments: json!({ "key": "value" }),
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: Some(Box::new(McpToolCallResult {
@@ -568,6 +570,7 @@ fn mcp_tool_call_failure_sets_failed_status() {
tool: "tool_y".to_string(),
status: ApiMcpToolCallStatus::Failed,
arguments: json!({ "param": 42 }),
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
@@ -617,6 +620,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() {
tool: "tool_z".to_string(),
status: ApiMcpToolCallStatus::InProgress,
arguments: serde_json::Value::Null,
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
@@ -635,6 +639,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() {
tool: "tool_z".to_string(),
status: ApiMcpToolCallStatus::Completed,
arguments: serde_json::Value::Null,
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: Some(Box::new(McpToolCallResult {
+10
View File
@@ -188,9 +188,15 @@ pub struct McpToolCallItem {
pub arguments: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub connector_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mcp_app_resource_uri: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub link_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub plugin_id: Option<String>,
pub status: McpToolCallStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -546,7 +552,9 @@ impl McpToolCallItem {
tool: self.tool.clone(),
arguments: (!self.arguments.is_null()).then(|| self.arguments.clone()),
},
connector_id: self.connector_id.clone(),
mcp_app_resource_uri: self.mcp_app_resource_uri.clone(),
link_id: self.link_id.clone(),
plugin_id: self.plugin_id.clone(),
})
}
@@ -566,6 +574,8 @@ impl McpToolCallItem {
arguments: (!self.arguments.is_null()).then(|| self.arguments.clone()),
},
mcp_app_resource_uri: self.mcp_app_resource_uri.clone(),
connector_id: self.connector_id.clone(),
link_id: self.link_id.clone(),
plugin_id: self.plugin_id.clone(),
duration: self.duration?,
result,
+20
View File
@@ -2324,9 +2324,15 @@ pub struct McpToolCallBeginEvent {
pub invocation: McpInvocation,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub connector_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mcp_app_resource_uri: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub link_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub plugin_id: Option<String>,
}
@@ -2337,9 +2343,15 @@ pub struct McpToolCallEndEvent {
pub invocation: McpInvocation,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub connector_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub mcp_app_resource_uri: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub link_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub plugin_id: Option<String>,
#[ts(type = "string")]
pub duration: Duration,
@@ -4940,7 +4952,9 @@ mod tests {
server: "server".into(),
tool: "tool".into(),
arguments: json!({"arg": "value"}),
connector_id: Some("connector".into()),
mcp_app_resource_uri: Some("app://connector".into()),
link_id: Some("link_123".into()),
plugin_id: Some("sample@test".into()),
status: McpToolCallStatus::InProgress,
result: None,
@@ -4956,10 +4970,12 @@ mod tests {
assert_eq!(event.call_id, "mcp-1");
assert_eq!(event.invocation.server, "server");
assert_eq!(event.invocation.tool, "tool");
assert_eq!(event.connector_id.as_deref(), Some("connector"));
assert_eq!(
event.mcp_app_resource_uri.as_deref(),
Some("app://connector")
);
assert_eq!(event.link_id.as_deref(), Some("link_123"));
assert_eq!(event.plugin_id.as_deref(), Some("sample@test"));
}
_ => panic!("expected McpToolCallBegin event"),
@@ -5047,7 +5063,9 @@ mod tests {
server: "server".into(),
tool: "tool".into(),
arguments: json!({"arg": "value"}),
connector_id: Some("connector".into()),
mcp_app_resource_uri: Some("app://connector".into()),
link_id: Some("link_123".into()),
plugin_id: Some("sample@test".into()),
status: McpToolCallStatus::Completed,
result: Some(CallToolResult {
@@ -5068,10 +5086,12 @@ mod tests {
assert_eq!(event.call_id, "mcp-1");
assert_eq!(event.invocation.server, "server");
assert_eq!(event.invocation.tool, "tool");
assert_eq!(event.connector_id.as_deref(), Some("connector"));
assert_eq!(
event.mcp_app_resource_uri.as_deref(),
Some("app://connector")
);
assert_eq!(event.link_id.as_deref(), Some("link_123"));
assert_eq!(event.plugin_id.as_deref(), Some("sample@test"));
assert_eq!(event.duration, Duration::from_millis(42));
assert!(event.is_success());
@@ -973,6 +973,7 @@ async fn replayed_in_progress_mcp_tool_call_stays_active() {
tool: "copilot".to_string(),
status: codex_app_server_protocol::McpToolCallStatus::InProgress,
arguments: json!({"action": "wait"}),
app_context: None,
mcp_app_resource_uri: None,
plugin_id: None,
result: None,