Timeout after a committed write
A successful write times out and the retry duplicates it
Repair pattern: Stable idempotency key per logical operation; reconcile by lookup; never a second write after an unknown outcome
Curbi / MCP Chaos · Local fault workbench
A local fault-injection workbench for MCP tool calls. The included refund fixtures exercise six failure scenarios; extend the server adapter and oracles for your integration.
Each cell is a fresh fixture payments server. Refund counts are the server’s ground truth; the oracles agreed with them on every cell.
| Failure | Typical first version | New key per retry | Repaired pattern |
|---|---|---|---|
| Timeout after a committed write A successful write times out and the retry duplicates it | FAIL 2 refunds · duplicate_write | FAIL 2 refunds · duplicate_write | PASS 1 refund |
| Request lost before the server The call never arrived; the client cannot tell this from case 01 | PASS 1 refund | PASS 1 refund | PASS 0 refunds · held |
| Authentication expired mid-workflow Token expires between steps; blind retries or silent success follow | PASS 1 refund | PASS 1 refund | PASS 0 refunds |
| Provider changed its response shape The tool's declared output and actual response disagree | FAIL 1 refund · false_success, unhandled_schema_drift | FAIL 1 refund · false_success, unhandled_schema_drift | PASS 1 refund |
| Silent failure: success with no data The API returns a success code with an empty payload | FAIL 1 refund · false_success | FAIL 1 refund · false_success | PASS 1 refund |
| The whole operation is retried (crash, double-tap, queue redelivery) The integration runs twice for one logical operation; a per-invocation key cannot de-duplicate it | FAIL 3 refunds · duplicate_write | FAIL 2 refunds · duplicate_write | PASS 1 refund |
“Held” means the repair kept the outcome pending instead of guessing; retrying with the same key is a per-provider policy decision.
A successful write times out and the retry duplicates it
Repair pattern: Stable idempotency key per logical operation; reconcile by lookup; never a second write after an unknown outcome
The call never arrived; the client cannot tell this from case 01
Repair pattern: Same as 01: from the client this is indistinguishable, so it must reconcile, not guess
Token expires between steps; blind retries or silent success follow
Repair pattern: Recognise the auth failure, refresh credentials out of band, retry with the SAME key
The tool's declared output and actual response disagree
Repair pattern: Validate against the declared output schema; on mismatch treat the outcome as unknown and reconcile
The API returns a success code with an empty payload
Repair pattern: Never accept success without the declared evidence fields; reconcile
The integration runs twice for one logical operation; a per-invocation key cannot de-duplicate it
Repair pattern: Derive the idempotency key from the logical operation id (stable across invocations and restarts), never from a per-call random or attempt counter
cd curbi-mcp-chaos-v1 npm ci --ignore-scripts node src/matrix.mjs # the matrix above npm test # 10 tests, including oracle-vs-ground-truth
A pass means no configured rule fired. It grants no execution authority or provider-completion proof.