Skip to content

Expose LLM tool-call id to MCP tool invocations #1332

Description

@lxwl-fht

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions