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
Copy file name to clipboardExpand all lines: docs/protocol/v2/migration.mdx
+64-6Lines changed: 64 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,7 +15,23 @@ If you only remember five things, remember these:
15
15
4.**Capabilities were reorganized.** One `capabilities` + required `info` field on both sides, session-scoped groups nested under `session`, object support markers instead of booleans, and a required baseline of session methods.
16
16
5.**Everything is extensible now.** Enums and tagged unions accept unknown values; `_`-prefixed values are yours, the rest are reserved for future ACP versions.
17
17
18
-
The rest of this guide walks through each area: the [at-a-glance tables](#at-a-glance) for a quick diff, one section per change with before/after wire messages, and [step-by-step checklists](#migration-checklist-agents) at the end.
18
+
The rest of this guide walks through each area: the [at-a-glance tables](#at-a-glance) for a quick diff, one section per change with before/after wire messages, and [step-by-step checklists](#migration-checklist-agents) and [common mistakes](#common-migration-mistakes) at the end.
19
+
20
+
## Scope: stable v2 and draft surfaces
21
+
22
+
This guide describes the stable v2 baseline (`schema/v2/schema.json`). The v2 protocol surface as a whole is still labeled draft, so gate v2 support behind explicit version negotiation and feature flags until it stabilizes.
23
+
24
+
The unstable schema (`schema/v2/schema.unstable.json`) layers opt-in draft features on top of that baseline. Negotiating `protocolVersion: 2` does **not** imply any of them — gate each behind its own capability or feature flag, and keep stable-v2 and unstable-v2 test fixtures separate. Draft-only surfaces currently include:
4. Rework prompt handling around `state_update` instead of the `session/prompt` response.
91
+
5. Convert message, tool-call, and plan handling to keyed upserts.
92
+
6. Update permissions, diffs, MCP configs, and slash-command input.
93
+
7. Make parsing forward-compatible (open enums, batch arrays) and add v2 fixtures covering the full prompt lifecycle, replay, cancellation, permissions, and diff rendering.
94
+
69
95
## Initialization
70
96
71
97
### Role-agnostic `info` and `capabilities`
@@ -373,7 +399,7 @@ The v1 diff shape — a single `path` with `oldText`/`newText` — could not dis
@@ -434,7 +460,7 @@ There is no mechanical mapping from v2 diffs back to `oldText`/`newText` — whe
434
460
435
461
In v1, agents routinely stuffed permission prompt text into the tool call's `title`, mutating displayed tool state as a side effect. In v2, put prompt copy in `title`/`description` and let `subject.toolCall` carry only genuine tool-call state.
436
462
437
-
`options` and the response shape are unchanged: options carry `optionId`, `name`, and `kind` (`allow_once`, `allow_always`, `reject_once`, `reject_always`), and the response outcome is `{ "outcome": "selected", "optionId": ... }` or `{ "outcome": "cancelled" }`.
463
+
`options` and the response shape are unchanged: options carry `optionId`, `name`, and `kind` (`allow_once`, `allow_always`, `reject_once`, `reject_always`), and the response outcome is `{ "outcome": "selected", "optionId": ... }` or `{ "outcome": "cancelled" }`. Both unions are extensible in v2, with one safety rule: an Agent that receives an outcome it does not understand **MUST NOT** treat it as approval.
438
464
439
465
While a permission request is pending, the Agent **SHOULD** report `state_update` with `requires_action`, and `running` again once resolved.
440
466
@@ -504,7 +530,9 @@ The cursor is a tagged union so future versions can add replay-from-a-point vari
504
530
505
531
### Consistent lifecycle requests
506
532
507
-
`session/new` and `session/resume` take the same shape of environment parameters: a required `cwd` (absolute path), plus optional `additionalDirectories` and `mcpServers`. Note that `mcpServers` was required (even if empty) on `session/new` in v1 and is now optional. Both responses carry the session's `configOptions`; neither carries `modes` anymore.
533
+
`session/new` and `session/resume` take the same shape of environment parameters: a required `cwd` (absolute path), plus optional `additionalDirectories` and `mcpServers`. Note that `mcpServers` was required (even if empty) on `session/new` in v1 and is now optional; omitting it and sending `[]` are equivalent. Both responses carry the session's `configOptions`; neither carries `modes` anymore.
534
+
535
+
`additionalDirectories` still requires the `session.additionalDirectories` capability and absolute paths, and each `session/resume` must send the full intended additional-root list — omitting the field or sending `[]` activates no additional roots rather than restoring previous ones.
508
536
509
537
### Baseline methods
510
538
@@ -571,7 +599,9 @@ MCP transport configuration is aligned with the current MCP transport model:
571
599
572
600
## Content blocks
573
601
574
-
The five content block types are unchanged: `text`, `image`, `audio`, `resource_link`, and `resource`. v2 realigns their fields with the latest MCP specification — notably, `resource_link` gains an optional `icons` array — and the `type` discriminator is extensible like every other v2 enum, so unknown block types no longer break deserialization for receivers that have a fallback.
602
+
The five content block types are unchanged: `text`, `image`, `audio`, `resource_link`, and `resource`. v2 realigns their fields with the latest MCP specification — notably, `resource_link` gains an optional `icons` array (each icon has a required `src` URI plus optional `mimeType`, `sizes`, and `theme` of `light`/`dark`) — and the `type` discriminator is extensible like every other v2 enum, so unknown block types no longer break deserialization for receivers that have a fallback.
603
+
604
+
If you maintain a validator or generated models, v2 also tightens schema-level details that v1 left loose: base64 payloads (image/audio `data`, resource `blob`) are marked `format: "byte"`, URI fields are marked `format: "uri"`, and `annotations.priority` is bounded to the inclusive range 0–1.
575
605
576
606
## Slash commands
577
607
@@ -662,6 +692,30 @@ A standard remote transport (streamable HTTP with SSE streams, plus WebSocket) i
662
692
12.**Cancellation.** After `session/cancel`, keep accepting updates and wait for the idle `state_update` with `stopReason: "cancelled"` as confirmation.
663
693
13.**Parsing.** Preserve unknown enum variants, tolerate unknown `sessionUpdate` types, read command `input` as a tagged union, and accept batch arrays on stdio.
664
694
695
+
## Migration checklist: SDKs, validators, and proxies
696
+
697
+
1.**Separate the versions.** Keep v1 and v2 schemas, generated models, and test fixtures fully separate, and gate unstable-v2 surfaces independently of `protocolVersion: 2`.
698
+
2.**Three-state fields.** Model omitted vs `null` vs concrete values distinctly wherever v2 defines patch semantics — a plain nullable/optional type erases a distinction the protocol depends on.
699
+
3.**Strict where it counts.** Reject malformed payloads for _known_ discriminator values instead of demoting them to the unknown-variant fallback; the fallback exists only for genuinely unknown values.
700
+
4.**Preserve what you don't understand.** Round-trip `_`-prefixed extension values, unknown future variants, and `_meta` when storing, replaying, proxying, or forwarding.
701
+
5.**Validation refinements.** Account for `format: "uri"`, `format: "byte"`, and the 0–1 `annotations.priority` bounds when generating validators.
702
+
6.**Fixtures.** Cover the full prompt lifecycle, resume replay, cancellation, permission flow, message replacement and clearing, tool-call content chunks, structured diffs, and JSON-RPC batches — and document anywhere your v2→v1 bridging is lossy.
703
+
704
+
## Common migration mistakes
705
+
706
+
- Treating the `session/prompt` response as turn completion — or treating `requires_action` as idle.
707
+
- Rendering the outgoing prompt as the canonical user message instead of the Agent's `user_message` acknowledgment.
708
+
- Appending a whole-message `content` array instead of replacing accumulated content (or replacing on chunks, which should append).
709
+
- Collapsing omitted and `null` patch fields into a single "missing" state.
710
+
- Still checking `clientCapabilities.fs` / `clientCapabilities.terminal`, or calling `session/load` or `session/set_mode`, on v2 connections.
711
+
- Assuming terminal _authentication_ was removed because terminal _execution_ was — they are separate surfaces, and only execution is gone.
712
+
- Advertising `capabilities.session` without implementing the entire baseline method set.
713
+
- Omitting the `type` discriminator on auth methods, MCP server configs, or command input because v1 had implicit defaults.
714
+
- Reading config options from `id`/`group` instead of `configId`/`groupId`.
715
+
- Minting non-underscore custom enum values, or treating an unknown non-underscore value as someone's extension when it is reserved for future ACP versions.
716
+
- Enabling unstable-v2 methods on every v2 connection instead of gating each behind its own flag.
717
+
- Treating an unknown permission outcome as approval.
718
+
665
719
## Supporting v1 and v2 side by side
666
720
667
721
Version negotiation gives you one protocol version per connection, so the cleanest approach is to keep two thin protocol surfaces behind shared application logic and select one after `initialize`. Design your internal model around v2 concepts — upserts, message IDs, session states, structured diffs — and downgrade at the edge for v1 peers, because the reverse direction doesn't round-trip:
@@ -670,4 +724,8 @@ Version negotiation gives you one protocol version per connection, so the cleane
670
724
- v2 deliberately drops v1's client file system and terminal surface and the modes API, so v1 peers relying on those need genuine fallback behavior, not just field mapping.
671
725
- The prompt lifecycles differ architecturally: emulating v1 on top of a v2 core means holding the `session/prompt` response until your v2-style idle state fires — simple in that direction, impossible in the other.
672
726
673
-
If a message can't be expressed for a v1 peer, degrade deliberately (drop the refinement, not the message) rather than relying on any automatic translation to decide for you.
727
+
Treat the two versions as separate wire contracts with version-specific encoders and decoders — don't build a v2 implementation on generic v1↔v2 conversion helpers. If a message can't be expressed for a v1 peer, degrade deliberately (drop the refinement, not the message) rather than letting an automatic translation decide for you, and fail clearly when a v2 feature has no v1 representation at all.
728
+
729
+
## Next steps
730
+
731
+
Work through the v2 docs in an order that mirrors a connection's lifecycle: [Initialization](/protocol/v2/initialization), [Authentication](/protocol/v2/authentication), [Session Setup](/protocol/v2/session-setup), [Prompt Lifecycle](/protocol/v2/prompt-lifecycle), [Content](/protocol/v2/content), [Tool Calls](/protocol/v2/tool-calls), [Agent Plan](/protocol/v2/agent-plan), [Session Config Options](/protocol/v2/session-config-options), [Extensibility](/protocol/v2/extensibility), [Transports](/protocol/v2/transports), and the [Schema](/protocol/v2/schema) reference.
0 commit comments