Skip to content

context_id: prose requires MCP callers to send it, no request schema declares it, and both SDKs refuse or drop it on MCP #7343

Description

@KonstantinMirin

Summary

The prose instructs MCP callers to send context_id as a tool argument and calls it "Required for continuity". No request schema declares it — 0 of 103 at v3.1.1, 0 of 103 at v3.1.20. Both reference SDKs refuse or silently drop it on MCP, neither reference server ever sets or reads it, no MCP round-trip test exists in either SDK, and the compliance bundle does not grade it.

So a conformant MCP client following the documentation sends a field the schema does not model, to a server neither reference implementation teaches how to read.

The prose (verified at ?ref=v3.1.1)

docs/building/by-layer/L2/context-sessions.mdx:

// Follow-up - must include context_id
const result2 = await mcp.call('get_products', {
  ...
  context_id: contextId,  // Required for continuity

"A2A: Context is handled automatically by the protocol / MCP: Requires manual context_id management."

docs/building/by-layer/L0/mcp-guide.mdx, §Context Management (MCP-Specific):

"Critical: MCP requires manual context management. You must pass context_id to maintain conversation state."

with request.arguments.context_id = this.contextId;.

The same file is internally inconsistent — earlier it says the caller "MAY echo it on subsequent calls to thread a session", two sections before "You must pass".

The schemas

core/protocol-envelope.json declares context_id on the RESPONSE:

"Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from context (per-request opaque echo…)."

Request side, counted over the non-bundled *-request.json:

version request schemas declaring context_id
3.1.1 103 0
3.1.20 103 0
3.2.0-rc.1 374 4

3.2.0-rc.1 adds "context_id": {"type": "string", "minLength": 1} to list-products, request-proposals, refine-proposals and decline-proposals only, never required — and not to create_media_buy or update_media_buy, which are the tools the L2 example itself uses.

ContextObject, the type of the request's context field, has zero properties at 3.1.1, so it cannot carry the id either.

The reference implementations disagree with the prose

Python SDK (adcp 6.6.0) refuses it outright — client.py:587-596:

if context_id:
    if not isinstance(self.adapter, A2AAdapter):
        raise TypeError(f"context_id is only supported for A2A protocol; got {protocol}")

protocols/mcp.py contains zero occurrences of context_id. There is no MCP path.

TypeScript SDK (adcp-client) drops it — TaskExecutor.ts:1531-1542:

"Pass the caller's A2A session ids (contextId for conversation binding, taskId for resuming a non-terminal server-side task). The adapter drops these on the wire for MCP (no session concept there)."

On A2A it rides message.contextId (protocols/a2a.ts:625,714) — the A2A envelope, not the AdCP body. tools.generated.ts: 4 of 64 request interfaces declare context_id; 33 of 40 response interfaces do.

Neither reference server implements it. The TS server never sets context_id on a response (wrap-envelope.ts, responses.ts, structured-serialize.ts: zero hits) and never reads one from a request; its A2A path threads A2A's own Task.contextId (server/a2a-adapter.ts:462-469,594). The Python server has one occurrence, a2a_server.py:677, also on the A2A pb.Task.

No MCP round-trip test exists in either SDK. The A2A ones do (test/lib/a2a-context-retention.test.js, test/storyboard-a2a-context-continuity.test.js), and both assert on the A2A Message envelope. The one MCP-adjacent test asserts the opposite behaviour — test/lib/task-executor-async-patterns.test.js:550-563, "does not invent an MCP continuation tool for a returned pause": the mocked response carries context_id: 'mcp-context' and the test asserts callTool is called exactly once.

Not graded. context_id appears zero times under _schemas/3.1/compliance/. The only related check in later versions is a2a_context_continuity, which grades A2A 0.3.0 §7.1, not AdCP.

Why it stays hidden

63 of the 103 request schemas at 3.1 are additionalProperties: true, so a client following the prose gets no validation error — the field is simply unmodelled and ignored.

Expected

Pick one and make the three artifacts agree:

  1. Context continuation is real on MCP — then context_id belongs on the request envelope (alongside adcp_version in core/version-envelope.json, or a shared request envelope), not on 4 of 374 tools, and the reference servers should read it.
  2. Context continuation is A2A-only — then the L2 and L0 prose should say so instead of instructing MCP callers to send a field that does not exist, and ProtocolEnvelope.context_id should say it is only actionable on A2A.

Either way the "MAY echo" / "You must pass" inconsistency inside mcp-guide.mdx should be resolved.

How this was found

Implementing an AdCP sales agent that serves MCP, A2A and REST from one registry. We followed the prose and accepted context_id via an x-context-id header on MCP, because there was no declared field — then found the buyer could never learn their id, because no response builder we could write had a conformant way to be told one back.

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

    Labels

    bugSomething isn't workingclaude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.priority:P0Immediate priority: user/revenue/security/reliability bug to work nowschemaJSON Schema source-of-truth: definitions, codegen artifacts, validation, hygienespec / protocol

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions