Skip to main content
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:
VariableEffectDefault
PORTHTTP server port3000
HOSTBind hostnamelocalhost
MCP_URLFull public base URL (for widget/asset URLs)http://{HOST}:{PORT}
NODE_ENVproduction disables dev features (inspector, type generation)
DEBUGEnables verbose debug logging
CSP_URLSComma-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:
ModeSessionsSSEBest for
StatefulYesYesLong-lived clients, notifications, sampling
StatelessNoNoEdge 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)
npm install redis
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:
npm install redis
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:
  1. Client connects to Server A → SSE stream created, Server A subscribes to mcp:stream:{sessionId} in Redis
  2. Client’s next request hits Server B (load balancer)
  3. Server B sends a notification → publishes to Redis channel
  4. 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)
})

Input Validation

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