From 1fa704638cda28b73564c8c6054267ed8a2d52a8 Mon Sep 17 00:00:00 2001 From: Rohith Gajawada <141448174+RohithGajawada45@users.noreply.github.com> Date: Thu, 30 Jul 2026 00:59:47 +0530 Subject: [PATCH 1/6] Add draft SEP for recovery metadata --- seps/0000-tool-recovery-metadata.md | 120 ++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 seps/0000-tool-recovery-metadata.md diff --git a/seps/0000-tool-recovery-metadata.md b/seps/0000-tool-recovery-metadata.md new file mode 100644 index 000000000..e94eb5a5b --- /dev/null +++ b/seps/0000-tool-recovery-metadata.md @@ -0,0 +1,120 @@ +# Recovery Metadata for MCP ToolAnnotations + +## 1. Preamble + +- **Title:** Recovery Metadata for MCP ToolAnnotations +- **Author:** Rohith Gajawada (@RohithGajawada45), Mahathi Kanduri (@Mahathi04) +- **Status:** Proposal (pre-submission draft) +- **Type:** Standards Track +- **PR:** TBD (assigned on submission per SEP-1850 process) +- **Created:** 2026-07-30 + +## 2. Abstract + +MCP's `ToolAnnotations` currently describe whether a tool is read-only, destructive, idempotent, or operates in an open world — information aimed primarily at approval/UX decisions ("should a human be asked before this runs"). None of these annotations tell an orchestrator what to do *after* a tool has already succeeded and a later step in the same workflow fails. This proposal adds an optional `recovery` field to `ToolAnnotations` that lets a tool declare a compensating operation — a separate tool call, with parameters bound from the original call's response — that can undo its effect. The binding syntax follows the `Link` object from OpenAPI 3.0, adapted to MCP's tool-call model. The field is additive, advisory, and orthogonal to `idempotentHint`: idempotency governs safe retry of a single step that has not yet completed; `recovery` governs safe undoing of a step that has already completed, when a subsequent step in the same workflow fails. Where no compensating operation exists or is declared, the workflow's failure handling is unchanged from today. + +## 3. Motivation + +MCP tool calls are increasingly composed into multi-step workflows by orchestrators (LangGraph, custom agent loops, etc.) where an early step causes a real, external side effect — a payment is charged, an inventory unit is reserved — before a later step fails. Today, `ToolAnnotations` gives an orchestrator no machine-readable signal about what to do next. It can inspect `destructiveHint` or `idempotentHint` on the *failed* tool, but neither field says anything about the *already-succeeded* tools earlier in the chain. In practice this means one of three things happens: the workflow aborts and leaves side effects in place for a human to clean up; the orchestrator hard-codes framework-specific cleanup logic that doesn't travel if the workflow is later run by a different orchestrator; or teams build a bespoke state/compensation layer on top of MCP, which is exactly the kind of gap several production teams (including a Shopify engineer describing a "MCP Lite" workaround, and a widely cited "Is MCP Outdated? A 2026 Reality Check" write-up) have already reported hitting. + +The pattern this workflow needs — compensating actions that undo the effect of an already-committed step — is well established in distributed systems (the Saga pattern) and has already been implemented at the framework layer more than once for LLM agent workflows specifically (SagaLLM, ALAS, and LangGraph v1.2's built-in Saga support). What's missing is not the pattern itself, but a way to express it *in the tool's own wire-format descriptor*, so that any MCP-compliant orchestrator — not just the one framework that happened to implement Saga support — can discover and use it. Today, a tool that has a well-defined compensating action (e.g. `refund_payment` for `charge_payment`) has no way to advertise that fact to a client it doesn't know about in advance. + +## 4. Specification + +### 4.1 New field: `recovery` + +An optional field is added to the `ToolAnnotations` interface: + +```typescript +interface ToolAnnotations { + // ... existing fields (title, readOnlyHint, destructiveHint, + // idempotentHint, openWorldHint) unchanged ... + + recovery?: { + strategy: "compensation"; + compensatingOperation: { + operationId: string; // name of a tool exposed by the same server + parameters: Record; // runtime-expression bindings, see 4.2 + }; + }; +} +``` + +`strategy` is a string enum to allow future extension (e.g. `"retry-elsewhere"`, `"manual-only"`) without a breaking change; this proposal defines only `"compensation"`. A tool with no `recovery` field makes no claim about recoverability, identical to today's behavior — this is a purely additive change. + +This proposal deliberately scopes `compensatingOperation` to `operationId` — a tool exposed by the same server as the original call. A cross-server reference (analogous to OpenAPI's `operationRef`) would raise separate questions of authentication, remote discovery, trust, and failure domains that are unnecessary for the motivating use case and are left to a future SEP if same-server compensation proves useful in practice. + +### 4.2 Parameter binding + +Binding of the compensating operation's parameters follows OpenAPI 3.0's `Link` object runtime-expression syntax (`$response.body#/field`, `$response.header.`, etc.), applied to the *original* tool call's response. For example, a `charge_payment` tool that returns `{"transactionId": "TXN-123", "amount": 59.97}` can declare: + +```yaml +recovery: + strategy: compensation + compensatingOperation: + operationId: refund_payment + parameters: + transactionId: $response.body#/transactionId +``` + +An orchestrator that needs to compensate this call resolves `$response.body#/transactionId` against the stored response of the original `charge_payment` invocation, then invokes `refund_payment` with the resolved value. The syntax intentionally mirrors OpenAPI 3.0 runtime expressions so implementations may reuse existing parsers where convenient; no new expression language is introduced. + +If a runtime expression cannot be resolved (e.g. the referenced field is absent from the stored response), the orchestrator MUST treat recovery metadata as unavailable for that operation and fall back to today's behavior for that step, rather than invoking the compensating operation with partial or default arguments. + +### 4.3 Orchestrator behavior (advisory, not mandated) + +This SEP does not require any client or orchestrator to implement compensation. It defines the *metadata shape* only. A conforming orchestrator that supports recovery MAY use the declared recovery metadata to compensate previously completed operations on workflow failure. Reverse completion order is RECOMMENDED as a default strategy. Steps with no `recovery` field are left as-is, matching current behavior. + +### 4.4 Non-goals + +This proposal explicitly does **not**: +- Define distributed transactions or ACID guarantees across tool calls. +- Require any orchestrator to implement compensation. +- Standardize a workflow engine, execution graph, or ordering language. +- Define retry policies, backoff, or checkpoint/resume semantics — these are separable concerns, out of scope here, and left to future proposals. +- Guarantee that compensation itself succeeds — see Rationale, "Open questions." + +## 5. Rationale + +### 5.1 Why not just use `idempotentHint`? + +A related proposal, SEP-1984 ("Comprehensive Tool Annotations"), included a `reversibleHint` boolean, and a reviewer objected that "reverting is usually harder to implement and error prone" and that idempotent retry is generally the more robust pattern. That objection is correct for the failure mode it addresses, but idempotency and compensation address different failure modes: + +| Failure | Idempotency helps? | Compensation helps? | +|---|---|---| +| Network timeout before completion | Yes | Usually unnecessary | +| Duplicate invocation of the same step | Yes | No | +| A later, independent step fails after this step already committed a durable side effect | No | Yes | + +When a workflow step fails *after* an earlier step has already committed a durable side effect, retrying the earlier operation does not undo it — retrying `charge_payment` does not un-charge a card that was already charged; it only guards against charging it twice. A compensating operation is required to restore the previous state. The two mechanisms are complementary, not competing, and this proposal is scoped narrowly to the second case, which today has no representation in `ToolAnnotations` at all. + +### 5.2 Related work + +- **SEP-1984** proposed a boolean `reversibleHint` with no binding mechanism; this proposal instead reuses OpenAPI's `Link` runtime-expression convention to make the compensating call directly invocable, and deliberately avoids the term "reversible" since not every operation with a recovery strategy is fully undoable (see 4.4). +- **SEP-2487** ("Add `execution.requirements` field to Tool for preconditions") addresses a different problem — declaring what must be true *before* a tool runs (auth, approval, ordering) — and is complementary rather than overlapping with this proposal, which addresses what to do *after* a tool has already run and a later step fails. +- **SagaLLM**, **ALAS**, and **Atomix** demonstrate the Saga/compensation pattern for LLM agent workflows at the framework or research-prototype level. **LangGraph v1.2** ships built-in Saga support inside its own state graph. None of these expose recovery relationships through a protocol-level tool description that independent orchestrators can consume without framework-specific integration. This proposal's contribution is narrowly that interoperability, not the underlying pattern, which is well established elsewhere. + +### 5.3 Why recovery metadata belongs on the tool, not the workflow + +An alternative design would declare compensation relationships in the workflow definition (as LangGraph's Saga support does) rather than on the tool descriptor itself. This proposal deliberately places `recovery` on the tool because it describes a property of the operation, not of any particular workflow. A payment capture is compensated by a refund regardless of whether it appears in a checkout workflow, an order-modification workflow, or a subscription-renewal workflow. Declaring the relationship once, on the tool, avoids duplicating the same recovery knowledge across every workflow definition and every orchestrator that happens to call the tool — which is the same reasoning that already justifies putting `idempotentHint` and `destructiveHint` on the tool rather than requiring each caller to know and redeclare them. + +A related but distinct objection is that `recovery`, unlike existing annotation fields, references *another* operation rather than describing the current one in isolation, and so arguably doesn't belong in `ToolAnnotations` at all. Although `recovery` references another operation, it still describes a behavioral property of the current operation: namely, how its externally visible effects may be compensated. This is analogous to `idempotentHint`, which likewise describes execution semantics rather than presentation or approval metadata — the fact that expressing "how to compensate this" requires naming a second operation doesn't change that the claim being made is about the *first* operation's behavior. + +### 5.4 Open questions + +- **Compensation ordering:** the reference implementation compensates in reverse (LIFO) order of completion. This SEP does not mandate that orchestrators use LIFO — only recommends it as a sensible default — since some workflows may have compensations that are safe to run in any order or in parallel. +- **Failure of compensation itself:** if a compensating operation fails, this SEP does not define a terminal protocol-level state for that case. It is left to the orchestrator (e.g., surface to a human, retry the compensation if it is itself idempotent) pending real-world experience with this field before standardizing further. + +## 6. Backward Compatibility + +Fully backward compatible. `recovery` is an optional field on `ToolAnnotations`; existing servers, clients, and tools that do not set it are unaffected, and existing clients that don't recognize the field will simply ignore it, per MCP's existing annotation-handling guidance. + +## 7. Reference Implementation + +A standalone prototype (~230 lines) implements a three-step workflow (`reserve_inventory` → `charge_payment` → `create_shipment`) with a simulated failure in the final step. It runs the workflow twice: once against a baseline orchestrator that reads only today's `ToolAnnotations` (workflow aborts, side effects left in place), and once against an orchestrator that also reads the proposed `recovery` field (workflow automatically compensates in reverse order and reaches a consistent terminal state). The orchestrator code contains no hard-coded references to `refund_payment` or `release_inventory` — those names appear only in the tool metadata itself, demonstrating that the recovery behavior is driven entirely by declared metadata rather than framework-specific glue. The reference implementation demonstrates that identical workflow logic can execute under both orchestrators, with the enhanced orchestrator deriving recovery behavior exclusively from declared metadata and without embedding tool-specific recovery logic. [https://github.com/RohithGajawada45/mcp-recovery-metadata-prototype] + +## 8. Security Implications + +`recovery.compensatingOperation` causes a tool call to be invoked automatically, without a fresh human-in-the-loop approval, when a workflow fails — by an orchestrator that chooses to support this field. Implementers should treat a declared compensating operation with at least the same scrutiny as the original tool call: a malicious or compromised server could declare a `compensatingOperation` that does something other than what its name implies. As with existing `ToolAnnotations`, clients MUST NOT treat `recovery` metadata as fully trusted attestation from a potentially untrusted server, and orchestrators that auto-invoke compensating operations should consider surfacing them to the same approval/audit path as the original destructive or state-changing call. + +Automatic compensation can itself trigger a destructive operation (a refund, a deletion, a release of a held resource) without a fresh human-in-the-loop approval at the moment it runs. An orchestrator MAY require the same approval policy for an automatically-invoked `compensatingOperation` that it would have required had that operation been invoked directly by the model — this is a natural extension of the existing approval discussion above, not a separate mechanism. From 322c937c9e90b5921f5589460cc15882d2589674 Mon Sep 17 00:00:00 2001 From: Rohith Gajawada <141448174+RohithGajawada45@users.noreply.github.com> Date: Thu, 30 Jul 2026 01:04:06 +0530 Subject: [PATCH 2/6] Add 3172 tool recovery metadata document --- ...0-tool-recovery-metadata.md => 3172-tool-recovery-metadata.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename seps/{0000-tool-recovery-metadata.md => 3172-tool-recovery-metadata.md} (100%) diff --git a/seps/0000-tool-recovery-metadata.md b/seps/3172-tool-recovery-metadata.md similarity index 100% rename from seps/0000-tool-recovery-metadata.md rename to seps/3172-tool-recovery-metadata.md From 377482c27bb3f5190b9e7b0c31ede2212cb8b1d5 Mon Sep 17 00:00:00 2001 From: Rohith Gajawada <141448174+RohithGajawada45@users.noreply.github.com> Date: Thu, 30 Jul 2026 01:08:56 +0530 Subject: [PATCH 3/6] Revise author details and enhance formatting Updated author information and improved formatting in the recovery metadata proposal. --- seps/3172-tool-recovery-metadata.md | 35 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/seps/3172-tool-recovery-metadata.md b/seps/3172-tool-recovery-metadata.md index e94eb5a5b..73ac5e9be 100644 --- a/seps/3172-tool-recovery-metadata.md +++ b/seps/3172-tool-recovery-metadata.md @@ -3,7 +3,7 @@ ## 1. Preamble - **Title:** Recovery Metadata for MCP ToolAnnotations -- **Author:** Rohith Gajawada (@RohithGajawada45), Mahathi Kanduri (@Mahathi04) +- **Author:** [your name / handle] - **Status:** Proposal (pre-submission draft) - **Type:** Standards Track - **PR:** TBD (assigned on submission per SEP-1850 process) @@ -11,13 +11,13 @@ ## 2. Abstract -MCP's `ToolAnnotations` currently describe whether a tool is read-only, destructive, idempotent, or operates in an open world — information aimed primarily at approval/UX decisions ("should a human be asked before this runs"). None of these annotations tell an orchestrator what to do *after* a tool has already succeeded and a later step in the same workflow fails. This proposal adds an optional `recovery` field to `ToolAnnotations` that lets a tool declare a compensating operation — a separate tool call, with parameters bound from the original call's response — that can undo its effect. The binding syntax follows the `Link` object from OpenAPI 3.0, adapted to MCP's tool-call model. The field is additive, advisory, and orthogonal to `idempotentHint`: idempotency governs safe retry of a single step that has not yet completed; `recovery` governs safe undoing of a step that has already completed, when a subsequent step in the same workflow fails. Where no compensating operation exists or is declared, the workflow's failure handling is unchanged from today. +MCP's `ToolAnnotations` currently describe whether a tool is read-only, destructive, idempotent, or operates in an open world — information aimed primarily at approval/UX decisions ("should a human be asked before this runs"). None of these annotations tell an orchestrator what to do _after_ a tool has already succeeded and a later step in the same workflow fails. This proposal adds an optional `recovery` field to `ToolAnnotations` that lets a tool declare a compensating operation — a separate tool call, with parameters bound from the original call's response — that can undo its effect. The binding syntax follows the `Link` object from OpenAPI 3.0, adapted to MCP's tool-call model. The field is additive, advisory, and orthogonal to `idempotentHint`: idempotency governs safe retry of a single step that has not yet completed; `recovery` governs safe undoing of a step that has already completed, when a subsequent step in the same workflow fails. Where no compensating operation exists or is declared, the workflow's failure handling is unchanged from today. ## 3. Motivation -MCP tool calls are increasingly composed into multi-step workflows by orchestrators (LangGraph, custom agent loops, etc.) where an early step causes a real, external side effect — a payment is charged, an inventory unit is reserved — before a later step fails. Today, `ToolAnnotations` gives an orchestrator no machine-readable signal about what to do next. It can inspect `destructiveHint` or `idempotentHint` on the *failed* tool, but neither field says anything about the *already-succeeded* tools earlier in the chain. In practice this means one of three things happens: the workflow aborts and leaves side effects in place for a human to clean up; the orchestrator hard-codes framework-specific cleanup logic that doesn't travel if the workflow is later run by a different orchestrator; or teams build a bespoke state/compensation layer on top of MCP, which is exactly the kind of gap several production teams (including a Shopify engineer describing a "MCP Lite" workaround, and a widely cited "Is MCP Outdated? A 2026 Reality Check" write-up) have already reported hitting. +MCP tool calls are increasingly composed into multi-step workflows by orchestrators (LangGraph, custom agent loops, etc.) where an early step causes a real, external side effect — a payment is charged, an inventory unit is reserved — before a later step fails. Today, `ToolAnnotations` gives an orchestrator no machine-readable signal about what to do next. It can inspect `destructiveHint` or `idempotentHint` on the _failed_ tool, but neither field says anything about the _already-succeeded_ tools earlier in the chain. In practice this means one of three things happens: the workflow aborts and leaves side effects in place for a human to clean up; the orchestrator hard-codes framework-specific cleanup logic that doesn't travel if the workflow is later run by a different orchestrator; or teams build a bespoke state/compensation layer on top of MCP, which is exactly the kind of gap several production teams (including a Shopify engineer describing a "MCP Lite" workaround, and a widely cited "Is MCP Outdated? A 2026 Reality Check" write-up) have already reported hitting. -The pattern this workflow needs — compensating actions that undo the effect of an already-committed step — is well established in distributed systems (the Saga pattern) and has already been implemented at the framework layer more than once for LLM agent workflows specifically (SagaLLM, ALAS, and LangGraph v1.2's built-in Saga support). What's missing is not the pattern itself, but a way to express it *in the tool's own wire-format descriptor*, so that any MCP-compliant orchestrator — not just the one framework that happened to implement Saga support — can discover and use it. Today, a tool that has a well-defined compensating action (e.g. `refund_payment` for `charge_payment`) has no way to advertise that fact to a client it doesn't know about in advance. +The pattern this workflow needs — compensating actions that undo the effect of an already-committed step — is well established in distributed systems (the Saga pattern) and has already been implemented at the framework layer more than once for LLM agent workflows specifically (SagaLLM, ALAS, and LangGraph v1.2's built-in Saga support). What's missing is not the pattern itself, but a way to express it _in the tool's own wire-format descriptor_, so that any MCP-compliant orchestrator — not just the one framework that happened to implement Saga support — can discover and use it. Today, a tool that has a well-defined compensating action (e.g. `refund_payment` for `charge_payment`) has no way to advertise that fact to a client it doesn't know about in advance. ## 4. Specification @@ -33,8 +33,8 @@ interface ToolAnnotations { recovery?: { strategy: "compensation"; compensatingOperation: { - operationId: string; // name of a tool exposed by the same server - parameters: Record; // runtime-expression bindings, see 4.2 + operationId: string; // name of a tool exposed by the same server + parameters: Record; // runtime-expression bindings, see 4.2 }; }; } @@ -46,7 +46,7 @@ This proposal deliberately scopes `compensatingOperation` to `operationId` — a ### 4.2 Parameter binding -Binding of the compensating operation's parameters follows OpenAPI 3.0's `Link` object runtime-expression syntax (`$response.body#/field`, `$response.header.`, etc.), applied to the *original* tool call's response. For example, a `charge_payment` tool that returns `{"transactionId": "TXN-123", "amount": 59.97}` can declare: +Binding of the compensating operation's parameters follows OpenAPI 3.0's `Link` object runtime-expression syntax (`$response.body#/field`, `$response.header.`, etc.), applied to the _original_ tool call's response. For example, a `charge_payment` tool that returns `{"transactionId": "TXN-123", "amount": 59.97}` can declare: ```yaml recovery: @@ -63,11 +63,12 @@ If a runtime expression cannot be resolved (e.g. the referenced field is absent ### 4.3 Orchestrator behavior (advisory, not mandated) -This SEP does not require any client or orchestrator to implement compensation. It defines the *metadata shape* only. A conforming orchestrator that supports recovery MAY use the declared recovery metadata to compensate previously completed operations on workflow failure. Reverse completion order is RECOMMENDED as a default strategy. Steps with no `recovery` field are left as-is, matching current behavior. +This SEP does not require any client or orchestrator to implement compensation. It defines the _metadata shape_ only. A conforming orchestrator that supports recovery MAY use the declared recovery metadata to compensate previously completed operations on workflow failure. Reverse completion order is RECOMMENDED as a default strategy. Steps with no `recovery` field are left as-is, matching current behavior. ### 4.4 Non-goals This proposal explicitly does **not**: + - Define distributed transactions or ACID guarantees across tool calls. - Require any orchestrator to implement compensation. - Standardize a workflow engine, execution graph, or ordering language. @@ -80,25 +81,25 @@ This proposal explicitly does **not**: A related proposal, SEP-1984 ("Comprehensive Tool Annotations"), included a `reversibleHint` boolean, and a reviewer objected that "reverting is usually harder to implement and error prone" and that idempotent retry is generally the more robust pattern. That objection is correct for the failure mode it addresses, but idempotency and compensation address different failure modes: -| Failure | Idempotency helps? | Compensation helps? | -|---|---|---| -| Network timeout before completion | Yes | Usually unnecessary | -| Duplicate invocation of the same step | Yes | No | -| A later, independent step fails after this step already committed a durable side effect | No | Yes | +| Failure | Idempotency helps? | Compensation helps? | +| --------------------------------------------------------------------------------------- | ------------------ | ------------------- | +| Network timeout before completion | Yes | Usually unnecessary | +| Duplicate invocation of the same step | Yes | No | +| A later, independent step fails after this step already committed a durable side effect | No | Yes | -When a workflow step fails *after* an earlier step has already committed a durable side effect, retrying the earlier operation does not undo it — retrying `charge_payment` does not un-charge a card that was already charged; it only guards against charging it twice. A compensating operation is required to restore the previous state. The two mechanisms are complementary, not competing, and this proposal is scoped narrowly to the second case, which today has no representation in `ToolAnnotations` at all. +When a workflow step fails _after_ an earlier step has already committed a durable side effect, retrying the earlier operation does not undo it — retrying `charge_payment` does not un-charge a card that was already charged; it only guards against charging it twice. A compensating operation is required to restore the previous state. The two mechanisms are complementary, not competing, and this proposal is scoped narrowly to the second case, which today has no representation in `ToolAnnotations` at all. ### 5.2 Related work - **SEP-1984** proposed a boolean `reversibleHint` with no binding mechanism; this proposal instead reuses OpenAPI's `Link` runtime-expression convention to make the compensating call directly invocable, and deliberately avoids the term "reversible" since not every operation with a recovery strategy is fully undoable (see 4.4). -- **SEP-2487** ("Add `execution.requirements` field to Tool for preconditions") addresses a different problem — declaring what must be true *before* a tool runs (auth, approval, ordering) — and is complementary rather than overlapping with this proposal, which addresses what to do *after* a tool has already run and a later step fails. +- **SEP-2487** ("Add `execution.requirements` field to Tool for preconditions") addresses a different problem — declaring what must be true _before_ a tool runs (auth, approval, ordering) — and is complementary rather than overlapping with this proposal, which addresses what to do _after_ a tool has already run and a later step fails. - **SagaLLM**, **ALAS**, and **Atomix** demonstrate the Saga/compensation pattern for LLM agent workflows at the framework or research-prototype level. **LangGraph v1.2** ships built-in Saga support inside its own state graph. None of these expose recovery relationships through a protocol-level tool description that independent orchestrators can consume without framework-specific integration. This proposal's contribution is narrowly that interoperability, not the underlying pattern, which is well established elsewhere. ### 5.3 Why recovery metadata belongs on the tool, not the workflow An alternative design would declare compensation relationships in the workflow definition (as LangGraph's Saga support does) rather than on the tool descriptor itself. This proposal deliberately places `recovery` on the tool because it describes a property of the operation, not of any particular workflow. A payment capture is compensated by a refund regardless of whether it appears in a checkout workflow, an order-modification workflow, or a subscription-renewal workflow. Declaring the relationship once, on the tool, avoids duplicating the same recovery knowledge across every workflow definition and every orchestrator that happens to call the tool — which is the same reasoning that already justifies putting `idempotentHint` and `destructiveHint` on the tool rather than requiring each caller to know and redeclare them. -A related but distinct objection is that `recovery`, unlike existing annotation fields, references *another* operation rather than describing the current one in isolation, and so arguably doesn't belong in `ToolAnnotations` at all. Although `recovery` references another operation, it still describes a behavioral property of the current operation: namely, how its externally visible effects may be compensated. This is analogous to `idempotentHint`, which likewise describes execution semantics rather than presentation or approval metadata — the fact that expressing "how to compensate this" requires naming a second operation doesn't change that the claim being made is about the *first* operation's behavior. +A related but distinct objection is that `recovery`, unlike existing annotation fields, references _another_ operation rather than describing the current one in isolation, and so arguably doesn't belong in `ToolAnnotations` at all. Although `recovery` references another operation, it still describes a behavioral property of the current operation: namely, how its externally visible effects may be compensated. This is analogous to `idempotentHint`, which likewise describes execution semantics rather than presentation or approval metadata — the fact that expressing "how to compensate this" requires naming a second operation doesn't change that the claim being made is about the _first_ operation's behavior. ### 5.4 Open questions @@ -111,7 +112,7 @@ Fully backward compatible. `recovery` is an optional field on `ToolAnnotations`; ## 7. Reference Implementation -A standalone prototype (~230 lines) implements a three-step workflow (`reserve_inventory` → `charge_payment` → `create_shipment`) with a simulated failure in the final step. It runs the workflow twice: once against a baseline orchestrator that reads only today's `ToolAnnotations` (workflow aborts, side effects left in place), and once against an orchestrator that also reads the proposed `recovery` field (workflow automatically compensates in reverse order and reaches a consistent terminal state). The orchestrator code contains no hard-coded references to `refund_payment` or `release_inventory` — those names appear only in the tool metadata itself, demonstrating that the recovery behavior is driven entirely by declared metadata rather than framework-specific glue. The reference implementation demonstrates that identical workflow logic can execute under both orchestrators, with the enhanced orchestrator deriving recovery behavior exclusively from declared metadata and without embedding tool-specific recovery logic. [https://github.com/RohithGajawada45/mcp-recovery-metadata-prototype] +A standalone prototype (~230 lines) implements a three-step workflow (`reserve_inventory` → `charge_payment` → `create_shipment`) with a simulated failure in the final step. It runs the workflow twice: once against a baseline orchestrator that reads only today's `ToolAnnotations` (workflow aborts, side effects left in place), and once against an orchestrator that also reads the proposed `recovery` field (workflow automatically compensates in reverse order and reaches a consistent terminal state). The orchestrator code contains no hard-coded references to `refund_payment` or `release_inventory` — those names appear only in the tool metadata itself, demonstrating that the recovery behavior is driven entirely by declared metadata rather than framework-specific glue. The reference implementation demonstrates that identical workflow logic can execute under both orchestrators, with the enhanced orchestrator deriving recovery behavior exclusively from declared metadata and without embedding tool-specific recovery logic. [Link to prototype repo/gist — to be added on submission.] ## 8. Security Implications From b1a90565c9820f23df4f27c1558462fef3ccb102 Mon Sep 17 00:00:00 2001 From: Rohith Gajawada <141448174+RohithGajawada45@users.noreply.github.com> Date: Thu, 30 Jul 2026 01:14:02 +0530 Subject: [PATCH 4/6] Fix JSON formatting in docs.json --- docs/docs.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/docs.json b/docs/docs.json index f5c6a8f17..73a5451d9 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -821,6 +821,12 @@ "seps/2596-spec-feature-lifecycle-and-deprecation", "seps/2663-tasks-extension" ] + }, + { + "group": "Unknown", + "pages": [ + "seps/3172-tool-recovery-metadata" + ] } ] }, From b81fc46a765be6fc851bbebe4e8114ed2fe5567e Mon Sep 17 00:00:00 2001 From: Rohith Gajawada <141448174+RohithGajawada45@users.noreply.github.com> Date: Thu, 30 Jul 2026 01:14:57 +0530 Subject: [PATCH 5/6] Update SEP index and add new entries Updated the SEP index to include a new SEP and adjusted the table formatting. --- docs/seps/index.mdx | 88 +++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/docs/seps/index.mdx b/docs/seps/index.mdx index 658464479..e1d3b3928 100644 --- a/docs/seps/index.mdx +++ b/docs/seps/index.mdx @@ -12,53 +12,55 @@ Specification Enhancement Proposals (SEPs) are the primary mechanism for proposi ## Summary +- **Unknown**: 1 - **Final**: 41 ## All SEPs -| SEP | Title | Status | Type | Created | -| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ----------------------------------------------- | ---------------- | ---------- | -| [SEP-2663](/seps/2663-tasks-extension) | Tasks Extension | Final | Extensions Track | 2026-04-27 | -| [SEP-2596](/seps/2596-spec-feature-lifecycle-and-deprecation) | Specification Feature Lifecycle and Deprecation Policy | Final | Process | 2026-04-17 | -| [SEP-2577](/seps/2577-deprecate-roots-sampling-and-logging) | Deprecate Roots, Sampling, and Logging | Final | Standards Track | 2026-04-14 | -| [SEP-2575](/seps/2575-stateless-mcp) | Make MCP Stateless | Final | Standards Track | 2025-06-18 | -| [SEP-2567](/seps/2567-sessionless-mcp) | Sessionless MCP via Explicit State Handles | Final | Standards Track | 2026-03-11 | -| [SEP-2549](/seps/2549-TTL-for-list-results) | TTL for List Results | Final | Standards Track | 2026-04-09 | -| [SEP-2484](/seps/2484-conformance-tests-required-for-final-seps) | Require Conformance Tests for Standards Track SEPs to Reach Final Status | Final | Process | 2026-03-27 | -| [SEP-2468](/seps/2468-recommend-issuer-claim-for-auth) | Recommend Issuer (iss) Parameter in MCP Auth Responses | Final | Standards Track | 2026-03-25 | -| [SEP-2322](/seps/2322-MRTR) | Multi Round-Trip Requests | Final | Standards Track | 2026-02-03 | -| [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Final | Standards Track | 2026-02-16 | -| [SEP-2243](/seps/2243-http-standardization) | HTTP Header Standardization for Streamable HTTP Transport | Final | Standards Track | 2026-02-04 | -| [SEP-2207](/seps/2207-oidc-refresh-token-guidance) | OIDC-Flavored Refresh Token Guidance | Final | Standards Track | 2026-02-04 | -| [SEP-2164](/seps/2164-resource-not-found-error) | Standardize Resource Not Found Error Code | Final | Standards Track | 2026-01-28 | -| [SEP-2149](/seps/2149-working-group-charter-template) | MCP Group Governance and Charter Template | Final | Process | 2025-01-15 | -| [SEP-2148](/seps/2148-contributor-ladder) | MCP Contributor Ladder | Final | Process | 2026-01-15 | -| [SEP-2133](/seps/2133-extensions) | Extensions | Final | Standards Track | 2025-01-21 | -| [SEP-2106](/seps/2106-json-schema-2020-12) | Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12 | Final | Standards Track | 2026-01-06 | -| [SEP-2085](/seps/2085-governance-succession-and-amendment) | Governance Succession and Amendment Procedures | Final | Process | 2025-12-05 | -| [SEP-1865](/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp) | MCP Apps - Interactive User Interfaces for MCP | Final | Extensions Track | 2025-11-21 | -| [SEP-1850](/seps/1850-pr-based-sep-workflow) | PR-Based SEP Workflow | Final | Process | 2025-11-20 | -| [SEP-1730](/seps/1730-sdks-tiering-system) | SDKs Tiering System | Final | Standards Track | 2025-10-29 | -| [SEP-1699](/seps/1699-support-sse-polling-via-server-side-disconnect) | Support SSE polling via server-side disconnect | Final | Standards Track | 2025-10-22 | -| [SEP-1686](/seps/1686-tasks) | Tasks | Final | Standards Track | 2025-10-20 | -| [SEP-1613](/seps/1613-establish-json-schema-2020-12-as-default-dialect-f) | Establish JSON Schema 2020-12 as Default Dialect for MCP | Final | Standards Track | 2025-10-06 | -| [SEP-1577](/seps/1577--sampling-with-tools) | Sampling With Tools | Final | Standards Track | 2025-09-30 | -| [SEP-1330](/seps/1330-elicitation-enum-schema-improvements-and-standards) | Elicitation Enum Schema Improvements and Standards Compliance | Final | Standards Track | 2025-08-11 | -| [SEP-1319](/seps/1319-decouple-request-payload-from-rpc-methods-definiti) | Decouple Request Payload from RPC Methods Definition | Final | Standards Track | 2025-08-08 | -| [SEP-1303](/seps/1303-input-validation-errors-as-tool-execution-errors) | Input Validation Errors as Tool Execution Errors | Final | Standards Track | 2025-08-05 | -| [SEP-1302](/seps/1302-formalize-working-groups-and-interest-groups-in-mc) | Formalize Working Groups and Interest Groups in MCP Governance | Final | Standards Track | 2025-08-05 | -| [SEP-1046](/seps/1046-support-oauth-client-credentials-flow-in-authoriza) | Support OAuth client credentials flow in authorization | Final | Standards Track | 2025-07-23 | -| [SEP-1036](/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera) | URL Mode Elicitation for secure out-of-band interactions | Final | Standards Track | 2025-07-22 | -| [SEP-1034](/seps/1034--support-default-values-for-all-primitive-types-in) | Support default values for all primitive types in elicitation schemas | Final | Standards Track | 2025-07-22 | -| [SEP-1024](/seps/1024-mcp-client-security-requirements-for-local-server-) | MCP Client Security Requirements for Local Server Installation | Final | Standards Track | 2025-07-22 | -| [SEP-994](/seps/994-shared-communication-practicesguidelines) | Shared Communication Practices/Guidelines | Final | Process | 2025-07-17 | -| [SEP-991](/seps/991-enable-url-based-client-registration-using-oauth-c) | Enable URL-based Client Registration using OAuth Client ID Metadata Documents | Final | Standards Track | 2025-07-07 | -| [SEP-990](/seps/990-enable-enterprise-idp-policy-controls-during-mcp-o) | Enable enterprise IdP policy controls during MCP OAuth flows | Final | Standards Track | 2025-06-04 | -| [SEP-986](/seps/986-specify-format-for-tool-names) | Specify Format for Tool Names | Final | Standards Track | 2025-07-16 | -| [SEP-985](/seps/985-align-oauth-20-protected-resource-metadata-with-rf) | Align OAuth 2.0 Protected Resource Metadata with RFC 9728 | Final | Standards Track | 2025-07-16 | -| [SEP-973](/seps/973-expose-additional-metadata-for-implementations-res) | Expose additional metadata for Implementations, Resources, Tools and Prompts | Final | Standards Track | 2025-07-15 | -| [SEP-932](/seps/932-model-context-protocol-governance) | Model Context Protocol Governance | Final | Process | 2025-07-08 | -| [SEP-414](/seps/414-request-meta) | Document OpenTelemetry Trace Context Propagation Conventions | Final | Standards Track | 2025-04-25 | +| SEP | Title | Status | Type | Created | +| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------ | ---------------- | ---------- | +| [SEP-3172](/seps/3172-tool-recovery-metadata) | Untitled | Unknown | Unknown | Unknown | +| [SEP-2663](/seps/2663-tasks-extension) | Tasks Extension | Final | Extensions Track | 2026-04-27 | +| [SEP-2596](/seps/2596-spec-feature-lifecycle-and-deprecation) | Specification Feature Lifecycle and Deprecation Policy | Final | Process | 2026-04-17 | +| [SEP-2577](/seps/2577-deprecate-roots-sampling-and-logging) | Deprecate Roots, Sampling, and Logging | Final | Standards Track | 2026-04-14 | +| [SEP-2575](/seps/2575-stateless-mcp) | Make MCP Stateless | Final | Standards Track | 2025-06-18 | +| [SEP-2567](/seps/2567-sessionless-mcp) | Sessionless MCP via Explicit State Handles | Final | Standards Track | 2026-03-11 | +| [SEP-2549](/seps/2549-TTL-for-list-results) | TTL for List Results | Final | Standards Track | 2026-04-09 | +| [SEP-2484](/seps/2484-conformance-tests-required-for-final-seps) | Require Conformance Tests for Standards Track SEPs to Reach Final Status | Final | Process | 2026-03-27 | +| [SEP-2468](/seps/2468-recommend-issuer-claim-for-auth) | Recommend Issuer (iss) Parameter in MCP Auth Responses | Final | Standards Track | 2026-03-25 | +| [SEP-2322](/seps/2322-MRTR) | Multi Round-Trip Requests | Final | Standards Track | 2026-02-03 | +| [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Final | Standards Track | 2026-02-16 | +| [SEP-2243](/seps/2243-http-standardization) | HTTP Header Standardization for Streamable HTTP Transport | Final | Standards Track | 2026-02-04 | +| [SEP-2207](/seps/2207-oidc-refresh-token-guidance) | OIDC-Flavored Refresh Token Guidance | Final | Standards Track | 2026-02-04 | +| [SEP-2164](/seps/2164-resource-not-found-error) | Standardize Resource Not Found Error Code | Final | Standards Track | 2026-01-28 | +| [SEP-2149](/seps/2149-working-group-charter-template) | MCP Group Governance and Charter Template | Final | Process | 2025-01-15 | +| [SEP-2148](/seps/2148-contributor-ladder) | MCP Contributor Ladder | Final | Process | 2026-01-15 | +| [SEP-2133](/seps/2133-extensions) | Extensions | Final | Standards Track | 2025-01-21 | +| [SEP-2106](/seps/2106-json-schema-2020-12) | Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12 | Final | Standards Track | 2026-01-06 | +| [SEP-2085](/seps/2085-governance-succession-and-amendment) | Governance Succession and Amendment Procedures | Final | Process | 2025-12-05 | +| [SEP-1865](/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp) | MCP Apps - Interactive User Interfaces for MCP | Final | Extensions Track | 2025-11-21 | +| [SEP-1850](/seps/1850-pr-based-sep-workflow) | PR-Based SEP Workflow | Final | Process | 2025-11-20 | +| [SEP-1730](/seps/1730-sdks-tiering-system) | SDKs Tiering System | Final | Standards Track | 2025-10-29 | +| [SEP-1699](/seps/1699-support-sse-polling-via-server-side-disconnect) | Support SSE polling via server-side disconnect | Final | Standards Track | 2025-10-22 | +| [SEP-1686](/seps/1686-tasks) | Tasks | Final | Standards Track | 2025-10-20 | +| [SEP-1613](/seps/1613-establish-json-schema-2020-12-as-default-dialect-f) | Establish JSON Schema 2020-12 as Default Dialect for MCP | Final | Standards Track | 2025-10-06 | +| [SEP-1577](/seps/1577--sampling-with-tools) | Sampling With Tools | Final | Standards Track | 2025-09-30 | +| [SEP-1330](/seps/1330-elicitation-enum-schema-improvements-and-standards) | Elicitation Enum Schema Improvements and Standards Compliance | Final | Standards Track | 2025-08-11 | +| [SEP-1319](/seps/1319-decouple-request-payload-from-rpc-methods-definiti) | Decouple Request Payload from RPC Methods Definition | Final | Standards Track | 2025-08-08 | +| [SEP-1303](/seps/1303-input-validation-errors-as-tool-execution-errors) | Input Validation Errors as Tool Execution Errors | Final | Standards Track | 2025-08-05 | +| [SEP-1302](/seps/1302-formalize-working-groups-and-interest-groups-in-mc) | Formalize Working Groups and Interest Groups in MCP Governance | Final | Standards Track | 2025-08-05 | +| [SEP-1046](/seps/1046-support-oauth-client-credentials-flow-in-authoriza) | Support OAuth client credentials flow in authorization | Final | Standards Track | 2025-07-23 | +| [SEP-1036](/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera) | URL Mode Elicitation for secure out-of-band interactions | Final | Standards Track | 2025-07-22 | +| [SEP-1034](/seps/1034--support-default-values-for-all-primitive-types-in) | Support default values for all primitive types in elicitation schemas | Final | Standards Track | 2025-07-22 | +| [SEP-1024](/seps/1024-mcp-client-security-requirements-for-local-server-) | MCP Client Security Requirements for Local Server Installation | Final | Standards Track | 2025-07-22 | +| [SEP-994](/seps/994-shared-communication-practicesguidelines) | Shared Communication Practices/Guidelines | Final | Process | 2025-07-17 | +| [SEP-991](/seps/991-enable-url-based-client-registration-using-oauth-c) | Enable URL-based Client Registration using OAuth Client ID Metadata Documents | Final | Standards Track | 2025-07-07 | +| [SEP-990](/seps/990-enable-enterprise-idp-policy-controls-during-mcp-o) | Enable enterprise IdP policy controls during MCP OAuth flows | Final | Standards Track | 2025-06-04 | +| [SEP-986](/seps/986-specify-format-for-tool-names) | Specify Format for Tool Names | Final | Standards Track | 2025-07-16 | +| [SEP-985](/seps/985-align-oauth-20-protected-resource-metadata-with-rf) | Align OAuth 2.0 Protected Resource Metadata with RFC 9728 | Final | Standards Track | 2025-07-16 | +| [SEP-973](/seps/973-expose-additional-metadata-for-implementations-res) | Expose additional metadata for Implementations, Resources, Tools and Prompts | Final | Standards Track | 2025-07-15 | +| [SEP-932](/seps/932-model-context-protocol-governance) | Model Context Protocol Governance | Final | Process | 2025-07-08 | +| [SEP-414](/seps/414-request-meta) | Document OpenTelemetry Trace Context Propagation Conventions | Final | Standards Track | 2025-04-25 | ## SEP Status Definitions From c66570f6c82ea87a6cf4eca16ced0876f828118b Mon Sep 17 00:00:00 2001 From: Rohith Gajawada <141448174+RohithGajawada45@users.noreply.github.com> Date: Thu, 30 Jul 2026 01:15:46 +0530 Subject: [PATCH 6/6] Create SEP-3172 for ToolAnnotations recovery metadata Add SEP-3172 proposal for recovery metadata in ToolAnnotations. --- docs/seps/3172-tool-recovery-metadata.mdx | 149 ++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 docs/seps/3172-tool-recovery-metadata.mdx diff --git a/docs/seps/3172-tool-recovery-metadata.mdx b/docs/seps/3172-tool-recovery-metadata.mdx new file mode 100644 index 000000000..f456d0a76 --- /dev/null +++ b/docs/seps/3172-tool-recovery-metadata.mdx @@ -0,0 +1,149 @@ +--- +title: "SEP-3172: Untitled" +sidebarTitle: "SEP-3172: Untitled" +description: "Untitled" +--- + +
+ + Unknown + + + Unknown + +
+ +| Field | Value | +| ------------- | ------------------------------------------------------------------------------- | +| **SEP** | 3172 | +| **Title** | Untitled | +| **Status** | Unknown | +| **Type** | Unknown | +| **Created** | Unknown | +| **Author(s)** | Unknown | +| **Sponsor** | None | +| **PR** | [#3172](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/3172) | + +--- + +# Recovery Metadata for MCP ToolAnnotations + +## 1. Preamble + +- **Title:** Recovery Metadata for MCP ToolAnnotations +- **Author:** [your name / handle] +- **Status:** Proposal (pre-submission draft) +- **Type:** Standards Track +- **PR:** TBD (assigned on submission per SEP-1850 process) +- **Created:** 2026-07-30 + +## 2. Abstract + +MCP's `ToolAnnotations` currently describe whether a tool is read-only, destructive, idempotent, or operates in an open world — information aimed primarily at approval/UX decisions ("should a human be asked before this runs"). None of these annotations tell an orchestrator what to do _after_ a tool has already succeeded and a later step in the same workflow fails. This proposal adds an optional `recovery` field to `ToolAnnotations` that lets a tool declare a compensating operation — a separate tool call, with parameters bound from the original call's response — that can undo its effect. The binding syntax follows the `Link` object from OpenAPI 3.0, adapted to MCP's tool-call model. The field is additive, advisory, and orthogonal to `idempotentHint`: idempotency governs safe retry of a single step that has not yet completed; `recovery` governs safe undoing of a step that has already completed, when a subsequent step in the same workflow fails. Where no compensating operation exists or is declared, the workflow's failure handling is unchanged from today. + +## 3. Motivation + +MCP tool calls are increasingly composed into multi-step workflows by orchestrators (LangGraph, custom agent loops, etc.) where an early step causes a real, external side effect — a payment is charged, an inventory unit is reserved — before a later step fails. Today, `ToolAnnotations` gives an orchestrator no machine-readable signal about what to do next. It can inspect `destructiveHint` or `idempotentHint` on the _failed_ tool, but neither field says anything about the _already-succeeded_ tools earlier in the chain. In practice this means one of three things happens: the workflow aborts and leaves side effects in place for a human to clean up; the orchestrator hard-codes framework-specific cleanup logic that doesn't travel if the workflow is later run by a different orchestrator; or teams build a bespoke state/compensation layer on top of MCP, which is exactly the kind of gap several production teams (including a Shopify engineer describing a "MCP Lite" workaround, and a widely cited "Is MCP Outdated? A 2026 Reality Check" write-up) have already reported hitting. + +The pattern this workflow needs — compensating actions that undo the effect of an already-committed step — is well established in distributed systems (the Saga pattern) and has already been implemented at the framework layer more than once for LLM agent workflows specifically (SagaLLM, ALAS, and LangGraph v1.2's built-in Saga support). What's missing is not the pattern itself, but a way to express it _in the tool's own wire-format descriptor_, so that any MCP-compliant orchestrator — not just the one framework that happened to implement Saga support — can discover and use it. Today, a tool that has a well-defined compensating action (e.g. `refund_payment` for `charge_payment`) has no way to advertise that fact to a client it doesn't know about in advance. + +## 4. Specification + +### 4.1 New field: `recovery` + +An optional field is added to the `ToolAnnotations` interface: + +```typescript +interface ToolAnnotations { + // ... existing fields (title, readOnlyHint, destructiveHint, + // idempotentHint, openWorldHint) unchanged ... + + recovery?: { + strategy: "compensation"; + compensatingOperation: { + operationId: string; // name of a tool exposed by the same server + parameters: Record; // runtime-expression bindings, see 4.2 + }; + }; +} +``` + +`strategy` is a string enum to allow future extension (e.g. `"retry-elsewhere"`, `"manual-only"`) without a breaking change; this proposal defines only `"compensation"`. A tool with no `recovery` field makes no claim about recoverability, identical to today's behavior — this is a purely additive change. + +This proposal deliberately scopes `compensatingOperation` to `operationId` — a tool exposed by the same server as the original call. A cross-server reference (analogous to OpenAPI's `operationRef`) would raise separate questions of authentication, remote discovery, trust, and failure domains that are unnecessary for the motivating use case and are left to a future SEP if same-server compensation proves useful in practice. + +### 4.2 Parameter binding + +Binding of the compensating operation's parameters follows OpenAPI 3.0's `Link` object runtime-expression syntax (`$response.body#/field`, `$response.header.`, etc.), applied to the _original_ tool call's response. For example, a `charge_payment` tool that returns `{"transactionId": "TXN-123", "amount": 59.97}` can declare: + +```yaml +recovery: + strategy: compensation + compensatingOperation: + operationId: refund_payment + parameters: + transactionId: $response.body#/transactionId +``` + +An orchestrator that needs to compensate this call resolves `$response.body#/transactionId` against the stored response of the original `charge_payment` invocation, then invokes `refund_payment` with the resolved value. The syntax intentionally mirrors OpenAPI 3.0 runtime expressions so implementations may reuse existing parsers where convenient; no new expression language is introduced. + +If a runtime expression cannot be resolved (e.g. the referenced field is absent from the stored response), the orchestrator MUST treat recovery metadata as unavailable for that operation and fall back to today's behavior for that step, rather than invoking the compensating operation with partial or default arguments. + +### 4.3 Orchestrator behavior (advisory, not mandated) + +This SEP does not require any client or orchestrator to implement compensation. It defines the _metadata shape_ only. A conforming orchestrator that supports recovery MAY use the declared recovery metadata to compensate previously completed operations on workflow failure. Reverse completion order is RECOMMENDED as a default strategy. Steps with no `recovery` field are left as-is, matching current behavior. + +### 4.4 Non-goals + +This proposal explicitly does **not**: + +- Define distributed transactions or ACID guarantees across tool calls. +- Require any orchestrator to implement compensation. +- Standardize a workflow engine, execution graph, or ordering language. +- Define retry policies, backoff, or checkpoint/resume semantics — these are separable concerns, out of scope here, and left to future proposals. +- Guarantee that compensation itself succeeds — see Rationale, "Open questions." + +## 5. Rationale + +### 5.1 Why not just use `idempotentHint`? + +A related proposal, SEP-1984 ("Comprehensive Tool Annotations"), included a `reversibleHint` boolean, and a reviewer objected that "reverting is usually harder to implement and error prone" and that idempotent retry is generally the more robust pattern. That objection is correct for the failure mode it addresses, but idempotency and compensation address different failure modes: + +| Failure | Idempotency helps? | Compensation helps? | +| --------------------------------------------------------------------------------------- | ------------------ | ------------------- | +| Network timeout before completion | Yes | Usually unnecessary | +| Duplicate invocation of the same step | Yes | No | +| A later, independent step fails after this step already committed a durable side effect | No | Yes | + +When a workflow step fails _after_ an earlier step has already committed a durable side effect, retrying the earlier operation does not undo it — retrying `charge_payment` does not un-charge a card that was already charged; it only guards against charging it twice. A compensating operation is required to restore the previous state. The two mechanisms are complementary, not competing, and this proposal is scoped narrowly to the second case, which today has no representation in `ToolAnnotations` at all. + +### 5.2 Related work + +- **SEP-1984** proposed a boolean `reversibleHint` with no binding mechanism; this proposal instead reuses OpenAPI's `Link` runtime-expression convention to make the compensating call directly invocable, and deliberately avoids the term "reversible" since not every operation with a recovery strategy is fully undoable (see 4.4). +- **SEP-2487** ("Add `execution.requirements` field to Tool for preconditions") addresses a different problem — declaring what must be true _before_ a tool runs (auth, approval, ordering) — and is complementary rather than overlapping with this proposal, which addresses what to do _after_ a tool has already run and a later step fails. +- **SagaLLM**, **ALAS**, and **Atomix** demonstrate the Saga/compensation pattern for LLM agent workflows at the framework or research-prototype level. **LangGraph v1.2** ships built-in Saga support inside its own state graph. None of these expose recovery relationships through a protocol-level tool description that independent orchestrators can consume without framework-specific integration. This proposal's contribution is narrowly that interoperability, not the underlying pattern, which is well established elsewhere. + +### 5.3 Why recovery metadata belongs on the tool, not the workflow + +An alternative design would declare compensation relationships in the workflow definition (as LangGraph's Saga support does) rather than on the tool descriptor itself. This proposal deliberately places `recovery` on the tool because it describes a property of the operation, not of any particular workflow. A payment capture is compensated by a refund regardless of whether it appears in a checkout workflow, an order-modification workflow, or a subscription-renewal workflow. Declaring the relationship once, on the tool, avoids duplicating the same recovery knowledge across every workflow definition and every orchestrator that happens to call the tool — which is the same reasoning that already justifies putting `idempotentHint` and `destructiveHint` on the tool rather than requiring each caller to know and redeclare them. + +A related but distinct objection is that `recovery`, unlike existing annotation fields, references _another_ operation rather than describing the current one in isolation, and so arguably doesn't belong in `ToolAnnotations` at all. Although `recovery` references another operation, it still describes a behavioral property of the current operation: namely, how its externally visible effects may be compensated. This is analogous to `idempotentHint`, which likewise describes execution semantics rather than presentation or approval metadata — the fact that expressing "how to compensate this" requires naming a second operation doesn't change that the claim being made is about the _first_ operation's behavior. + +### 5.4 Open questions + +- **Compensation ordering:** the reference implementation compensates in reverse (LIFO) order of completion. This SEP does not mandate that orchestrators use LIFO — only recommends it as a sensible default — since some workflows may have compensations that are safe to run in any order or in parallel. +- **Failure of compensation itself:** if a compensating operation fails, this SEP does not define a terminal protocol-level state for that case. It is left to the orchestrator (e.g., surface to a human, retry the compensation if it is itself idempotent) pending real-world experience with this field before standardizing further. + +## 6. Backward Compatibility + +Fully backward compatible. `recovery` is an optional field on `ToolAnnotations`; existing servers, clients, and tools that do not set it are unaffected, and existing clients that don't recognize the field will simply ignore it, per MCP's existing annotation-handling guidance. + +## 7. Reference Implementation + +A standalone prototype (~230 lines) implements a three-step workflow (`reserve_inventory` → `charge_payment` → `create_shipment`) with a simulated failure in the final step. It runs the workflow twice: once against a baseline orchestrator that reads only today's `ToolAnnotations` (workflow aborts, side effects left in place), and once against an orchestrator that also reads the proposed `recovery` field (workflow automatically compensates in reverse order and reaches a consistent terminal state). The orchestrator code contains no hard-coded references to `refund_payment` or `release_inventory` — those names appear only in the tool metadata itself, demonstrating that the recovery behavior is driven entirely by declared metadata rather than framework-specific glue. The reference implementation demonstrates that identical workflow logic can execute under both orchestrators, with the enhanced orchestrator deriving recovery behavior exclusively from declared metadata and without embedding tool-specific recovery logic. [Link to prototype repo/gist — to be added on submission.] + +## 8. Security Implications + +`recovery.compensatingOperation` causes a tool call to be invoked automatically, without a fresh human-in-the-loop approval, when a workflow fails — by an orchestrator that chooses to support this field. Implementers should treat a declared compensating operation with at least the same scrutiny as the original tool call: a malicious or compromised server could declare a `compensatingOperation` that does something other than what its name implies. As with existing `ToolAnnotations`, clients MUST NOT treat `recovery` metadata as fully trusted attestation from a potentially untrusted server, and orchestrators that auto-invoke compensating operations should consider surfacing them to the same approval/audit path as the original destructive or state-changing call. + +Automatic compensation can itself trigger a destructive operation (a refund, a deletion, a release of a held resource) without a fresh human-in-the-loop approval at the moment it runs. An orchestrator MAY require the same approval policy for an automatically-invoked `compensatingOperation` that it would have required had that operation been invoked directly by the model — this is a natural extension of the existing approval discussion above, not a separate mechanism.