Expose LLM tool-call id to MCP tool invocations
Background
When the SDK invokes an MCP tool, our host process needs to know the LLM-side tool call id that triggered the call, and (ideally) propagate it to the MCP server. We use this id for:
- Correlating MCP server-side audit logs with LLM turns
- Distributed tracing across the LLM → CLI → MCP server boundary
- Reconciling outputs from multiple parallel tool calls in the same turn
Today, the id is already tracked inside the CLI — it surfaces in:
ToolInvocation.toolCallId (local tools registered via tools[])
tool.execution_start.data.toolCallId (session event stream, read-only)
kind:"mcp" PermissionRequest's toolCallId (only fires on permission path)
But none of these reaches the MCP server, and none lets the host inject the id into the outbound tools/call request.
Asks
Either of the following would unblock us. Option A is preferred because it requires no host-side code.
Option A — CLI auto-injects _meta on outbound MCP tools/call
When the CLI sends a tools/call JSON-RPC request to an MCP server, include the id in the standard MCP _meta request field:
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "search",
"arguments": { ... },
"_meta": {
"copilot/toolCallId": "<toolCallId>"
}
}
}
_meta is part of the MCP spec for out-of-band request metadata. It sits alongside arguments, isn't part of inputSchema, isn't visible to the LLM, and strict additionalProperties: false schemas don't reject it. Servers that don't read _meta are unaffected — zero breaking change.
Optionally include sessionId, parentToolCallId, and W3C traceparent to match the richness already present in tool.execution_start.data.
Option B — Add toolCallId to PreToolUseHookInput
Currently:
export interface PreToolUseHookInput extends BaseHookInput {
toolName: string;
toolArgs: unknown;
}
Proposed (additive, back-compatible):
export interface PreToolUseHookInput extends BaseHookInput {
toolName: string;
toolArgs: unknown;
toolCallId?: string; // LLM-side tool call id
}
With the id available in the hook, hosts can use PreToolUseHookOutput.modifiedArgs to inject the id into MCP tools/call arguments themselves. This is strictly less clean than Option A (pollutes arguments, requires schema cooperation), but it gives hosts a self-service escape hatch.
Today's workaround (and why it's bad)
The only way to pass toolCallId to an MCP server today is to reimplement the MCP server as a host-side proxy registered via tools[], where ToolInvocation.toolCallId is available. The host then runs its own MCP client and forwards calls, injecting the id.
Costs of that workaround:
- Lose
kind:"mcp" PermissionRequest metadata (serverName, toolTitle, readOnly)
- Reimplement MCP lifecycle: process spawn, reconnect, timeouts
- Reimplement OAuth flow (
mcp.oauth_required event no longer fires)
- Lose
tool.execution_start.mcpServerName / mcpToolName telemetry
- Tools show up as
kind:"custom-tool" in UIs that branch on tool kind
For functionality the CLI already has internally, this is a lot of duplicated machinery on the host side.
Summary
Option A is the simplest, smallest, and most aligned with the MCP spec — one place in the CLI's outbound tools/call construction needs the id added to _meta. Option B is a fallback if Option A is not feasible.
Expose LLM tool-call id to MCP tool invocations
Background
When the SDK invokes an MCP tool, our host process needs to know the LLM-side tool call id that triggered the call, and (ideally) propagate it to the MCP server. We use this id for:
Today, the id is already tracked inside the CLI — it surfaces in:
ToolInvocation.toolCallId(local tools registered viatools[])tool.execution_start.data.toolCallId(session event stream, read-only)kind:"mcp"PermissionRequest'stoolCallId(only fires on permission path)But none of these reaches the MCP server, and none lets the host inject the id into the outbound
tools/callrequest.Asks
Either of the following would unblock us. Option A is preferred because it requires no host-side code.
Option A — CLI auto-injects
_metaon outbound MCPtools/callWhen the CLI sends a
tools/callJSON-RPC request to an MCP server, include the id in the standard MCP_metarequest field:{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "search", "arguments": { ... }, "_meta": { "copilot/toolCallId": "<toolCallId>" } } }_metais part of the MCP spec for out-of-band request metadata. It sits alongsidearguments, isn't part ofinputSchema, isn't visible to the LLM, and strictadditionalProperties: falseschemas don't reject it. Servers that don't read_metaare unaffected — zero breaking change.Optionally include
sessionId,parentToolCallId, and W3Ctraceparentto match the richness already present intool.execution_start.data.Option B — Add
toolCallIdtoPreToolUseHookInputCurrently:
Proposed (additive, back-compatible):
With the id available in the hook, hosts can use
PreToolUseHookOutput.modifiedArgsto inject the id into MCPtools/callarguments themselves. This is strictly less clean than Option A (pollutesarguments, requires schema cooperation), but it gives hosts a self-service escape hatch.Today's workaround (and why it's bad)
The only way to pass
toolCallIdto an MCP server today is to reimplement the MCP server as a host-side proxy registered viatools[], whereToolInvocation.toolCallIdis available. The host then runs its own MCP client and forwards calls, injecting the id.Costs of that workaround:
kind:"mcp"PermissionRequest metadata (serverName,toolTitle,readOnly)mcp.oauth_requiredevent no longer fires)tool.execution_start.mcpServerName/mcpToolNametelemetrykind:"custom-tool"in UIs that branch on tool kindFor functionality the CLI already has internally, this is a lot of duplicated machinery on the host side.
Summary
Option A is the simplest, smallest, and most aligned with the MCP spec — one place in the CLI's outbound
tools/callconstruction needs the id added to_meta. Option B is a fallback if Option A is not feasible.