The ambiguity. `core/protocol-envelope.json` defines a canonical envelope shape with `status` + `payload` required and `context_id` / `task_id` / `timestamp` / `replayed` / `governance_context` as optional siblings. The schema's own `notes` acknowledge that transports serialize this differently:
MCP may represent this via tool response content fields and metadata
A2A may represent this via assistant messages with structured data
REST may use HTTP headers for status/task metadata and JSON body for payload
But the spec doesn't normatively define what MCP-specifically looks like on the wire. Two load-bearing questions aren't answered:
Q1 — Payload nesting in MCP
Given a response like the schema's own example:
```json
{
"status": "completed",
"context_id": "ctx_abc",
"timestamp": "2025-10-14T14:25:30Z",
"payload": { "products": [...] }
}
```
When an MCP tool returns this, do implementations serialize the JSON verbatim (nested `payload` object as a sibling of `status`), or are `payload` fields hoisted to the root?
Current shipping behavior across at least one SDK (`@adcp/client`'s `wrapEnvelope`) is flat: `{ products: [...], replayed: false, context: {...}, operation_id: "op_123" }`. No `status` field, no nested `payload`. Task response schemas like `media-buy/get-products-response.json` declare body fields at the root, not under `payload`.
Which is spec-conforming? The schema's `required: [status, payload]` suggests nested; shipping SDKs ship flat. Conformance testing can't grade this without a normative answer.
Q2 — `context_id` vs a `context` echo object
`protocol-envelope` defines `context_id: string` ("session/conversation identifier"). SDKs in the wild attach a `context: object` — the request's `context` block echoed back unchanged for caller-side correlation (internal tracing, UI session IDs, custom metadata). These are semantically distinct (session scope vs per-request echo) but similarly named.
Is `context` (echo object) a second envelope field that should join `protocol-envelope.json`, or is it the SDK's misinterpretation of `context_id`?
Why this matters
- Multi-SDK interop. Only one production SDK (`@adcp/client`) ships today. Python / Go / Rust SDKs will hit this ambiguity on day one and either choose nested (spec-literal) or flat (SDK-precedent). Divergence here propagates every transport integration.
- Strict validation. Runners that want to validate the envelope (see `adcp-client#832`) can't AJV-check `/schemas/3.0.0/core/protocol-envelope.json` against a flat-wire response without knowing whether that's spec-compliant.
- AdCP 3.0 GA is pinned. Resolving this before 3.1 keeps the path clean; deferring risks every SDK freezing divergent behavior.
Proposed outcome
One of:
- Normative MCP serialization section in the spec — a short paragraph under each transport (MCP / A2A / REST) stating how `protocol-envelope` serializes, including whether `payload` is nested or flattened. This is the ideal.
- An errata / clarification note — if the decision is "flat at root" for MCP, add that verbatim to the envelope schema's `notes` and add `context` (echo object) to `protocol-envelope.json` as an optional sibling distinct from `context_id`.
- Declare current SDK behavior non-conformant and publish migration guidance — if the decision is "nested `payload` is required for MCP too", that's a 3.1 breaking change and needs an explicit ADR.
Happy to contribute the schema / docs change once the working group picks a direction.
Related
- `core/protocol-envelope.json` (definition)
- `adcp-client#832` (downstream tracker for per-field envelope validation — blocked on this question)
- `adcp-client#788` (security-review thread that formalized the SDK's current envelope governance)
The ambiguity. `core/protocol-envelope.json` defines a canonical envelope shape with `status` + `payload` required and `context_id` / `task_id` / `timestamp` / `replayed` / `governance_context` as optional siblings. The schema's own `notes` acknowledge that transports serialize this differently:
But the spec doesn't normatively define what MCP-specifically looks like on the wire. Two load-bearing questions aren't answered:
Q1 — Payload nesting in MCP
Given a response like the schema's own example:
```json
{
"status": "completed",
"context_id": "ctx_abc",
"timestamp": "2025-10-14T14:25:30Z",
"payload": { "products": [...] }
}
```
When an MCP tool returns this, do implementations serialize the JSON verbatim (nested `payload` object as a sibling of `status`), or are `payload` fields hoisted to the root?
Current shipping behavior across at least one SDK (`@adcp/client`'s `wrapEnvelope`) is flat: `{ products: [...], replayed: false, context: {...}, operation_id: "op_123" }`. No `status` field, no nested `payload`. Task response schemas like `media-buy/get-products-response.json` declare body fields at the root, not under `payload`.
Which is spec-conforming? The schema's `required: [status, payload]` suggests nested; shipping SDKs ship flat. Conformance testing can't grade this without a normative answer.
Q2 — `context_id` vs a `context` echo object
`protocol-envelope` defines `context_id: string` ("session/conversation identifier"). SDKs in the wild attach a `context: object` — the request's `context` block echoed back unchanged for caller-side correlation (internal tracing, UI session IDs, custom metadata). These are semantically distinct (session scope vs per-request echo) but similarly named.
Is `context` (echo object) a second envelope field that should join `protocol-envelope.json`, or is it the SDK's misinterpretation of `context_id`?
Why this matters
Proposed outcome
One of:
Happy to contribute the schema / docs change once the working group picks a direction.
Related