[codex] Add plugin id to MCP tool call items (#23737)

Add owning plugin id to MCP tool call items so we can better filter them
at plugin level.

## Summary
- add optional `plugin_id` to MCP tool-call items and legacy begin/end
events
- propagate plugin metadata into emitted core items and app-server v2
`ThreadItem::McpToolCall`
- preserve plugin ids through app-server replay/redaction paths and
regenerate v2 schema fixtures

## Testing
- `just write-app-server-schema`
- `just fmt`
- `just fix -p codex-core`
- `cargo test -p codex-protocol -p codex-app-server-protocol`
- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-core mcp_tool_call_item_includes_plugin_id --lib`
- `cargo check -p codex-tui --tests`
- `cargo check -p codex-app-server --tests`
- `git diff --check`

## Notes
- `just fix -p codex-core` completed with two non-fatal
`too_many_arguments` warnings on the touched MCP notification helpers.
- A broader `cargo test -p codex-core` run passed core unit tests, then
hit shell/sandbox/snapshot failures in the integration target.
- A broader app-server downstream run hit the existing
`in_process::tests::in_process_start_clamps_zero_channel_capacity` stack
overflow; `cargo test -p codex-exec` also hit the existing sandbox
expectation mismatch in
`thread_lifecycle_params_include_legacy_sandbox_when_no_active_profile`.
This commit is contained in:
Matthew Zeng
2026-05-20 17:02:10 -07:00
committed by GitHub
Unverified
parent 0b5cf85b64
commit 0a4179bb19
36 changed files with 219 additions and 20 deletions
@@ -3628,6 +3628,7 @@ async fn turn_event_counts_completed_tool_items() {
status: McpToolCallStatus::Completed,
arguments: json!({}),
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
error: None,
duration_ms: Some(2),
@@ -3861,6 +3861,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -16319,6 +16319,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -14143,6 +14143,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -800,6 +800,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -800,6 +800,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -944,6 +944,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -1421,6 +1421,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -1236,6 +1236,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -1236,6 +1236,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -1236,6 +1236,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -1421,6 +1421,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -1236,6 +1236,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -1421,6 +1421,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -1236,6 +1236,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -1236,6 +1236,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -944,6 +944,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -944,6 +944,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -944,6 +944,12 @@
"null"
]
},
"pluginId": {
"type": [
"string",
"null"
]
},
"result": {
"anyOf": [
{
@@ -53,7 +53,7 @@ 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, 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, mcpAppResourceUri?: string, pluginId: string | null, result: McpToolCallResult | null, error: McpToolCallError | null,
/**
* The duration of the MCP tool call in milliseconds.
*/
@@ -525,6 +525,7 @@ impl ThreadHistoryBuilder {
.clone()
.unwrap_or(serde_json::Value::Null),
mcp_app_resource_uri: payload.mcp_app_resource_uri.clone(),
plugin_id: payload.plugin_id.clone(),
result: None,
error: None,
duration_ms: None,
@@ -566,6 +567,7 @@ impl ThreadHistoryBuilder {
.clone()
.unwrap_or(serde_json::Value::Null),
mcp_app_resource_uri: payload.mcp_app_resource_uri.clone(),
plugin_id: payload.plugin_id.clone(),
result,
error,
duration_ms,
@@ -1911,6 +1913,7 @@ mod tests {
arguments: Some(serde_json::json!({"id":"123"})),
},
mcp_app_resource_uri: None,
plugin_id: None,
duration: Duration::from_millis(8),
result: Err("boom".into()),
}),
@@ -1960,6 +1963,7 @@ mod tests {
status: McpToolCallStatus::Failed,
arguments: serde_json::json!({"id":"123"}),
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
error: Some(McpToolCallError {
message: "boom".into(),
@@ -1986,6 +1990,7 @@ mod tests {
arguments: Some(serde_json::json!({"id":"123"})),
},
mcp_app_resource_uri: Some("ui://widget/lookup.html".into()),
plugin_id: Some("sample@test".into()),
duration: Duration::from_millis(8),
result: Ok(CallToolResult {
content: vec![serde_json::json!({
@@ -2016,6 +2021,7 @@ mod tests {
status: McpToolCallStatus::Completed,
arguments: serde_json::json!({"id":"123"}),
mcp_app_resource_uri: Some("ui://widget/lookup.html".into()),
plugin_id: Some("sample@test".into()),
result: Some(Box::new(McpToolCallResult {
content: vec![serde_json::json!({
"type": "text",
@@ -286,6 +286,7 @@ pub enum ThreadItem {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
mcp_app_resource_uri: Option<String>,
plugin_id: Option<String>,
result: Option<Box<McpToolCallResult>>,
error: Option<McpToolCallError>,
/// The duration of the MCP tool call in milliseconds.
@@ -846,6 +847,7 @@ impl From<CoreTurnItem> for ThreadItem {
status: McpToolCallStatus::from(mcp.status),
arguments: mcp.arguments,
mcp_app_resource_uri: mcp.mcp_app_resource_uri,
plugin_id: mcp.plugin_id,
result: mcp.result.map(McpToolCallResult::from).map(Box::new),
error: mcp.error.map(McpToolCallError::from),
duration_ms,
@@ -2491,6 +2491,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
tool: "tool".to_string(),
arguments: json!({"arg": "value"}),
mcp_app_resource_uri: Some("app://connector".to_string()),
plugin_id: Some("sample@test".to_string()),
status: CoreMcpToolCallStatus::InProgress,
result: None,
error: None,
@@ -2506,6 +2507,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
status: McpToolCallStatus::InProgress,
arguments: json!({"arg": "value"}),
mcp_app_resource_uri: Some("app://connector".to_string()),
plugin_id: Some("sample@test".to_string()),
result: None,
error: None,
duration_ms: None,
@@ -2518,6 +2520,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
tool: "tool".to_string(),
arguments: JsonValue::Null,
mcp_app_resource_uri: None,
plugin_id: None,
status: CoreMcpToolCallStatus::Completed,
result: Some(CallToolResult {
content: vec![json!({"type": "text", "text": "ok"})],
@@ -2538,6 +2541,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
status: McpToolCallStatus::Completed,
arguments: JsonValue::Null,
mcp_app_resource_uri: None,
plugin_id: None,
result: Some(Box::new(McpToolCallResult {
content: vec![json!({"type": "text", "text": "ok"})],
structured_content: Some(json!({"ok": true})),
+1 -1
View File
@@ -1231,7 +1231,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, result?, error?}` describing MCP calls; `status` is `inProgress`, `completed`, or `failed`.
- `mcpToolCall``{id, server, tool, status, arguments, mcpAppResourceUri?, pluginId, result?, error?}` describing MCP calls; `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.
@@ -79,6 +79,7 @@ mod tests {
status: McpToolCallStatus::Completed,
arguments: serde_json::json!({"secret":"argument"}),
mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()),
plugin_id: Some("sample@test".to_string()),
result: Some(Box::new(McpToolCallResult {
content: vec![serde_json::json!({
"type": "text",
@@ -120,6 +121,7 @@ mod tests {
status: McpToolCallStatus::Completed,
arguments: JsonValue::String(REDACTED_PAYLOAD.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())),
error: None,
duration_ms: Some(8),
@@ -136,6 +138,7 @@ mod tests {
status: McpToolCallStatus::Failed,
arguments: serde_json::json!({"secret":"argument"}),
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
error: Some(McpToolCallError {
message: "secret error".to_string(),
@@ -154,6 +157,7 @@ mod tests {
status: McpToolCallStatus::Failed,
arguments: JsonValue::String(REDACTED_PAYLOAD.to_string()),
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
error: Some(McpToolCallError {
message: REDACTED_PAYLOAD.to_string(),
@@ -513,6 +513,7 @@ url = "{mcp_server_url}/mcp"
status,
arguments: json!({ "message": LARGE_RESPONSE_MESSAGE }),
mcp_app_resource_uri: None,
plugin_id: None,
result: Some(result),
error: None,
duration_ms: None,
@@ -679,6 +679,7 @@ fn append_resume_redaction_history(
arguments: Some(json!({"secret":"argument"})),
},
mcp_app_resource_uri: Some("ui://widget/lookup.html".to_string()),
plugin_id: None,
duration: Duration::from_millis(8),
result: Ok(CallToolResult {
content: vec![json!({
+31 -18
View File
@@ -138,9 +138,14 @@ 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 mcp_app_resource_uri = metadata
.as_ref()
.and_then(|metadata| metadata.mcp_app_resource_uri.clone());
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 app_tool_policy = if server == CODEX_APPS_MCP_SERVER_NAME {
connectors::app_tool_policy(
&turn_context.config,
@@ -171,7 +176,7 @@ pub(crate) async fn handle_mcp_tool_call(
turn_context.as_ref(),
&call_id,
invocation,
mcp_app_resource_uri.clone(),
item_metadata.clone(),
"MCP tool call blocked by app configuration".to_string(),
/*already_started*/ false,
)
@@ -200,7 +205,7 @@ pub(crate) async fn handle_mcp_tool_call(
turn_context.as_ref(),
&call_id,
invocation.clone(),
mcp_app_resource_uri.clone(),
item_metadata.clone(),
)
.await;
@@ -225,7 +230,7 @@ pub(crate) async fn handle_mcp_tool_call(
&call_id,
invocation,
metadata.as_ref(),
mcp_app_resource_uri,
item_metadata,
)
.await;
}
@@ -236,7 +241,7 @@ pub(crate) async fn handle_mcp_tool_call(
turn_context.as_ref(),
&call_id,
invocation,
mcp_app_resource_uri.clone(),
item_metadata.clone(),
message,
/*already_started*/ true,
)
@@ -249,7 +254,7 @@ pub(crate) async fn handle_mcp_tool_call(
turn_context.as_ref(),
&call_id,
invocation,
mcp_app_resource_uri.clone(),
item_metadata.clone(),
message,
/*already_started*/ true,
)
@@ -280,7 +285,7 @@ pub(crate) async fn handle_mcp_tool_call(
&call_id,
invocation,
metadata.as_ref(),
mcp_app_resource_uri,
item_metadata,
)
.await
}
@@ -290,13 +295,19 @@ pub(crate) struct HandledMcpToolCall {
pub(crate) tool_input: JsonValue,
}
#[derive(Clone)]
struct McpToolCallItemMetadata {
mcp_app_resource_uri: Option<String>,
plugin_id: Option<String>,
}
async fn handle_approved_mcp_tool_call(
sess: &Session,
turn_context: &TurnContext,
call_id: &str,
invocation: McpInvocation,
metadata: Option<&McpToolApprovalMetadata>,
mcp_app_resource_uri: Option<String>,
item_metadata: McpToolCallItemMetadata,
) -> HandledMcpToolCall {
let server = invocation.server.clone();
maybe_mark_thread_memory_mode_polluted(sess, turn_context, &server).await;
@@ -365,7 +376,7 @@ async fn handle_approved_mcp_tool_call(
turn_context,
call_id,
invocation,
mcp_app_resource_uri,
item_metadata,
duration,
truncate_mcp_tool_result_for_event(&result),
)
@@ -840,7 +851,7 @@ async fn notify_mcp_tool_call_started(
turn_context: &TurnContext,
call_id: &str,
invocation: McpInvocation,
mcp_app_resource_uri: Option<String>,
item_metadata: McpToolCallItemMetadata,
) {
let McpInvocation {
server,
@@ -852,7 +863,8 @@ async fn notify_mcp_tool_call_started(
server,
tool,
arguments: arguments.unwrap_or(JsonValue::Null),
mcp_app_resource_uri,
mcp_app_resource_uri: item_metadata.mcp_app_resource_uri,
plugin_id: item_metadata.plugin_id,
status: McpToolCallStatus::InProgress,
result: None,
error: None,
@@ -866,7 +878,7 @@ async fn notify_mcp_tool_call_completed(
turn_context: &TurnContext,
call_id: &str,
invocation: McpInvocation,
mcp_app_resource_uri: Option<String>,
item_metadata: McpToolCallItemMetadata,
duration: Duration,
result: Result<CallToolResult, String>,
) {
@@ -891,7 +903,8 @@ async fn notify_mcp_tool_call_completed(
server,
tool,
arguments: arguments.unwrap_or(JsonValue::Null),
mcp_app_resource_uri,
mcp_app_resource_uri: item_metadata.mcp_app_resource_uri,
plugin_id: item_metadata.plugin_id,
status,
result,
error,
@@ -2092,7 +2105,7 @@ async fn notify_mcp_tool_call_skip(
turn_context: &TurnContext,
call_id: &str,
invocation: McpInvocation,
mcp_app_resource_uri: Option<String>,
item_metadata: McpToolCallItemMetadata,
message: String,
already_started: bool,
) -> Result<CallToolResult, String> {
@@ -2102,7 +2115,7 @@ async fn notify_mcp_tool_call_skip(
turn_context,
call_id,
invocation.clone(),
mcp_app_resource_uri.clone(),
item_metadata.clone(),
)
.await;
}
@@ -2112,7 +2125,7 @@ async fn notify_mcp_tool_call_skip(
turn_context,
call_id,
invocation,
mcp_app_resource_uri,
item_metadata,
Duration::ZERO,
truncate_mcp_tool_result_for_event(&Err(message.clone())),
)
+34
View File
@@ -1112,6 +1112,40 @@ async fn plugin_mcp_tool_call_request_meta_includes_plugin_id() {
);
}
#[tokio::test]
async fn mcp_tool_call_item_includes_plugin_id() {
let (session, turn_context, rx_event) = make_session_and_context_with_rx().await;
notify_mcp_tool_call_started(
&session,
&turn_context,
"call-plugin",
McpInvocation {
server: "sample".to_string(),
tool: "echo".to_string(),
arguments: None,
},
McpToolCallItemMetadata {
mcp_app_resource_uri: None,
plugin_id: Some("sample@test".to_string()),
},
)
.await;
let event = tokio::time::timeout(std::time::Duration::from_secs(1), rx_event.recv())
.await
.expect("tool call item timed out")
.expect("tool call item event");
let EventMsg::ItemStarted(item_started) = event.msg else {
panic!("expected ItemStarted event");
};
let TurnItem::McpToolCall(item) = item_started.item else {
panic!("expected MCP tool call item");
};
assert_eq!(item.plugin_id.as_deref(), Some("sample@test"));
}
#[tokio::test]
async fn codex_apps_tool_call_request_meta_includes_turn_metadata_and_codex_apps_meta() {
let (_, turn_context) = make_session_and_context().await;
@@ -202,6 +202,7 @@ async fn emit_tool_call_begin(
tool,
arguments: arguments.unwrap_or(Value::Null),
mcp_app_resource_uri: None,
plugin_id: None,
status: McpToolCallStatus::InProgress,
result: None,
error: None,
@@ -240,6 +241,7 @@ async fn emit_tool_call_end(
tool,
arguments: arguments.unwrap_or(Value::Null),
mcp_app_resource_uri: None,
plugin_id: None,
status,
result,
error,
+1
View File
@@ -1103,6 +1103,7 @@ async fn stdio_image_responses_round_trip() -> anyhow::Result<()> {
arguments: Some(json!({})),
},
mcp_app_resource_uri: None,
plugin_id: None,
},
);
@@ -72,6 +72,7 @@ fn mcp_tool_call_result_preserves_meta_in_jsonl_event() {
status: McpToolCallStatus::Completed,
arguments: json!({"search_query": [{"q": "OpenAI Codex CLI documentation"}]}),
mcp_app_resource_uri: None,
plugin_id: None,
result: Some(Box::new(codex_app_server_protocol::McpToolCallResult {
content: vec![json!({"type": "text", "text": "search result"})],
structured_content: None,
@@ -478,6 +478,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() {
status: ApiMcpToolCallStatus::InProgress,
arguments: json!({ "key": "value" }),
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
error: None,
duration_ms: None,
@@ -495,6 +496,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() {
status: ApiMcpToolCallStatus::Completed,
arguments: json!({ "key": "value" }),
mcp_app_resource_uri: None,
plugin_id: None,
result: Some(Box::new(McpToolCallResult {
content: Vec::new(),
structured_content: None,
@@ -566,6 +568,7 @@ fn mcp_tool_call_failure_sets_failed_status() {
status: ApiMcpToolCallStatus::Failed,
arguments: json!({ "param": 42 }),
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
error: Some(McpToolCallError {
message: "tool exploded".to_string(),
@@ -614,6 +617,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() {
status: ApiMcpToolCallStatus::InProgress,
arguments: serde_json::Value::Null,
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
error: None,
duration_ms: None,
@@ -631,6 +635,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() {
status: ApiMcpToolCallStatus::Completed,
arguments: serde_json::Value::Null,
mcp_app_resource_uri: None,
plugin_id: None,
result: Some(Box::new(McpToolCallResult {
content: vec![json!({
"type": "text",
+5
View File
@@ -179,6 +179,9 @@ pub struct McpToolCallItem {
#[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 plugin_id: Option<String>,
pub status: McpToolCallStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional)]
@@ -530,6 +533,7 @@ impl McpToolCallItem {
arguments: (!self.arguments.is_null()).then(|| self.arguments.clone()),
},
mcp_app_resource_uri: self.mcp_app_resource_uri.clone(),
plugin_id: self.plugin_id.clone(),
})
}
@@ -548,6 +552,7 @@ impl McpToolCallItem {
arguments: (!self.arguments.is_null()).then(|| self.arguments.clone()),
},
mcp_app_resource_uri: self.mcp_app_resource_uri.clone(),
plugin_id: self.plugin_id.clone(),
duration: self.duration?,
result,
}))
+10
View File
@@ -2186,6 +2186,9 @@ pub struct McpToolCallBeginEvent {
#[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 plugin_id: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS, PartialEq)]
@@ -2196,6 +2199,9 @@ pub struct McpToolCallEndEvent {
#[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 plugin_id: Option<String>,
#[ts(type = "string")]
pub duration: Duration,
/// Result of the tool call. Note this could be an error.
@@ -4536,6 +4542,7 @@ mod tests {
tool: "tool".into(),
arguments: json!({"arg": "value"}),
mcp_app_resource_uri: Some("app://connector".into()),
plugin_id: Some("sample@test".into()),
status: McpToolCallStatus::InProgress,
result: None,
error: None,
@@ -4554,6 +4561,7 @@ mod tests {
event.mcp_app_resource_uri.as_deref(),
Some("app://connector")
);
assert_eq!(event.plugin_id.as_deref(), Some("sample@test"));
}
_ => panic!("expected McpToolCallBegin event"),
}
@@ -4641,6 +4649,7 @@ mod tests {
tool: "tool".into(),
arguments: json!({"arg": "value"}),
mcp_app_resource_uri: Some("app://connector".into()),
plugin_id: Some("sample@test".into()),
status: McpToolCallStatus::Completed,
result: Some(CallToolResult {
content: vec![json!({"type": "text", "text": "ok"})],
@@ -4664,6 +4673,7 @@ mod tests {
event.mcp_app_resource_uri.as_deref(),
Some("app://connector")
);
assert_eq!(event.plugin_id.as_deref(), Some("sample@test"));
assert_eq!(event.duration, Duration::from_millis(42));
assert!(event.is_success());
}
@@ -859,6 +859,7 @@ async fn replayed_in_progress_mcp_tool_call_stays_active() {
status: codex_app_server_protocol::McpToolCallStatus::InProgress,
arguments: json!({"action": "wait"}),
mcp_app_resource_uri: None,
plugin_id: None,
result: None,
error: None,
duration_ms: None,