Skip to content

Task lifecycle (tasks/get/tasks/list/tasks/cancel) collides with MCP and A2A transport-native methods — recommend retiring AdCP's surface in favor of transport-native + extension fields #3209

Description

@bokelley

Summary

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.

Background — three actors, one wire name

AdCP tasks/get A2A tasks/get (JSON-RPC method) MCP tasks/get (JSON-RPC method)
Spec home core/tasks-get-response.json A2A spec — Task schema MCP experimental — Task schema
Task-id field task_id id taskId
Created/updated created_at, updated_at status.timestamp (single most-recent only) createdAt, lastUpdatedAt
Status enum A2A-aligned (see below) A2A-aligned (canonical) narrower + spelling drift
History history?: [{timestamp, type, data}] history?: Message[] separate tasks/result long-poll
Result result? (per #3126) Task.artifacts[] separate tasks/result long-poll
Extension carrier top-level task_type, protocol, has_webhook, progress, error.details Task.metadata (free-form) Task._meta (free-form)

Status-enum alignment

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:

{ taskId, status, ttl, createdAt, lastUpdatedAt, statusMessage?, pollInterval? }

That is what an AdCP buyer dispatching tasks/get over MCP receives. AdCP's storyboard runner validates against tasks-get-response.json, expecting:

{ task_id, task_type, protocol, status, created_at, updated_at, completed_at?, has_webhook?, progress?, error?, history?, result?, context?, ext? }

These shapes don't share a single field name. Sellers using @adcp/client either:

  • Run the SDK's auto-registered handler and fail AdCP storyboard polling, or
  • Override the protocol-level handler — which breaks the MCP TasksClient that parses with GetTaskResultSchema (camelCase only), or
  • Register a parallel tool with / 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 lifecycle

Delete core/tasks-get-request.json, core/tasks-get-response.json, core/tasks-list-request.json, core/tasks-list-response.json. Document that:

  • 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/getget_task_status, tasks/listlist_async_tasks, tasks/cancelcancel_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
  • adcp#3138 — force_task_completion controller scenario
  • adcp#3104, adcp#3115 — force_create_media_buy_arm
  • MCP SDK experimental tasks: @modelcontextprotocol/sdk/experimental/tasks/ (status enum at dist/esm/spec.types.d.ts:1227)
  • A2A SDK Task: @a2a-js/sdk/dist/extensions-APfrw8gz.d.ts:1621 (status enum at :1705)

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.rfcProtocol change — auto-adds to roadmap board

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions