You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
AdCP currently defines its own tasks/get, tasks/list, and tasks/cancel with first-class request/response schemas (core/tasks-get-request.json, core/tasks-get-response.json, core/tasks-list-request.json, core/tasks-list-response.json). These names collide with the JSON-RPC methods that both MCP (@modelcontextprotocol/sdk/experimental/tasks/) and A2A (@a2a-js/sdk) expose at the transport layer, and the AdCP shapes don't match either transport's wire shape. SDK implementations end up either (a) running an interceptor that breaks transport-native clients, or (b) shipping a parallel tool that buyers don't know to call. Reference SDK gap: adcp-client#994.
The good news: A2A's task model already aligns with AdCP's. The bad news: MCP's doesn't — and MCP's surface is still experimental, so we have an opening to fix it upstream rather than fight it from inside AdCP.
AdCP's enums/task-status.json already declares: "Standardized task status values based on A2A TaskState enum." Verifying:
Status
AdCP
A2A TaskState
MCP TaskStatus
submitted
✅
✅
❌ missing
working
✅
✅
✅
input-required
hyphen
hyphen
input_required (underscore)
completed
✅
✅
✅
canceled
American
American
cancelled (British)
failed
✅
✅
✅
rejected
✅
✅
❌ missing
auth-required
✅
✅
❌ missing
unknown
✅
✅
❌ missing
A2A is a 1:1 match. MCP is missing 4 values critical to AdCP semantics — including submitted, which the new force_create_media_buy_arm / force_task_completion controller scenarios (#3104, #3138) depend on for buyer-supplied determinism.
The gap in concrete terms
Per node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js:39-51, when a TaskStore is provided the MCP SDK auto-registers a tasks/get JSON-RPC handler that returns the MCP Task shape:
A2A buyers dispatch tasks/get/tasks/cancel/tasks/resubscribe JSON-RPC methods directly. Status enum already aligns. AdCP-specific fields (task_type, protocol, has_webhook, progress) ride in Task.metadata under a namespaced key (e.g. org.adcontextprotocol/task). The result rides in Task.artifacts[] per A2A convention. history rides in Task.history per A2A convention.
MCP buyers dispatch tasks/get/tasks/result/tasks/cancel JSON-RPC methods directly. AdCP-specific fields ride in Task._meta under the same namespaced key. The result is fetched via tasks/result per MCP convention.
Blocker: MCP's status enum is narrower than AdCP's. Without a fix in MCP, AdCP loses submitted, rejected, auth-required, unknown over the MCP transport. Mitigation: file an upstream issue against modelcontextprotocol/specification proposing the experimental-tasks status enum align with A2A's TaskState. The MCP transport for tasks is still experimental — this is the time to fix it.
Pro: zero collision; one wire path per transport. Idiomatic on both sides. Con: depends on MCP enum fix landing, or AdCP ships with a documented "MCP buyers will see submitted mapped to working until upstream lands" caveat.
Option 2: Rename AdCP's task-lifecycle ops to non-colliding tool names
Rename tasks/get → get_task_status, tasks/list → list_async_tasks, tasks/cancel → cancel_async_task. They become ordinary AdCP tools (snake_case, dispatched via tools/call on MCP, message/send with skill on A2A). The transport-level tasks/* JSON-RPC methods stay owned by their respective transports for whatever the transport wants to do with them.
Pro: zero ambiguity, no upstream dependency, AdCP keeps its richer status semantics intact. Con: spec churn; breaking change for any early adopter coding directly to tasks/get. Loses the "AdCP follows A2A's lead" symmetry.
Option 3: Hybrid — A2A native, MCP renamed
Keep AdCP's task lifecycle riding on A2A's transport-native methods (1:1 alignment already exists). On MCP, rename to get_task_status etc. Document the per-transport mapping explicitly in the spec.
Pro: capitalizes on A2A's clean alignment without waiting on MCP upstream. No breaking change for A2A early adopters. Con: AdCP's wire-form differs by transport, contradicting AdCP's "same shape across transports" design principle. Asymmetric.
Recommendation
Option 1, paired with an upstream issue against modelcontextprotocol/specification proposing the experimental task-status enum align with A2A's TaskState. AdCP's task lifecycle was already designed to A2A's enum; it makes sense to push MCP to converge rather than for AdCP to maintain a parallel surface that fights both transports. If the MCP enum fix doesn't land in time, ship Option 1 with the spelling/missing-value mapping documented as a known transport-mapping caveat for MCP.
If MCP upstream proves unwilling, fall back to Option 3 — A2A buyers get the clean transport-native path; MCP buyers call renamed AdCP tools.
Out of scope (but worth noting)
tasks-list-request.json and tasks-list-response.json are also wire-shape, not just tasks-get-response.json. Resolution should cover all three.
tasks/result (MCP) has no AdCP equivalent today; if Option 1 is adopted, the spec needs to document that MCP buyers fetch the typed result via tasks/result, A2A buyers via the artifact on the resolved Task, and that's where the typed payload from feat(schema): add result and include_result to tasks/get #3126 actually lives.
The webhook_url callback channel is independent of polling and unaffected by this proposal.
References
adcp-client#994 — SDK-side gap that surfaced this
adcp-client#996 — Gap 1 fix (caller-supplied task_id) shipped 2026-04-25
adcp#3126 — typed result + include_result on tasks/get
Summary
AdCP currently defines its own
tasks/get,tasks/list, andtasks/cancelwith first-class request/response schemas (core/tasks-get-request.json,core/tasks-get-response.json,core/tasks-list-request.json,core/tasks-list-response.json). These names collide with the JSON-RPC methods that both MCP (@modelcontextprotocol/sdk/experimental/tasks/) and A2A (@a2a-js/sdk) expose at the transport layer, and the AdCP shapes don't match either transport's wire shape. SDK implementations end up either (a) running an interceptor that breaks transport-native clients, or (b) shipping a parallel tool that buyers don't know to call. Reference SDK gap: adcp-client#994.The good news: A2A's task model already aligns with AdCP's. The bad news: MCP's doesn't — and MCP's surface is still experimental, so we have an opening to fix it upstream rather than fight it from inside AdCP.
Background — three actors, one wire name
tasks/gettasks/get(JSON-RPC method)tasks/get(JSON-RPC method)core/tasks-get-response.jsonTaskschemaTaskschematask_ididtaskIdcreated_at,updated_atstatus.timestamp(single most-recent only)createdAt,lastUpdatedAthistory?: [{timestamp, type, data}]history?: Message[]tasks/resultlong-pollresult?(per #3126)Task.artifacts[]tasks/resultlong-polltask_type,protocol,has_webhook,progress,error.detailsTask.metadata(free-form)Task._meta(free-form)Status-enum alignment
AdCP's
enums/task-status.jsonalready declares: "Standardized task status values based on A2A TaskState enum." Verifying:TaskStateTaskStatussubmittedworkinginput-requiredinput_required(underscore)completedcanceledcancelled(British)failedrejectedauth-requiredunknownA2A is a 1:1 match. MCP is missing 4 values critical to AdCP semantics — including
submitted, which the newforce_create_media_buy_arm/force_task_completioncontroller scenarios (#3104, #3138) depend on for buyer-supplied determinism.The gap in concrete terms
Per
node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js:39-51, when a TaskStore is provided the MCP SDK auto-registers atasks/getJSON-RPC handler that returns the MCPTaskshape:That is what an AdCP buyer dispatching
tasks/getover MCP receives. AdCP's storyboard runner validates againsttasks-get-response.json, expecting:These shapes don't share a single field name. Sellers using
@adcp/clienteither:TasksClientthat parses withGetTaskResultSchema(camelCase only), or/in its name (unconventional and easy to confuse with the transport method).There's no clean wiring inside the SDK. The collision is in the spec.
Proposal — three options
Option 1: Retire AdCP's
tasks/*surface; ride on transport-native lifecycleDelete
core/tasks-get-request.json,core/tasks-get-response.json,core/tasks-list-request.json,core/tasks-list-response.json. Document that:tasks/get/tasks/cancel/tasks/resubscribeJSON-RPC methods directly. Status enum already aligns. AdCP-specific fields (task_type,protocol,has_webhook,progress) ride inTask.metadataunder a namespaced key (e.g.org.adcontextprotocol/task). Theresultrides inTask.artifacts[]per A2A convention.historyrides inTask.historyper A2A convention.tasks/get/tasks/result/tasks/cancelJSON-RPC methods directly. AdCP-specific fields ride inTask._metaunder the same namespaced key. Theresultis fetched viatasks/resultper MCP convention.Blocker: MCP's status enum is narrower than AdCP's. Without a fix in MCP, AdCP loses
submitted,rejected,auth-required,unknownover the MCP transport. Mitigation: file an upstream issue againstmodelcontextprotocol/specificationproposing the experimental-tasks status enum align with A2A'sTaskState. The MCP transport for tasks is still experimental — this is the time to fix it.Pro: zero collision; one wire path per transport. Idiomatic on both sides.
Con: depends on MCP enum fix landing, or AdCP ships with a documented "MCP buyers will see
submittedmapped toworkinguntil upstream lands" caveat.Option 2: Rename AdCP's task-lifecycle ops to non-colliding tool names
Rename
tasks/get→get_task_status,tasks/list→list_async_tasks,tasks/cancel→cancel_async_task. They become ordinary AdCP tools (snake_case, dispatched viatools/callon MCP,message/sendwith skill on A2A). The transport-leveltasks/*JSON-RPC methods stay owned by their respective transports for whatever the transport wants to do with them.Pro: zero ambiguity, no upstream dependency, AdCP keeps its richer status semantics intact.
Con: spec churn; breaking change for any early adopter coding directly to
tasks/get. Loses the "AdCP follows A2A's lead" symmetry.Option 3: Hybrid — A2A native, MCP renamed
Keep AdCP's task lifecycle riding on A2A's transport-native methods (1:1 alignment already exists). On MCP, rename to
get_task_statusetc. Document the per-transport mapping explicitly in the spec.Pro: capitalizes on A2A's clean alignment without waiting on MCP upstream. No breaking change for A2A early adopters.
Con: AdCP's wire-form differs by transport, contradicting AdCP's "same shape across transports" design principle. Asymmetric.
Recommendation
Option 1, paired with an upstream issue against
modelcontextprotocol/specificationproposing the experimental task-status enum align with A2A'sTaskState. AdCP's task lifecycle was already designed to A2A's enum; it makes sense to push MCP to converge rather than for AdCP to maintain a parallel surface that fights both transports. If the MCP enum fix doesn't land in time, ship Option 1 with the spelling/missing-value mapping documented as a known transport-mapping caveat for MCP.If MCP upstream proves unwilling, fall back to Option 3 — A2A buyers get the clean transport-native path; MCP buyers call renamed AdCP tools.
Out of scope (but worth noting)
tasks-list-request.jsonandtasks-list-response.jsonare also wire-shape, not justtasks-get-response.json. Resolution should cover all three.tasks/result(MCP) has no AdCP equivalent today; if Option 1 is adopted, the spec needs to document that MCP buyers fetch the typed result viatasks/result, A2A buyers via the artifact on the resolved Task, and that's where the typed payload from feat(schema): add result and include_result to tasks/get #3126 actually lives.webhook_urlcallback channel is independent of polling and unaffected by this proposal.References
task_id) shipped 2026-04-25result+include_resultontasks/getforce_task_completioncontroller scenarioforce_create_media_buy_arm@modelcontextprotocol/sdk/experimental/tasks/(status enum atdist/esm/spec.types.d.ts:1227)@a2a-js/sdk/dist/extensions-APfrw8gz.d.ts:1621(status enum at:1705)