Expose callId in MCP tool call _meta for all servers
Open 💬 3 comments Opened May 4, 2026 by daniloag
What variant of Codex are you using?
CLI
What feature would you like to see?
Context
When Codex dispatches an MCP tool call, the tool call's call_id is passed to build_mcp_tool_call_request_meta, but only included in _meta for the codex_apps server, nested under _codex_apps.call_id. Custom MCP servers receive no identifier for the originating tool call.
Ask
Add callId at the top level of _meta for all MCP servers, following the same pattern as threadId (which is already injected unconditionally via with_mcp_tool_call_thread_id_meta).
Use cases:
- Tracing / observability: correlate client-side spans with server-side processing.
- Audit logging: track which model action triggered which server-side operation.
- Request deduplication: detect retried or duplicate tool calls on the server side.
Additional information
call_id is already a parameter of build_mcp_tool_call_request_meta. It just needs to be inserted unconditionally.
Happy to submit a PR with tests if the team is open to this change.
3 Comments
From the audit-log consumer side, callId at the top of
_metafor every MCP server is the stable correlation key that lets per-call audit records chain to the host session without server-specific nesting.Once callId lives at
_meta.callIdunconditionally, downstream runtime-evidence pipelines stop needing per-server adapters to find it. Concretely, in Vaara's MCP proxy (github.com/vaaraio/vaara,src/vaara/integrations/mcp_proxy.py) the audit record emitted per tools/call carries anaction_idthat maps directly to the originating model action. With callId unwrapped at the top of_meta, the proxy can correlate Codex-side spans to action_id without falling back to threadId-plus-timestamp heuristics.The same surface unblocks a few other things. SEP-2787 (modelcontextprotocol/modelcontextprotocol#2787) attestation envelopes that bind per-call commitments key off the call identifier. Transparency-log inclusion proofs anchored on callId let the client verify "this annotation derives from record N in the audit log" without trusting the host. All of these become awkward without a stable top-level callId.
The patch you proposed (unconditional injection alongside threadId via the existing
with_mcp_tool_call_thread_id_metashape) is the cleanest place for it.This seems like the right place to add the field. A top-level
_meta.callIdgives MCP servers a stable host-side correlation key for the logical tool call, separate from the JSON-RPC request id and separate from transport trace context.One boundary I would keep explicit is that
callIdis an opaque correlation identifier. Servers can log it, attach it to spans, use it for best-effort deduplication of retries, and join it with host-side tool-call telemetry. They should not treat it by itself as authorization, provenance, or attestation evidence.That distinction matters because the adjacent identifiers have different jobs.
traceparentandtracestatecarry distributed trace context. The MCP or JSON-RPC request id correlates a protocol request and response. A hostcallIdidentifies the model-side tool-call action. A signed attestation or audit receipt could later choose to bind that call id into its own envelope, but the call id alone does not prove package provenance, policy approval, argument integrity, or execution outcome.The contract I would find easiest to implement against is small:
callIdis generated by the host, stable for one logical tool call including retries of that same call, opaque, non-secret, and interpreted within host or session context rather than as a global security principal.That keeps this issue nicely scoped: expose the host call id consistently for observability, audit-log joins, and deduplication, while leaving stronger evidence semantics to explicit attestation or receipt layers.
the pattern here shows up in every MCP host that injects request context: the useful field lands in _meta for the first-party server first (here, _codex_apps.call_id) and custom servers fly blind until someone files this. threadId rides along unconditionally, but it scopes the session, it can't tell one call from a retry of that call. harmless for read-only servers, brutal for side-effecting ones. on a server driving desktop actions, a retried call with no callId looks identical to a fresh one, so a timed-out click gets replayed and you double-type a field. hoist callId next to threadId.