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
co-authored by martinauyeung-oai
parent 9bcc09f9f7
commit 765309d5a6
40 changed files with 899 additions and 15 deletions
+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());