github / github/copilot-sdk

Expose LLM tool-call id to MCP tool invocations

Offen
#1,332 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
enhancement
Vorherrschende Sprache
Java
Sterne
10.5k
Forks
1.5k
Ø Merge
1 T. 11 Std.
Gemergte PRs (30 T.)
128

Beschreibung

# 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.

Beitragsleitfaden

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.