Federation profile — the A2A / MCP Tasks field mapping

Copy Markdown View Source

The Agent Blueprint Protocol's federation profile maps every logical task and evidence field against the CURRENT A2A and MCP Tasks contracts before inventing anything (the mapping-before-inventing law). The verdict, re-derived against the live sources below: 3 fields map natively, 5 partially, and 15 have no safe native home — so no protocol-native wire field and no native transport are warranted. The profile is one registered extension (com.example/federation, critical) carried inside each transport's untyped extension channel, with the same JCS-canonical object and the same detached Ed25519 signature envelope on both.

Pinned sources (re-derived 2026-08-22)

SourceVersion / pathshasum
a2a.protoA2A 1.0.0 (package lf.a2a.v1)fc18e7c29777b64dc1b4119a02859fbfd5937383
ext-tasks specspecification/draft/tasks.md52dd19738a3dac86518c28344b18630e29b12999
ext-tasks schemaschema/draft/schema.tsedb18b6edeab2ebb9807c8c03cccd8984d1d0ebc
ext-tasks typesschema/draft/spec.types.ts4070c34bf3a88ebe3eff45809f91d805cf30c5b3

Line citations in the table are the LIVE line numbers of these exact files.

The mapping table

Verdicts: native = the transport field carries the semantic exactly; partial = a native home exists but loses or misplaces the semantic; extension = no native home; the field rides the extension body.

#Logical fieldA2A locationMCP Tasks locationVerdict
1task identityTask.id (a2a.proto:170)Task.taskId (mcp-schema.ts:47)native
2idempotency identitynone — Message.message_id (a2a.proto:262) is a message id, not an idempotency keynoneextension
3parent execution referenceMessage.reference_task_ids (a2a.proto:276), Task.context_id (a2a.proto:173) — a reference, not a parent executionnonepartial
4initiating subjectnone — SecurityScheme/SecurityRequirement (a2a.proto:496-517) are transport-levelnone — transport authextension
5exact Blueprint digestnonenoneextension
6exact Deployment digestnonenoneextension
7input commitmentMessage.parts[].data (a2a.proto:233), Part.metadata (a2a.proto:236) — a payload home, not a digest commitmentrequest paramspartial
8result schema identityPart.media_type (a2a.proto:241), AgentSkill.output_modes (a2a.proto:450) — a media type is not a schema identityCompletedTask.result {[key: string]: unknown} (mcp-schema.ts:135)extension
9result classification ceilingnonenoneextension
10bounded time policySendMessageConfiguration (a2a.proto:143-161) has no time boundTask.ttlMs (mcp-schema.ts:82)partial
11bounded resource policynonenoneextension
12recovery handleTask.id + GetTask (a2a.proto:45)taskId + tasks/getnative
13issuertransport-level only (SecurityScheme)transport authextension
14subjectnonenoneextension
15audiencenonenoneextension
16local identity-mapping evidencenonenoneextension
17typed checkpoint requestTASK_STATE_INPUT_REQUIRED (a2a.proto:201) + TaskStatus.message — free-form, not typedstatus input_required + inputRequests (InputRequest union: elicitation/sampling/roots; mcp-schema.ts:111-119, mcp-spec.types.ts:545)partial
18checkpoint statusTaskStatus.state (a2a.proto:213)Task.status (mcp-schema.ts:31-36)native
19checkpoint decision commitmentnonetasks/update inputResponses (mcp-schema.ts:232-246) carries responses, no commitment digestextension
20terminal stateTASK_STATE_COMPLETED/FAILED/CANCELED/REJECTED (a2a.proto:194-205)completed/failed/cancelled (mcp-schema.ts:31-36)partial
21signed evidence receiptAgentCardSignature (a2a.proto:455-467) signs the AgentCard, not a task resultnoneextension
22exact compatibility referenceAgentCard.version (a2a.proto:374-376) — a version string, not an exact build identitynoneextension
23authority-proof referencestransport-level onlytransport authextension

The same rows are shipped as data: AgentBlueprintProtocol.federation_mapping/0.

Transport flags (binding on host adapters)

A2A is the primary lane. The MCP Tasks mapping ships flagged draft-tracking: the extension lives outside the MCP core, has zero tagged releases as of 2026-08-22, and is outside any deprecation guarantee. Revisit trigger: the first tagged ext-tasks release.

ttlMs is not an execution bound. MCP's Task.ttlMs is a storage TTL — "the server MAY discard the task after the TTL elapses", and the value MAY change over a task's lifetime. It bounds record retention, not work; the envelope's time_policy is the execution bound and rides the extension body on both transports.

