Skip to main content
The storage layer persists MCP session data — OAuth tokens, server URLs, connection state — across requests and server restarts. Every server-side operation (connecting, disconnecting, OAuth flows) reads and writes through this layer. You can use the auto-detected global storage proxy, or instantiate a specific backend directly for full control.

StorageBackend interface

All storage backends implement this interface. Import individual backends from @mcp-ts/sdk/server or use the global storage proxy, which selects the backend automatically.
import type { StorageBackend } from '@mcp-ts/sdk/server';

SessionData

The core type stored per session:
interface SessionData {
  sessionId: string;
  identity: string;
  serverUrl: string;
  callbackUrl: string;
  transportType: 'sse' | 'streamable_http';
  createdAt: number;         // Unix timestamp (ms)
  serverId?: string;
  serverName?: string;
  headers?: Record<string, string>;
  active?: boolean;          // false = OAuth-pending or failed; true = fully connected
  tokens?: OAuthTokens;
  clientInformation?: OAuthClientInformationMixed;
  codeVerifier?: string;
  clientId?: string;
}
active drives TTL promotion: sessions start with a 10-minute TTL during OAuth flows, then get promoted to 12 hours once the connection succeeds.

Methods

Generates a unique session ID. The implementation uses a short random string suitable for use as a storage key.
const sessionId = storage.generateSessionId();
Creates a new session. Throws if a session with the same sessionId already exists.
session
SessionData
required
The full session object to store.
ttl
number
Time-to-live in seconds. Defaults to the backend’s own default (typically 12 hours for active sessions).
await storage.createSession({
  sessionId: 'abc123',
  identity: 'user-123',
  serverId: 'my-server',
  serverName: 'My MCP Server',
  serverUrl: 'https://mcp.example.com',
  callbackUrl: 'https://myapp.com/callback',
  transportType: 'streamable_http',
  createdAt: Date.now(),
  active: false,
});
Updates an existing session with partial data. Throws if the session does not exist.
identity
string
required
The user identity that owns this session.
sessionId
string
required
The session to update.
data
Partial<SessionData>
required
Fields to update. Unspecified fields are unchanged.
ttl
number
New TTL in seconds.
await storage.updateSession('user-123', 'abc123', {
  active: true,
  tokens: { access_token: 'new-token', token_type: 'Bearer' },
});
Retrieves a session. Returns null if not found.
const session = await storage.getSession('user-123', 'abc123');
if (!session) {
  // session expired or was removed
}
Returns all sessions for a given identity, including full session data with OAuth tokens.
const sessions = await storage.getIdentitySessionsData('user-123');
Returns only the session IDs for a given identity. Lighter than getIdentitySessionsData when you only need IDs.
const sessionIds = await storage.getIdentityMcpSessions('user-123');
Permanently deletes a session and all its data.
await storage.removeSession('user-123', 'abc123');
Returns every session ID across all users. Admin operation — use with care.
const all = await storage.getAllSessionIds();
Deletes all sessions across all users. Admin operation.
await storage.clearAll();
Removes expired sessions. This is a no-op on backends that handle TTL natively (Redis, Supabase). Useful as a scheduled job on File and SQLite backends.
await storage.cleanupExpiredSessions();
Closes the connection to the storage backend. Call this during graceful shutdown.
await storage.disconnect();
Optional. When present, validates connectivity to the backend at startup (table existence, network reachability, etc.). Called automatically when the global storage proxy initializes.

Global storage proxy

The storage export is a lazy proxy that auto-selects and initializes the backend the first time any method is called.
import { storage } from '@mcp-ts/sdk/server';

Environment variables

VariablePurpose
MCP_TS_STORAGE_TYPEExplicit backend selection: redis, file, sqlite, supabase, or memory
REDIS_URLRedis connection URL (e.g. redis://localhost:6379)
MCP_TS_STORAGE_FILEPath for the file backend (e.g. ./sessions.json)
MCP_TS_STORAGE_SQLITE_PATHPath for the SQLite database file
SUPABASE_URLSupabase project URL
SUPABASE_SERVICE_ROLE_KEYSupabase service role key (recommended for server-side use)
SUPABASE_ANON_KEYSupabase anon key (fallback; may cause RLS violations)
Auto-detection order (when MCP_TS_STORAGE_TYPE is not set): REDIS_URLMCP_TS_STORAGE_FILEMCP_TS_STORAGE_SQLITE_PATHSUPABASE_URL → in-memory.

Individual backends

Import and instantiate backends directly when you want explicit control over initialization or need to pass a custom client instance.
import {
  RedisStorageBackend,
  MemoryStorageBackend,
  FileStorageBackend,
  SqliteStorage,
  SupabaseStorageBackend,
  createSupabaseStorageBackend,
} from '@mcp-ts/sdk/server';
Backed by Redis. Recommended for production — supports TTL natively, scales across multiple server instances.Requires the ioredis peer dependency.
redis
Redis
required
An ioredis Redis instance.
import { Redis } from 'ioredis';
import { RedisStorageBackend } from '@mcp-ts/sdk/server';

const redis = new Redis(process.env.REDIS_URL);
const store = new RedisStorageBackend(redis);
await store.init();
In-memory store — no constructor arguments. Data is lost when the process exits. Use for local development and testing.
import { MemoryStorageBackend } from '@mcp-ts/sdk/server';

const store = new MemoryStorageBackend();
Persists sessions as JSON to a local file. Suitable for single-process development environments.
options.path
string
Path to the JSON file. Defaults to ./mcp-sessions.json.
import { FileStorageBackend } from '@mcp-ts/sdk/server';

const store = new FileStorageBackend({ path: './sessions.json' });
await store.init();
Persists sessions in a local SQLite database. Zero-config for single-server deployments.Requires the better-sqlite3 peer dependency.
options.path
string
Path to the SQLite database file. Defaults to a file in the working directory.
import { SqliteStorage } from '@mcp-ts/sdk/server';

const store = new SqliteStorage({ path: './sessions.db' });
await store.init();
Backed by a Supabase (PostgreSQL) database. Supports row-level security policies and scales across multiple server instances.Requires the @supabase/supabase-js peer dependency. Run npx mcp-ts supabase-init to eject the required database migrations before using this backend.
client
SupabaseClient
required
A @supabase/supabase-js client instance. Use the service role key for server-side usage to bypass RLS policies.
import { createClient } from '@supabase/supabase-js';
import { SupabaseStorageBackend } from '@mcp-ts/sdk/server';

const client = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const store = new SupabaseStorageBackend(client);
await store.init();
Factory function that constructs a SupabaseStorageBackend. Equivalent to new SupabaseStorageBackend(client).
client
SupabaseClient
required
A Supabase client instance.
import { createClient } from '@supabase/supabase-js';
import { createSupabaseStorageBackend } from '@mcp-ts/sdk/server';

const store = createSupabaseStorageBackend(
  createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!)
);