Skip to main content

Storage Backends

The library supports multiple storage backends for session persistence, allowing you to choose the best option for your deployment environment.

Available Backends

Redis (Production)

Recommended for production and serverless deployments.

Redis provides distributed, persistent storage with automatic TTL (Time To Live) management. Perfect for:

  • Production environments
  • Serverless deployments (Vercel, AWS Lambda)
  • Multi-instance applications
  • High-availability setups

Installation:

npm install @mcp-ts/sdk ioredis

Configuration:

# Explicit selection (optional)
MCP_TS_STORAGE_TYPE=redis

# Redis connection URL (required)
REDIS_URL=redis://localhost:6379

# Or for cloud Redis with TLS
REDIS_URL=rediss://default:password@host.upstash.io:6379

Features:

  • Automatic session expiration (12 hours TTL)
  • Atomic operations for data consistency
  • Distributed storage across instances
  • Production-ready scalability

Usage:

import { storage } from '@mcp-ts/sdk/server';

// Storage automatically uses Redis when REDIS_URL is set
const sessionId = storage.generateSessionId();

await storage.createSession({
sessionId,
identity: 'user-123',
serverUrl: 'https://mcp.example.com',
callbackUrl: 'https://app.com/callback',
transportType: 'sse',
active: true,
createdAt: Date.now(),
});

File System (Development)

Perfect for local development with persistent sessions across restarts.

File storage persists sessions to a JSON file on disk. Ideal for:

  • Local development
  • Single-instance deployments
  • Testing with persistent state
  • Environments without Redis

Configuration:

# Explicit selection (optional)
MCP_TS_STORAGE_TYPE=file

# File path for session storage (required)
MCP_TS_STORAGE_FILE=./sessions.json

Features:

  • Persistent across application restarts
  • No external dependencies
  • Human-readable JSON format
  • Automatic directory creation

Usage:

import { storage } from '@mcp-ts/sdk/server';

// Storage automatically uses File when MCP_TS_STORAGE_FILE is set
const sessions = await storage.getIdentitySessionsData('user-123');
console.log('Stored sessions:', sessions);

File Format:

[
{
"sessionId": "abc123",
"identity": "user-123",
"serverId": "server-1",
"serverName": "My MCP Server",
"serverUrl": "https://mcp.example.com",
"callbackUrl": "https://app.com/callback",
"transportType": "sse",
"active": true,
"createdAt": 1706234567890
}
]

In-Memory (Testing)

Fast ephemeral storage, ideal for testing. Sessions are lost on restart.

In-memory storage keeps sessions in RAM. Best for:

  • Unit testing
  • Integration testing
  • Quick prototyping
  • Temporary sessions

Configuration:

# Explicit selection (optional)
MCP_TS_STORAGE_TYPE=memory

# No additional configuration needed

SQLite (Persistent & Fast)

Zero-configuration persistent storage, faster than file based.

SQLite provides a single-file relational database that is robust and requires no external server process.

Installation:

npm install better-sqlite3
npm install -D @types/better-sqlite3

Configuration:

# Explicit selection (optional)
MCP_TS_STORAGE_TYPE=sqlite

# SQLite DB Path (optional, defaults to ./sessions.db)
MCP_TS_STORAGE_SQLITE_PATH=./data/mcp.db

Features:

  • Persistent single-file database
  • Much faster than JSON file storage
  • ACID compliant transactions
  • Zero configuration (auto-creates DB)

Features:

  • Fastest performance
  • No external dependencies
  • Zero configuration
  • Sessions lost on restart

Usage:

import { storage } from '@mcp-ts/sdk/server';

// Storage uses in-memory by default if no other backend is configured
await storage.createSession({
sessionId: 'test-123',
identity: 'test-user',
serverUrl: 'https://test.example.com',
callbackUrl: 'https://test.com/callback',
transportType: 'sse',
active: true,
createdAt: Date.now(),
});

Supabase (Production)

Cloud-native PostgreSQL storage with built-in security and row-level security (RLS).

Supabase provides a powerful, scalable backend for your MCP sessions. Ideal for:

  • Production environments
  • Next.js applications (built-in integration)
  • Applications requiring Row Level Security (RLS)
  • Managed PostgreSQL with zero maintenance

Installation:

npm install @mcp-ts/sdk @supabase/supabase-js

Configuration:

# Explicit selection (optional)
MCP_TS_STORAGE_TYPE=supabase

# Supabase connection details (required)
SUPABASE_URL=https://your-project.supabase.co
# Use the service_role key for server-side storage (not the anon key)
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
warning

Always use SUPABASE_SERVICE_ROLE_KEY for server-side storage — not SUPABASE_ANON_KEY. The anon key is subject to Row Level Security (RLS) policies which will block session creation. The service_role key is designed for trusted server-to-server communication and bypasses RLS. Find it in: Supabase Dashboard → Project Settings → API → service_role.

Database Setup:

To use Supabase as a storage backend, you must create the mcp_sessions table and configure RLS policies.

You can easily "eject" the required migration SQL into your own project using the built-in CLI:

  1. Run the initialization command:

    npx mcp-ts supabase-init

    This will copy the migration files to your local ./supabase/migrations/ folder.

  2. Link your project & push:

    npx supabase link --project-ref <your-project-id>
    npx supabase db push

Option B: SQL Editor (Manual)

If you prefer manual setup, copy the SQL from the migration file and run it in the Supabase Dashboard SQL Editor.

Features:

  • PostgreSQL persistence with JSONB support
  • Row Level Security (RLS) for tenant isolation
  • Automatic updated_at and expires_at management
  • Cloud-native and serverless friendly
  • Application-level AES-256-GCM encryption for tokens and headers

