Skip to main content
Storage backends are how @mcp-ts/sdk keeps track of active MCP sessions across requests. Every session — its OAuth tokens, transport type, server URL, and connection state — lives in the storage layer. Because your server process may restart or scale horizontally at any time, storage must live outside the process. Pick the backend that fits your environment; the SDK handles the rest transparently through a single storage export.

How auto-detection works

The SDK selects a backend at startup using a fixed priority order. You can either set MCP_TS_STORAGE_TYPE to force a specific backend, or let the SDK infer one from whichever environment variables are present. Detection priority:
  1. MCP_TS_STORAGE_TYPE — explicit override always wins
  2. REDIS_URL present → Redis
  3. MCP_TS_STORAGE_FILE present → File
  4. MCP_TS_STORAGE_SQLITE_PATH present → SQLite
  5. SUPABASE_URL (+ key) present → Supabase
  6. Nothing set → Memory (default)
When Redis auto-detection fails (unreachable host, bad URL, etc.), the SDK logs a warning and falls back to in-memory storage. Your app continues to run, but sessions will not survive a restart. The same fallback applies when MCP_TS_STORAGE_TYPE=redis is set explicitly but the connection fails.

Backend comparison

BackendPersistenceDistributedAuto-expiryExtra installProduction-ready
MemoryNoNoNoNoneNo
FileYesNoNoNoneNo
SQLiteYesNoYes (manual)better-sqlite3Single-instance only
RedisYesYesYes (TTL)ioredisYes
SupabaseYesYesYes (manual)@supabase/supabase-jsYes
Use Redis or Supabase in production. Use Memory in tests and CI. Use File or SQLite for local development when you want sessions to survive a restart.

Quick configuration

MCP_TS_STORAGE_TYPE=redis
REDIS_URL=redis://localhost:6379

Using a backend directly

The auto-detection path covers most use cases, but you can also instantiate any backend directly — useful when you want to pass an existing client or a custom configuration.
import {
  RedisStorageBackend,
  MemoryStorageBackend,
  FileStorageBackend,
  createSupabaseStorageBackend,
} from '@mcp-ts/sdk/server';
import { Redis } from 'ioredis';
import { createClient } from '@supabase/supabase-js';

// Redis with a custom client
const redis = new Redis({ host: 'localhost', port: 6379, password: 'secret' });
const redisStorage = new RedisStorageBackend(redis);
await redisStorage.init();

// Supabase with an existing client
const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const supabaseStorage = createSupabaseStorageBackend(supabase);
await supabaseStorage.init();

// File at a custom path
const fileStorage = new FileStorageBackend({ path: '/var/data/sessions.json' });
await fileStorage.init();

// In-memory for tests
const memoryStorage = new MemoryStorageBackend();
await memoryStorage.init();

Session data structure

Every backend stores the same SessionData shape:
interface SessionData {
  sessionId: string;
  serverId?: string;
  serverName?: string;
  serverUrl: string;
  transportType: 'sse' | 'streamable_http';
  callbackUrl: string;
  createdAt: number;
  identity: string;
  headers?: Record<string, string>;
  active?: boolean;
  // OAuth fields
  clientInformation?: OAuthClientInformationMixed;
  tokens?: OAuthTokens;
  codeVerifier?: string;
  clientId?: string;
}
The active flag controls TTL transitions: false means the session is short-lived (auth pending or failed); true means the session is fully established and uses the long-lived TTL.

Choose your backend

Redis

Distributed, TTL-managed storage. Recommended for all production and serverless deployments.

Supabase

PostgreSQL-backed storage with RLS policies and AES-256-GCM encryption at rest.

SQLite

Zero-config persistent database. Good for single-instance apps and local development.

File and Memory

Built-in backends requiring no extra packages. File for local dev, Memory for testing.