This guide covers the full MCPServer configuration API, session and transport options, and deployment strategies.
Server Configuration
Basic Configuration
MCPServer accepts a ServerConfig object at construction time:
import { MCPServer } from 'mcp-use/server'
const server = new MCPServer({
// ── Required ──────────────────────────────────────────────
name: 'my-server',
version: '1.0.0',
// ── Identity (shown in Inspector / MCP clients) ───────────
title: 'My Server', // Display name (defaults to name)
description: 'What this server does', // Shown during discovery
websiteUrl: 'https://myserver.com',
favicon: 'favicon.ico', // Relative to public/ dir
icons: [
{
src: 'icon.svg',
mimeType: 'image/svg+xml',
sizes: ['512x512']
}
],
// ── Network ────────────────────────────────────────────────
host: 'localhost', // Bind hostname (default: 'localhost')
baseUrl: 'https://mcp.example.com', // Public URL for widget/asset URLs
// Overrides host:port; also read from MCP_URL env var
// ── CORS ───────────────────────────────────────────────────
cors: {
origin: ['https://myapp.com'],
allowMethods: ['GET', 'POST', 'OPTIONS'],
},
// ── DNS Rebinding Protection ───────────────────────────────
allowedOrigins: ['https://myapp.com'], // Enables Host header validation
// ── Transport ─────────────────────────────────────────────
stateless: false, // See "Stateless Mode" below
// ── Sessions ──────────────────────────────────────────────
sessionIdleTimeoutMs: 3600000, // 1 hour (default: 86400000 = 1 day)
sessionStore: new InMemorySessionStore(), // See "Session Storage" below
streamManager: new InMemoryStreamManager(), // See "Stream Manager" below
// ── Auth ─────────────────────────────────────────────────
oauth: oauthAuth0Provider({ ... }), // See OAuth docs
})
await server.listen(3000)
Port resolution order: listen(port) argument → --port CLI flag → PORT env var → 3000
Base URL resolution order: baseUrl config → MCP_URL env var → http://{host}:{port}
Environment Variables
The server reads the following environment variables at runtime:
| Variable | Effect | Default |
|---|
PORT | HTTP server port | 3000 |
HOST | Bind hostname | localhost |
MCP_URL | Full public base URL (for widget/asset URLs) | http://{HOST}:{PORT} |
NODE_ENV | production disables dev features (inspector, type generation) | — |
DEBUG | Enables verbose debug logging | — |
CSP_URLS | Comma-separated extra URLs added to widget CSP resource_domains | — |
OAuth providers also read zero-config env vars — see the OAuth docs for the full list.
Transport & Sessions
Stateless Mode
The server supports two transport modes:
| Mode | Sessions | SSE | Best for |
|---|
| Stateful | Yes | Yes | Long-lived clients, notifications, sampling |
| Stateless | No | No | Edge functions, serverless, simple request/response |
Auto-detection (default):
- Deno / edge runtimes → always stateless
- Node.js → detected per-request from the
Accept header:
Accept: application/json, text/event-stream → stateful (SSE)
Accept: application/json only → stateless
// Force stateless mode (ignores Accept header)
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
stateless: true,
})
// Force stateful mode
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
stateless: false,
})
Leave stateless unset in most cases. Auto-detection lets the same server handle both SSE-capable and HTTP-only clients transparently.
Session Storage
Session metadata (client capabilities, log level, etc.) is stored in a pluggable SessionStore. Three backends are available:
In-memory (default)
import { MCPServer, InMemorySessionStore } from 'mcp-use/server'
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
sessionStore: new InMemorySessionStore(), // default — no config needed
})
Sessions are lost on server restart. Suitable for single-instance production servers.
Filesystem (development)
import { MCPServer, FileSystemSessionStore } from 'mcp-use/server'
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
sessionStore: new FileSystemSessionStore({
path: '.mcp-use/sessions.json', // default
debounceMs: 100, // write debounce (default: 100ms)
maxAgeMs: 86400000, // session TTL (default: 24h)
}),
})
Sessions survive HMR reloads, so clients don’t need to re-initialize when you save a file during development.
FileSystemSessionStore is not designed for production or distributed deployments. Use Redis for those scenarios.
Redis (production)
import { MCPServer, RedisSessionStore } from 'mcp-use/server'
import { createClient } from 'redis'
const redis = createClient({ url: process.env.REDIS_URL })
await redis.connect()
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
sessionStore: new RedisSessionStore({
client: redis,
prefix: 'mcp:session:', // default
defaultTTL: 3600, // seconds (default: 3600 = 1 hour)
}),
})
Sessions persist across server restarts and are shared across all instances, enabling horizontal scaling.
Distributed Stream Management
The streamManager controls active SSE connections and is responsible for routing server-to-client push notifications. By default, SSE streams are in-memory, meaning notifications only reach clients connected to the same server instance.
For distributed deployments with multiple server instances behind a load balancer, use RedisStreamManager to fan-out notifications via Redis Pub/Sub:
import { MCPServer, RedisSessionStore, RedisStreamManager } from 'mcp-use/server'
import { createClient } from 'redis'
// Redis Pub/Sub requires a dedicated client — it cannot be shared
const redis = createClient({ url: process.env.REDIS_URL })
const pubSubRedis = redis.duplicate()
await redis.connect()
await pubSubRedis.connect()
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
sessionStore: new RedisSessionStore({ client: redis }),
streamManager: new RedisStreamManager({
client: redis, // for session availability checks
pubSubClient: pubSubRedis, // dedicated Pub/Sub client (required)
prefix: 'mcp:stream:', // default
heartbeatInterval: 10, // seconds (default: 10)
}),
})
How it works:
- Client connects to Server A → SSE stream created, Server A subscribes to
mcp:stream:{sessionId} in Redis
- Client’s next request hits Server B (load balancer)
- Server B sends a notification → publishes to Redis channel
- Server A receives the Redis message → pushes to the SSE stream → client gets the notification
Middleware Configuration
MCPServer exposes a server.app Hono instance for adding custom routes and middleware.
CORS Customization
By default, CORS is permissive (origin: "*") for developer ergonomics. Set cors in the server config to restrict it.
The cors config option replaces the default CORS configuration entirely — there is no merge. If you override cors, include all headers your clients need (e.g. mcp-session-id in exposeHeaders).
Default CORS configuration:
{
origin: '*',
allowMethods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowHeaders: [
'Content-Type', 'Accept', 'Authorization',
'mcp-protocol-version', 'mcp-session-id',
'X-Proxy-Token', 'X-Target-URL',
],
exposeHeaders: ['mcp-session-id'],
}
Custom CORS example:
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
cors: {
origin: ['https://app.example.com'],
allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
allowHeaders: ['Content-Type', 'Authorization', 'mcp-protocol-version'],
exposeHeaders: ['mcp-session-id'],
},
})
Route-Scoped Middleware
Add middleware or custom routes to server.app:
import { MCPServer } from 'mcp-use/server'
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
})
// Auth middleware for custom routes
server.app.use('/api/admin/*', async (c, next) => {
const apiKey = c.req.header('x-api-key')
if (!apiKey || apiKey !== process.env.API_KEY) {
return c.json({ error: 'Unauthorized' }, 401)
}
await next()
})
// Custom HTTP endpoint
server.app.get('/health', (c) => c.json({ status: 'ok' }))
MCP protocol routes (/mcp, /mcp-use/widgets/*, /inspector) are registered by the server internally. Add your custom routes before calling listen() or getHandler().
Security Configuration
DNS Rebinding Protection
Use allowedOrigins to enable Host header validation and protect against DNS rebinding attacks.
Behavior:
allowedOrigins not set (default) → no host validation, all Host values accepted
allowedOrigins: [] → no host validation (same as not set)
allowedOrigins: ['https://myapp.com'] → Host header must match one of the configured hostnames; applies globally to all routes
// Default: no host validation (development)
const devServer = new MCPServer({
name: 'dev-server',
version: '1.0.0',
})
// Production: restrict to known hostnames
const prodServer = new MCPServer({
name: 'prod-server',
version: '1.0.0',
allowedOrigins: [
'https://myapp.com',
'https://app.myapp.com',
],
})
// Load from environment
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
allowedOrigins: process.env.ALLOWED_ORIGINS?.split(','),
})
allowedOrigins accepts full URLs (e.g. "https://myapp.com") and normalizes them to hostnames for validation.
Session Timeout
Control how long idle sessions are kept in memory:
const server = new MCPServer({
name: 'my-server',
version: '1.0.0',
sessionIdleTimeoutMs: 3600000, // 1 hour (default: 86400000 = 1 day)
})
Always validate tool inputs using Zod schemas. The schema is enforced automatically before your handler runs:
import { error, text } from 'mcp-use/server'
import { z } from 'zod'
server.tool({
name: 'process_input',
schema: z.object({
email: z.string().email().describe('Email address'),
url: z.string().url().describe('URL to process'),
}),
}, async ({ email, url }) => {
// Inputs are already validated by Zod at this point
return text(`Processing ${email} and ${url}`)
})
Next Steps