Usage:

import { createSupabaseStorageBackend } from '@mcp-ts/sdk/server';
import { createClient } from '@supabase/supabase-js';

// Always use the service_role key for server-side usage
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const storage = createSupabaseStorageBackend(supabase);

await storage.createSession({
sessionId: 'sb-123',
identity: 'user-789',
serverUrl: 'https://mcp.example.com',
callbackUrl: 'https://app.com/callback',
transportType: 'sse',
active: true,
createdAt: Date.now(),
});

Encryption at Rest:

The Supabase backend automatically encrypts sensitive session fields (tokens and headers) using AES-256-GCM before writing to the database. All encryption/decryption happens transparently in your Node.js application — Supabase only ever sees cipher text.

To enable encryption, set the STORAGE_ENCRYPTION_KEY environment variable to a 32-byte hex string:

# Generate a secure key:
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
STORAGE_ENCRYPTION_KEY=your-64-character-hex-string

Once set, encrypted data in the database will look like this:

{
"tokens": "enc:1:cd4511ef932b...:3f2a1b...:a4b5c6d7...",
"headers": "enc:1:1234abcd...:..."
}
tip

If STORAGE_ENCRYPTION_KEY is not set, mcp-ts will print a single startup warning and save data without encryption — your app will not crash. This allows you to opt-in gradually or skip encryption in local dev.

warning

Never commit STORAGE_ENCRYPTION_KEY to version control. Treat it the same as a database password. If it is lost, encrypted session data from the database cannot be recovered.


PostgreSQL

For self-hosted PostgreSQL environments.

Features:

  • Relational data storage
  • Advanced querying capabilities
  • ACID compliance
  • Integration with existing databases

Automatic Backend Selection

The library automatically selects the appropriate storage backend using this priority:

Priority Order:

  1. Explicit: If MCP_TS_STORAGE_TYPE is set, use that backend
  2. Auto-detect Redis: If REDIS_URL is present, use Redis
  3. Auto-detect Supabase: If SUPABASE_URL is present, use Supabase
  4. Auto-detect File: If MCP_TS_STORAGE_FILE is present, use File
  5. Auto-detect SQLite: If MCP_TS_STORAGE_SQLITE_PATH is present, use SQLite
  6. Default: Fall back to In-Memory storage

Custom Backend Implementation

You can use specific storage backends directly:

import { 
RedisStorageBackend,
MemoryStorageBackend,
FileStorageBackend
} from '@mcp-ts/sdk/server';
import { Redis } from 'ioredis';

// Custom Redis instance
const redis = new Redis({
host: 'localhost',
port: 6379,
password: 'secret',
});
const redisStorage = new RedisStorageBackend(redis);

// Custom file path
const fileStorage = new FileStorageBackend({
path: '/var/data/sessions.json'
});
await fileStorage.init();

// In-memory for testing
const memoryStorage = new MemoryStorageBackend();

Backend Comparison

Feature Redis Supabase SQLite File System In-Memory
Persistence Yes Yes Yes Yes No
Distributed Yes Yes No No No
Auto-Expiry Yes (TTL) Yes (Manual) Yes (Manual) No No
Performance Fast Fast Very Fast Medium Fastest
Setup External Cloud Native Built-in Built-in
Serverless Yes Recommended Limited Yes
Production Recommended Recommended Single-instance Not recommended
Development Good Excellent Excellent Good
Testing Good Excellent Good Excellent
Encryption No AES-256-GCM No No No

Session Data Structure

All backends store the same session data structure:

interface SessionData {
sessionId: string;
identity?: string;
serverId?: string;
serverName?: string;
serverUrl: string;
callbackUrl: string;
transportType: 'sse' | 'streamable_http';
active: boolean;
createdAt: number;
headers?: Record<string, string>;
// OAuth data
tokens?: OAuthTokens;
clientInformation?: OAuthClientInformation;
codeVerifier?: string;
clientId?: string;
}

Best Practices

Production Deployments

# Use Redis for production
MCP_TS_STORAGE_TYPE=redis
REDIS_URL=rediss://user:pass@production-redis.example.com:6379

Local Development

# Use File storage for development
MCP_TS_STORAGE_TYPE=file
MCP_TS_STORAGE_FILE=./dev-sessions.json

Testing

# Use in-memory for tests
MCP_TS_STORAGE_TYPE=memory

Serverless (Vercel, AWS Lambda)

# Use Redis with Upstash or similar
REDIS_URL=rediss://default:token@serverless-redis.upstash.io:6379

Security (Supabase)

# Enable encryption for tokens and headers at rest
# Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
STORAGE_ENCRYPTION_KEY=your-64-character-hex-string

This enables transparent AES-256-GCM encryption so that even if your Supabase database is compromised, OAuth tokens and authorization headers remain unreadable.


Troubleshooting

Redis Connection Failed

# Verify Redis is running
redis-cli ping # Should return PONG

# Check connection string
echo $REDIS_URL

# Test with redis-cli
redis-cli -u $REDIS_URL ping

File Storage Not Persisting

# Check file permissions
ls -la ./sessions.json

# Verify path is writable
touch ./sessions.json

# Check environment variable
echo $MCP_TS_STORAGE_FILE

Sessions Lost on Restart

If you're using in-memory storage (default), sessions will be lost on restart. Switch to Redis or File storage for persistence:

# Add to .env
MCP_TS_STORAGE_TYPE=file
MCP_TS_STORAGE_FILE=./sessions.json

Next Steps