AUTH_REQUIRED is not authorization. A2A §7.6.4: agents MUST NOT treat the TASK_STATE_AUTH_REQUIRED state transition, by itself, as authorization for any operation. :auth_required crossing into MCP denies (:federation_state_unmappable — MCP has no counterpart state); inside A2A it is an interrupted state, never a terminal receipt.

AgentCard signatures need protobuf pre-normalization. A2A §8.4.1 specifies the canonicalization for AgentCard signing: BEFORE RFC 8785 JCS, the JSON must respect proto3 field presence — unset optional fields omitted, explicitly-set optionals kept even at default values, REQUIRED fields always present, default values omitted unless REQUIRED/optional, and the signatures field excluded from the signed content. A host adapter that JCS-canonicalizes the received card JSON directly computes a different preimage and verification fails silently.

Carrier keys and lossy-state rules

The envelope rides Task.metadata[<a2a_uri>] on A2A (keyed by the registry entry's https://example.com/extensions/federation, following A2A's URI-keyed metadata convention) and _meta["com.example/federation"] on MCP (the reverse-DNS namespace; the MCP _meta key grammar forbids a :// URI key). The same JCS-canonical object under both.

State mapping is explicit and lossy-aware: A2A TASK_STATE_UNSPECIFIED denies :federation_state_unmappable at intake; crossing into MCP denies for :rejected (terminal, no MCP counterpart) and :submitted/:auth_required (no counterpart) — never a silent degrade. cancelled (MCP) folds to :canceled (A2A single-L). Cancellation is a request, never a terminal receipt: no codec path synthesizes terminal evidence from a cancel request or acknowledgment (MCP cancellation is cooperative and eventually consistent — a cancelled task "MAY ultimately reach a terminal status other than cancelled").

Correlation grants nothing. The envelope's identity fields (issuer/subject/audience and the identity-mapping evidence) are correlation only; an authority-shaped claim in the evidence block denies :nonportable_content. Two terminal receipts for one task identity diverging in ANY commitment component — not just state — deny :federation_terminal_conflict.

Receiver posture (review-settled)

Purpose pinning. verify_commitment/2 accepts only signatures whose signed purpose is federation-envelope — a signature minted for a blueprint or deployment never verifies a federation receipt, whatever digest it names.

Key ids are labels, and the signature blob is opaque. The receipt's kid/key_id positions are portability-scanned in the strict mode: a PEM block or hex secret riding a key id denies :forbidden_portable_value. Use named, low-entropy key labels (the corpus shape), not embedded fingerprints. The signature member itself is deliberately unscanned — every honest signature is entropy-dense by nature; it never survives verification without the matching key.

Keys are not issuers — but attribution is expressible. The flat Context.keys pool matches by key_id only; nothing binds a key to the receipt's self-declared issuer string. Receivers wanting per-issuer attribution set Context.issuer_key_sets (issuer → keys): the receipt's claimed issuer member — proven signed by the binding step — selects the pool for the signature attempt, org-B's key never verifies a receipt claiming org-A, and precedence is strictest-wins (sets non-nil ⇒ the flat pool is ignored; an empty map trusts no issuer). Possession-of-key == signing authority remains the default model for single-issuer receivers.

Replay is the receiver's policy — with a signed-staleness mechanism. A receipt re-verifies idempotently; divergence-replay is prior_receipts state. Staleness is enforceable over signed bytes: Context.created_after/created_before bound the signed created_at (RFC 3339 Z-form, inclusive both ends; the receiver supplies the window, the package never invents a clock), checked after conflict detection so integrity and consistency outrank receiver policy.

The codec lane is bounded like the decode lane. Carrier reconstruction (from_a2a_carrier/from_mcp_carrier) re-imposes the decoder's member/item/node/string/depth ceilings and the UTF-8 gate on hand-built carrier maps — a hostile carrier cannot buy unbounded validation work or crash later digest encodes with non-UTF-8 bytes.

One namespace, two bodies

com.example/federation names two deliberately distinct objects: the ARTIFACT-side critical-extension body a Blueprint declares (the digest-pinned issuer/subject/audience trio in the compiled-in extension registry, validated at negotiation) and the WIRE-side TaskEnvelope extension body (the 19-member JCS object under the carrier key, validated by the Federation field table, which never passes through negotiation). The artifact-side schema is a frozen-digest contract for a different surface; widening it to cover the envelope would re-pin a shipped digest for no negotiation-time gain.