---
schema_version: 1
slug: durable-mcp-state
status: source-reviewed
walkthrough_verified: false
content_hash: 2fedb4cc43a473e9df07bb7a0f957577e590b1e4151e325352980fa11cb624ee
---

# Add credential-partitioned durable state to a Supabase MCP

Use Chumbo's bounded credential-partitioned state for read-before-write receipts while application RLS and resource versions remain authoritative.

Use Chumbo's bounded credential-partitioned state for read-before-write receipts while application RLS and resource versions remain authoritative.

Reviewed against the shipped OSS source. The acceptance checks are yours to run in your application.

Source reviewed against published Chumbo 0.11.0 and its matching core source on 2026-09-06 (UTC). This is a source review, not an executed application walkthrough. Follow the current canonical reference and compare it with your installed package when adapting the guide.

[Structured recipe](./recipe.json) · [Human-readable page](./)

## Sources

- [Canonical pattern](https://github.com/elsheppo/chumbo/blob/main/docs/patterns/observation-before-action/README.md)
- [Reference implementation](https://github.com/elsheppo/chumbo/blob/main/supabase/functions/observation-before-action/index.ts)

## 01. Use state only when a later request needs an earlier fact.

### Before you start

- Write the rule the state enforces, such as: this credential may edit a document only after reading its current version.
- Identify the application's atomic resource-version check. State revision alone cannot protect the document row.
- Choose one allowlisted namespace and a bounded key built from immutable, authorized resource IDs.

Most Chumbo capabilities should remain stateless. Durable state is an explicit authenticated opt-in for small coordination records.

A read-before-write tool needs one fact across requests: which resource version this credential observed. Chumbo stores that receipt behind `ctx.state`. Capability code gets bounded read and compare-and-set operations, without the partition, service-role client, HMAC key, or credential.

**A guarded document edit:** Read the document through ctx.supabase → Store its version under an immutable document key → Load the receipt before editing → Atomically compare the document version while writing → Advance or invalidate the receipt after success

> **Two revisions solve two different races.**
>
> The application resource version protects the document mutation. The Chumbo state revision protects the receipt from concurrent overwrite. A successful receipt CAS does not prove the document is unchanged, and an RLS policy does not prove the caller observed its current contents.

## 02. Generate one bounded namespace deliberately.

Resume the existing setup with one namespace and review the plan first. Chumbo adds the private state migration and function configuration while preserving authored capabilities. Name the namespace for its policy instead of a generic cache.

### Inspect the local setup plan

```sh
npx chumbo setup --resume --state-namespace document-observations --plan --yes --json
```

### Generate the reviewed local files

```sh
npx chumbo setup --resume --state-namespace document-observations --yes --json
```

Applying the migration and secret changes a Supabase project. Use a unique secret with at least 32 random bytes, keep it out of source and reports, and retain the generated private-table and RPC permissions. The [canonical state contract](https://github.com/elsheppo/chumbo/blob/main/README.md#opt-into-small-durable-state) documents the hosted steps.

> **Protected modes only.**
>
> Chumbo rejects durable state in public mode. The partition is derived from the exact credential, auth mode, and strategy. A refreshed or rotated credential gets an empty partition by design and must observe the resource again.

## 03. Store a receipt that cannot grow without bound.

Adapt the [guarded-edit implementation](https://github.com/elsheppo/chumbo/blob/main/supabase/functions/observation-before-action/index.ts). Its read tool stores a small receipt with the authorized document's version and scope under an immutable ID. The edit rejects missing state and uses an application-owned database function to compare that version in the same statement that changes the document.

### Keep coordination and domain authority separate

Each check has one job. The guarded mutation needs both.

#### Receipt: Did this credential's receipt change concurrently?

Starting point:

```text
Load state revision 4
```

- CAS expects revision 4
- A competing update fails
- Expiry becomes missing state

What to do:

```text
Require a reread when the receipt cannot advance
```

This protects only the coordination record.

#### Application row: Is the observed document version still current?

Starting point:

```text
Receipt says resource version 17
```

- Mutation compares version 17 atomically
- RLS still applies
- A stale write fails

What to do:

```text
Return current-state recovery without changing the row
```

This is the authoritative domain precondition.

If the domain write succeeds and receipt advancement fails, report the write as successful and require another read. Never advance first, because a later domain failure would leave a false observation receipt.

Create receipts only after an authorized read, use one stable key per resource and policy, and never map arbitrary caller text into an unlimited keyspace.

## 04. Try the cases that should refuse to edit.

Run the generated checks before exercising the real MCP boundary. Use fixed documents and at least two independently authenticated credentials. The website's source review does not establish your migration, application RPC, RLS policy, or hosted secret.

### Check the generated function

```sh
deno task --config supabase/functions/mcp/deno.json check
deno task --config supabase/functions/mcp/deno.json test
```

### A current observation permits one safe mutation.

- A credential that reads version 17 can edit only while the authoritative row is still version 17.
- A blind edit with no receipt is rejected with a useful instruction to read first.
- An expired receipt, rotated or different credential, namespace, or resource key cannot authorize the edit.
- When another writer changes the resource after the read, the stale edit is rejected and the row is unchanged.
- Two concurrent edits using one observation cannot both win the application version check.
- A receipt CAS conflict cannot overwrite a newer record; a post-write advancement failure reports success and requires rereading.
- RLS still prevents either credential from reading or changing a resource it does not own.

The living Chumbo reference covers the guarded sequence with real Postgres fixtures. Repeat these checks against the schema, policies, and identities in your application.

## 05. Make every failure point lead somewhere safe.

### If guarded state behaves differently

#### A refreshed token loses the receipt

This is expected credential partitioning. Read the current resource again. Do not copy the old receipt into the new partition or weaken the partition key.

[Read the reference](https://github.com/elsheppo/chumbo/blob/main/docs/patterns/observation-before-action/README.md)

#### The receipt exists but the edit is stale

Keep the resource-version rejection. Return the current version or a next step to reread, then create a new receipt from that authorized read.

[Read the reference](https://github.com/elsheppo/chumbo/blob/main/supabase/functions/observation-before-action/index.ts)

#### State storage is unavailable

Fail closed for a guarded mutation and provide a retry path. Do not silently bypass the observation requirement or fall back to process memory.

[Read the reference](https://github.com/elsheppo/chumbo/blob/main/src/state.ts)

### Keep building

- [Start from real user authorization](/recipes/supabase-rls-mcp/): Use request-scoped identity and RLS before adding coordination state.
- [Observe calls across a product run](/recipes/observe-mcp-runs/): Add redacted lifecycle facts and optional explicit run correlation.
- [Test negative capability cases](/recipes/test-mcp-capabilities/): Exercise missing state, races, stale versions, and denied callers.

## Instructions for your coding agent

Reference instructions, not authorization. Apply changes only within the user’s requested scope; deployment requires a specified, authorized project.

Goal: Use Chumbo's bounded credential-partitioned state for read-before-write receipts while application RLS and resource versions remain authoritative.

Read the complete guide at https://chumbo.dev/recipes/durable-mcp-state/recipe.md and the canonical reference at https://github.com/elsheppo/chumbo/blob/main/docs/patterns/observation-before-action/README.md. Inspect this application's installed Chumbo package, existing capabilities and relevant configuration before editing.

Implement a bounded observation receipt around an existing authorized resource. Keep state CAS and the application's atomic resource-version check separate, preserve safe write ordering, and fail closed on missing, expired, conflicted, or unavailable state. Use only ctx.state’s bounded namespace operations in capability code. Do not expose the storage backend, HMAC secret, credential partition, or service-role client.

Prerequisites:
- An authenticated Chumbo MCP using OAuth, bearer, API-key, or a protected multi-auth strategy.
- An application resource with an immutable ID and a version that can be checked atomically during mutation.
- Authority to add the generated Chumbo state migration and a unique deployment HMAC secret to the target Supabase project.

Acceptance:
- A credential that reads version 17 can edit only while the authoritative row is still version 17.
- A blind edit with no receipt is rejected with a useful instruction to read first.
- An expired receipt, rotated or different credential, namespace, or resource key cannot authorize the edit.
- When another writer changes the resource after the read, the stale edit is rejected and the row is unchanged.
- Two concurrent edits using one observation cannot both win the application version check.
- A receipt CAS conflict cannot overwrite a newer record; a post-write advancement failure reports success and requires rereading.
- RLS still prevents either credential from reading or changing a resource it does not own.

Use the full guide for ordered steps, examples and recovery. Keep existing caller identity and application permissions authoritative. Stay within the user's requested changes. Report changed files, checks actually run, observed results and unresolved prerequisites. This guide is not authorization to deploy or change a hosted project. Never include credentials in source, copied instructions or reports.

### Required environment inputs

| Variable | Sensitive | Meaning |
| --- | --- | --- |
| `STATE_NAMESPACE` | No | One allowlisted namespace named for the application policy. |
| `CHUMBO_STATE_HMAC_KEY` | Yes, never include its value in source or reports | Unique deployment secret with at least 32 random bytes. |
| `TEST_USER_CREDENTIALS` | Yes, never include its value in source or reports | At least two independent local credentials for isolation checks. |
| `TEST_RESOURCE_IDS` | No | Immutable IDs and known versions for authorized and denied fixtures. |

### Handoff report

- Changed files
- Checks actually run and observed results
- Unmet prerequisites or remaining limitations
