Skip to main content
The server-side module (@mcp-ts/sdk/server) gives you everything you need to accept MCP connections, manage sessions, and call tools on connected servers. Use createNextMcpHandler for Next.js App Router projects, createSSEHandler for Express or other Node.js frameworks, and MultiSessionClient or MCPClient when you need to drive connections from your own server-side code.

createNextMcpHandler(options)

Creates GET and POST route handlers for Next.js App Router API routes. The POST handler accepts JSON-RPC requests and returns either a direct JSON response or an SSE stream (when the client sends Accept: text/event-stream).
import { createNextMcpHandler } from '@mcp-ts/sdk/server';

export const { GET, POST } = createNextMcpHandler({
  getIdentity: (request) => request.headers.get('x-user-id'),
  authenticate: async (identity, token) => verifyJwt(token),
});

Options

getIdentity
(request: Request) => string | null
Extracts the user identity from the incoming request. Defaults to reading the identity query parameter. Return null to signal a missing identity, which causes the handler to respond with 400 MISSING_IDENTITY.
getAuthToken
(request: Request) => string | null
Extracts an auth token from the request. Defaults to reading the token query parameter or the Authorization header.
authenticate
(identity: string, token: string | null) => Promise<boolean> | boolean
Verifies that the identity and token are authorized. Return false to respond with 401 UNAUTHORIZED. Defaults to always returning true.
heartbeatInterval
number
Milliseconds between SSE heartbeat messages. Defaults to 30000.
clientDefaults
ClientMetadata
Static OAuth client metadata applied to every connection. Fields: clientName?, clientUri?, logoUri?, policyUri?.
getClientMetadata
(request: Request) => ClientMetadata | Promise<ClientMetadata>
Per-request OAuth client metadata. When provided, its values are merged over clientDefaults.

Return value

GET
(request: Request) => Promise<Response>
Returns a 405 METHOD_NOT_ALLOWED response directing clients to use POST.
POST
(request: Request) => Promise<Response>
Handles JSON-RPC requests. Returns application/json by default or text/event-stream when the client sends Accept: text/event-stream.

MultiSessionClient

Manages all active MCP connections for a single user identity. It reads session data from your configured storage backend, connects in batches of five, and retries failed connections automatically. Use this on traditional long-running servers (where you can cache the instance between requests) or on serverless platforms (where you create a fresh instance per invocation — session data persists in storage either way).
import { MultiSessionClient } from '@mcp-ts/sdk/server';

const mcp = new MultiSessionClient('user-123', {
  timeout: 15000,
  maxRetries: 2,
  retryDelay: 1000,
});

await mcp.connect();
const clients = mcp.getClients();

Constructor

identity
string
required
A unique string that identifies the user — for example, a user ID or email address. Used to look up sessions in the storage backend.
options
MultiSessionOptions
Optional connection tuning.

MultiSessionOptions

timeout
number
Per-connection timeout in milliseconds. Defaults to 15000.
maxRetries
number
Maximum retry attempts per session if the connection fails. Defaults to 2.
retryDelay
number
Milliseconds to wait between retry attempts. Defaults to 1000.

Methods

Fetches all active sessions for this identity from storage and establishes connections in batches. Sessions with active: false (OAuth-pending or previously failed) are skipped. A session that fails after all retries is logged and skipped — it does not throw.
await mcp.connect();
Returns all currently connected MCPClient instances. Use this to enumerate available tools across servers or to route a tool call to the right client by serverId.
const clients = mcp.getClients();
for (const client of clients) {
  const { tools } = await client.listTools();
  console.log(client.getServerId(), tools.map(t => t.name));
}
Disconnects all active clients and clears the internal client list. Call this on server shutdown or when the user logs out.
mcp.disconnect();

MCPClient

A single MCP client connection that supports OAuth 2.1, automatic transport negotiation (streamable HTTP → SSE fallback), and session persistence via the storage backend. You rarely need to instantiate MCPClient directly — prefer MultiSessionClient for multi-server scenarios.
import { MCPClient } from '@mcp-ts/sdk/server';

const client = new MCPClient({
  identity: 'user-123',
  sessionId: 'existing-session-id',
});

Constructor options (MCPOAuthClientOptions)

