Expose LLM tool-call id to MCP tool invocations
- Lenguaje dominante
- Java
- Estrellas
- 10.5k
- Forks
- 1.5k
- Merge medio
- 1 d 11 h
- PR fusionados (30 d)
- 128
Descripción
# 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:
```json
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "search",
"arguments": { ... },
"_meta": {
"copilot/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:
```ts
export interface PreToolUseHookInput extends BaseHookInput {
toolName: string;
toolArgs: unknown;
}
```
Proposed (additive, back-compatible):
```ts
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.
Guía de contribución
Evaluación
Este issue todavía no se ha evaluado.