Postgres Token State
@caracalai/tokenstate-postgres provides PostgresBackend, a class that persists MCP token state to a Postgres table. It is a storage backend for MCP session token records — not a RevocationStore and not a TokenCache. Use it when you need durable, queryable token state across restarts or replicas.
Install
Section titled “Install”npm install @caracalai/tokenstate-postgres pgnpm install --save-dev @types/pgPostgresBackend
Section titled “PostgresBackend”Constructor
Section titled “Constructor”import { PostgresBackend } from '@caracalai/tokenstate-postgres';import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });const backend = new PostgresBackend(pool);The constructor accepts a pg.Pool instance. PostgresBackend does not manage the pool lifecycle — create and close the pool in your application startup/shutdown code.
Table schema
Section titled “Table schema”PostgresBackend stores token state in the mcp_token_state table. Call migrate() once at startup to create the table if it does not exist.
CREATE TABLE IF NOT EXISTS mcp_token_state ( zone_id TEXT NOT NULL, sub TEXT NOT NULL, scope TEXT NOT NULL, expires_at TIMESTAMPTZ NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (zone_id, sub));The primary key is (zone_id, sub) — one active token state row per subject per zone.
Methods
Section titled “Methods”migrate()
Section titled “migrate()”async migrate(): Promise<void>Executes the DDL to create mcp_token_state. Safe to call on every startup — the IF NOT EXISTS clause makes it idempotent. Throws if the database connection fails.
upsert(zoneId, sub, scope, expiresAt)
Section titled “upsert(zoneId, sub, scope, expiresAt)”async upsert( zoneId: string, sub: string, scope: string, expiresAt: Date,): Promise<void>Inserts or updates the token state row for (zoneId, sub). Sets updated_at to the current database time. Use this when a new mandate is issued for a subject.
get(zoneId, sub)
Section titled “get(zoneId, sub)”async get(zoneId: string, sub: string): Promise<TokenState | null>Returns the TokenState for (zoneId, sub), or null if no row exists.
interface TokenState { zoneId: string; sub: string; scope: string; expiresAt: Date; updatedAt: Date;}delete(zoneId, sub)
Section titled “delete(zoneId, sub)”async delete(zoneId: string, sub: string): Promise<void>Deletes the token state row for (zoneId, sub). No-op if the row does not exist.
import { PostgresBackend } from '@caracalai/tokenstate-postgres';import { Pool } from 'pg';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });const backend = new PostgresBackend(pool);
// Run once at startupawait backend.migrate();
// Store token state after issuing a mandateawait backend.upsert( claims.zoneId, claims.sub, claims.scope, new Date(Date.now() + expiresIn * 1000),);
// Read back during a session checkconst state = await backend.get(claims.zoneId, claims.sub);if (!state || state.expiresAt < new Date()) { // Token not found or expired}
// Delete on logout or revocationawait backend.delete(claims.zoneId, claims.sub);PostgresBackend does not implement RevocationStore. It stores token state (scope, expiry) for your application’s session management — it does not participate in the mandate verification pipeline directly. Mandate verification remains the responsibility of @caracalai/transport-mcp or @caracalai/mcp-express.
For revocation store functionality, use @caracalai/revocation (in-memory) or @caracalai/revocation-redis (production). See the Redis connector reference.