identity
string
required
User identity — must match the value used when the session was created.
sessionId
string
required
Primary key for session lookup in storage.
serverUrl
string
MCP server URL. Loaded from storage if not provided.
serverName
string
Human-readable server name. Loaded from storage if not provided.
callbackUrl
string
OAuth redirect URL. Loaded from storage if not provided.
serverId
string
Server identifier (max 12 characters). Loaded from storage if not provided.
transportType
'sse' | 'streamable_http'
Force a specific transport. When omitted, the client tries streamable_http first, then falls back to sse.
clientName
string
OAuth client name shown during authorization.
clientUri
string
OAuth client URI.
logoUri
string
OAuth client logo URI.
policyUri
string
OAuth client policy URI.
headers
Record<string, string>
Additional HTTP headers sent with every request to the MCP server.
onRedirect
(url: string) => void
Called when the OAuth flow requires a redirect. Use this for server-side redirect handling.

Methods

Initializes the client from storage, validates and refreshes OAuth tokens if needed, then establishes the transport connection. Emits state_changed events throughout.Throws UnauthorizedError when OAuth authorization is required. In that case, the session is saved with a 10-minute TTL and the auth_required event fires with an authUrl.
import { UnauthorizedError } from '@mcp-ts/sdk/server';

try {
  await client.connect();
} catch (error) {
  if (error instanceof UnauthorizedError) {
    // Redirect user to the OAuth provider
  }
}
Closes the transport connection and clears internal state. Does not remove the session from storage — use clearSession() for that.
client.disconnect('user logged out');
Returns true when the underlying MCP client is active.
if (client.isConnected()) {
  const { tools } = await client.listTools();
}
Lists all tools available on the connected server. Emits DISCOVERINGREADY state transitions and fires tools_discovered.
const { tools } = await client.listTools();
Executes a tool on the connected server.
const result = await client.callTool('get_weather', { location: 'Berlin' });
Lists all prompts available on the connected server.
const { prompts } = await client.listPrompts();
Retrieves a specific prompt with optional arguments.
const prompt = await client.getPrompt('code-review', { language: 'typescript' });
Lists all resources available on the connected server.
const { resources } = await client.listResources();
Reads a specific resource by URI.
const resource = await client.readResource('file:///path/to/file');
Completes the OAuth authorization flow by exchanging the authorization code for tokens, then establishes the connection. Saves the session with a 12-hour TTL on success.
await client.finishAuth(req.query.code as string);
Invalidates OAuth credentials and removes the session from storage entirely, then calls disconnect().
Returns the server ID.
Returns the session ID.
Returns the server name as reported by the server, falling back to the name provided at construction.
Returns the server URL, or an empty string if not set.
Returns the transport type in use. Defaults to 'streamable_http'.
Loads all sessions for a user, validates OAuth tokens (refreshing if needed), and returns a config object keyed by sanitized server label. Useful for passing MCP server configurations to AI agent frameworks.
const config = await MCPClient.getMcpServerConfig('user-123');
// { 'my-server': { transport: 'streamable_http', url: '...', headers: { Authorization: 'Bearer ...' } } }

createSSEHandler(options)

Creates an SSE handler for Node.js HTTP frameworks like Express. Manages SSE streaming and routes RPC requests.
import { createSSEHandler } from '@mcp-ts/sdk/server';
import express from 'express';

const app = express();

app.use('/mcp', createSSEHandler({
  identity: 'user-123',
  heartbeatInterval: 30000,
}));

Options (SSEHandlerOptions)

identity
string
required
User or client identifier for this connection.
onAuth
(identity: string) => Promise<boolean>
Optional authentication callback.
heartbeatInterval
number
Heartbeat interval in milliseconds. Defaults to 30000.
clientDefaults
ClientMetadata
Static OAuth client metadata.
getClientMetadata
(request?: unknown) => ClientMetadata | Promise<ClientMetadata>
Per-request dynamic OAuth client metadata.

storage

A global StorageBackend proxy that lazily initializes the appropriate storage backend based on environment variables. All methods are async, even on backends where the underlying operation is synchronous.
import { storage } from '@mcp-ts/sdk/server';

const session = await storage.getSession('user-123', 'session-abc');
The backend is selected in this order:
  1. MCP_TS_STORAGE_TYPE environment variable (redis, file, sqlite, supabase, memory)
  2. Auto-detection: REDIS_URLMCP_TS_STORAGE_FILEMCP_TS_STORAGE_SQLITE_PATHSUPABASE_URL
  3. Falls back to in-memory storage
See the Storage API reference for the full method list and available backends.