diff --git a/sdk/server/index.mdx b/sdk/server/index.mdx
new file mode 100644
index 0000000..a20545b
--- /dev/null
+++ b/sdk/server/index.mdx
@@ -0,0 +1,261 @@
+---
+title: "Server SDK"
+description: "Build MCP servers with the Smithery SDK"
+---
+
+The Smithery SDK provides two server patterns for building MCP servers: stateful and stateless. Choose based on your use case.
+
+## Server Types
+
+
+
+ Maintains session state between requests
+
+
+ Simple servers without session management
+
+
+ Built-in session stores and patterns
+
+
+
+## Quick Comparison
+
+| Feature | Stateful | Stateless |
+|---------|----------|-----------|
+| Session persistence | ✅ Yes | ❌ No |
+| Memory usage | Higher | Lower |
+| Complexity | Medium | Low |
+| Use case | Chat, workflows | Simple tools |
+
+## Basic Server Example
+
+### Stateless Server (Simple)
+
+```typescript
+// The SDK does not provide a helper; see /sdk/server/stateless for a pattern
+import { Server } from "@modelcontextprotocol/sdk/server/index.js"
+
+function createMcpServer({ config }) {
+ const server = new Server({
+ name: "simple-server",
+ version: "1.0.0"
+ })
+
+ // Add your tools here
+
+ return server
+}
+
+// See the stateless pattern implementation: /sdk/server/stateless
+// Example returns an Express app you can .listen() on
+```
+
+### Stateful Server (With Sessions)
+
+```typescript
+import { createStatefulServer } from "@smithery/sdk"
+import { Server } from "@modelcontextprotocol/sdk/server/index.js"
+
+function createMcpServer({ sessionId, config }) {
+ const server = new Server({
+ name: "stateful-server",
+ version: "1.0.0"
+ })
+
+ // Session-specific state
+ const sessionData = {
+ id: sessionId,
+ history: [],
+ context: {}
+ }
+
+ // Add session-aware tools
+
+ return server
+}
+
+const { app } = createStatefulServer(createMcpServer)
+app.listen(process.env.PORT || 3000)
+```
+
+## Common Patterns
+
+### 1. Tool Registration
+
+```typescript
+import {
+ CallToolRequestSchema,
+ ListToolsRequestSchema,
+} from "@modelcontextprotocol/sdk/types.js"
+
+function setupTools(server: Server) {
+ // List available tools
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
+ tools: [
+ {
+ name: "get_data",
+ description: "Retrieve data from database",
+ inputSchema: {
+ type: "object",
+ properties: {
+ query: { type: "string" }
+ },
+ required: ["query"]
+ }
+ }
+ ]
+ }))
+
+ // Handle tool calls
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const { name, arguments: args } = request.params
+
+ switch (name) {
+ case "get_data":
+ const result = await queryDatabase(args.query)
+ return {
+ content: [{
+ type: "text",
+ text: JSON.stringify(result)
+ }]
+ }
+ default:
+ throw new Error(`Unknown tool: ${name}`)
+ }
+ })
+}
+```
+
+### 2. Resource Management
+
+```typescript
+import {
+ ListResourcesRequestSchema,
+ ReadResourceRequestSchema,
+} from "@modelcontextprotocol/sdk/types.js"
+
+function setupResources(server: Server) {
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
+ resources: [
+ {
+ uri: "config://settings",
+ name: "Application Settings",
+ mimeType: "application/json"
+ }
+ ]
+ }))
+
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
+ if (request.params.uri === "config://settings") {
+ return {
+ contents: [{
+ uri: "config://settings",
+ mimeType: "application/json",
+ text: JSON.stringify(getSettings())
+ }]
+ }
+ }
+ throw new Error("Resource not found")
+ })
+}
+```
+
+### 3. Error Handling
+
+```typescript
+import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"
+
+function createSafeHandler(handler: Function) {
+ return async (request: any) => {
+ try {
+ return await handler(request)
+ } catch (error) {
+ if (error instanceof McpError) {
+ throw error
+ }
+
+ // Convert to MCP error
+ throw new McpError(
+ ErrorCode.InternalError,
+ error.message || "Internal server error"
+ )
+ }
+ }
+}
+
+// Use in server setup
+server.setRequestHandler(
+ CallToolRequestSchema,
+ createSafeHandler(toolHandler)
+)
+```
+
+## Configuration
+
+Servers can accept configuration through:
+
+1. **Environment variables** - For secrets and deployment config
+2. **Config schemas** - For validated user configuration
+3. **Runtime options** - For session-specific settings
+
+```typescript
+import { z } from "zod"
+
+// Define configuration schema
+export const configSchema = z.object({
+ apiEndpoint: z.string().url().describe("API endpoint URL"),
+ apiKey: z.string().describe("API authentication key"),
+ timeout: z.number().default(30000).describe("Request timeout in ms"),
+ features: z.object({
+ caching: z.boolean().default(true),
+ rateLimit: z.number().default(100)
+ }).optional()
+})
+
+// Use in server
+const app = createStatefulServer(createMcpServer, {
+ schema: configSchema
+})
+```
+
+## Deployment Considerations
+
+### Memory Management
+
+- **Stateless**: Each request creates a new server instance
+- **Stateful**: Server instances persist per session
+- Use session stores to limit memory usage
+- Implement cleanup for long-running servers
+
+### Scaling
+
+- **Horizontal scaling**: Stateless servers scale easily
+- **Session affinity**: Required for stateful servers
+- **Connection limits**: Set based on available resources
+
+### Security
+
+- Validate all inputs with Zod schemas
+- Sanitize configuration values
+- Use API keys for authentication
+- Implement rate limiting
+
+## Next Steps
+
+- Learn about [stateful servers](/sdk/server/stateful)
+- Explore [stateless patterns](/sdk/server/stateless)
+- Implement [session management](/sdk/server/sessions)
+- Add [configuration validation](/sdk/configuration)
\ No newline at end of file
diff --git a/sdk/server/sessions.mdx b/sdk/server/sessions.mdx
new file mode 100644
index 0000000..6140581
--- /dev/null
+++ b/sdk/server/sessions.mdx
@@ -0,0 +1,448 @@
+---
+title: "Session Management"
+description: "Manage sessions and state in stateful MCP servers"
+---
+
+Session management is crucial for stateful MCP servers. The SDK provides built-in session stores and patterns for managing server instances across multiple client connections.
+
+## Session Store Interface
+
+All session stores implement this interface:
+
+```typescript
+interface SessionStore {
+ // Retrieve existing transport (or undefined)
+ get(id: string): T | undefined
+
+ // Insert or update transport
+ set(id: string, transport: T): void
+
+ // Optional: explicit eviction
+ delete?(id: string): void
+}
+```
+
+## Built-in LRU Store
+
+The SDK includes `createLRUStore` for simple session management:
+
+### Basic Usage
+
+```typescript
+import { createLRUStore, createStatefulServer } from "@smithery/sdk"
+
+// Create store with max 1000 sessions
+const sessionStore = createLRUStore(1000)
+
+const app = createStatefulServer(createMcpServer, {
+ sessionStore
+})
+```
+
+### How LRU Works
+
+```typescript
+const store = createLRUStore(3) // Max 3 sessions
+
+// Add sessions
+store.set("user1", transport1) // [user1]
+store.set("user2", transport2) // [user1, user2]
+store.set("user3", transport3) // [user1, user2, user3]
+
+// Access user1 (moves to end)
+store.get("user1") // [user2, user3, user1]
+
+// Add user4 (evicts user2)
+store.set("user4", transport4) // [user3, user1, user4]
+```
+
+### LRU Implementation Details
+
+```typescript
+export const createLRUStore = (
+ max = 1000
+): SessionStore => {
+ const cache = new Map()
+
+ return {
+ get: (id) => {
+ const transport = cache.get(id)
+ if (!transport) return undefined
+
+ // Refresh position
+ cache.delete(id)
+ cache.set(id, transport)
+ return transport
+ },
+
+ set: (id, transport) => {
+ if (cache.has(id)) {
+ // Update existing
+ cache.delete(id)
+ } else if (cache.size >= max) {
+ // Evict oldest
+ const [lruId, lruTransport] = cache.entries().next().value
+ lruTransport.close?.()
+ cache.delete(lruId)
+ }
+ cache.set(id, transport)
+ },
+
+ delete: (id) => {
+ // Removes entry from the cache (does not close transport)
+ cache.delete(id)
+ }
+ }
+}
+```
+
+## Custom Session Stores
+
+### Redis Session Store
+
+```typescript
+import Redis from "ioredis"
+
+class RedisSessionStore implements SessionStore {
+ constructor(
+ private redis: Redis,
+ private ttl: number = 3600 // 1 hour default
+ ) {}
+
+ async get(id: string): Promise {
+ const data = await this.redis.get(`session:${id}`)
+ if (!data) return undefined
+
+ // Deserialize transport
+ const transport = deserializeTransport(data)
+
+ // Extend TTL on access
+ await this.redis.expire(`session:${id}`, this.ttl)
+
+ return transport
+ }
+
+ async set(id: string, transport: Transport): Promise {
+ const data = serializeTransport(transport)
+ await this.redis.setex(`session:${id}`, this.ttl, data)
+ }
+
+ async delete(id: string): Promise {
+ const transport = await this.get(id)
+ transport?.close?.()
+ await this.redis.del(`session:${id}`)
+ }
+}
+
+// Usage
+const sessionStore = new RedisSessionStore(redis, 7200)
+const app = createStatefulServer(createMcpServer, {
+ sessionStore
+})
+```
+
+### Database Session Store
+
+```typescript
+import { Pool } from "pg"
+
+class PostgresSessionStore implements SessionStore {
+ constructor(private pool: Pool) {}
+
+ async get(id: string): Promise {
+ const client = await this.pool.connect()
+ try {
+ const result = await client.query(
+ 'SELECT data FROM sessions WHERE id = $1 AND expires_at > NOW()',
+ [id]
+ )
+
+ if (result.rows.length === 0) return undefined
+
+ // Update last accessed
+ await client.query(
+ 'UPDATE sessions SET last_accessed = NOW() WHERE id = $1',
+ [id]
+ )
+
+ return deserializeTransport(result.rows[0].data)
+ } finally {
+ client.release()
+ }
+ }
+
+ async set(id: string, transport: Transport): Promise {
+ const client = await this.pool.connect()
+ try {
+ await client.query(`
+ INSERT INTO sessions (id, data, created_at, last_accessed, expires_at)
+ VALUES ($1, $2, NOW(), NOW(), NOW() + INTERVAL '1 hour')
+ ON CONFLICT (id) DO UPDATE
+ SET data = $2, last_accessed = NOW(), expires_at = NOW() + INTERVAL '1 hour'
+ `, [id, serializeTransport(transport)])
+ } finally {
+ client.release()
+ }
+ }
+
+ async delete(id: string): Promise {
+ const transport = await this.get(id)
+ transport?.close?.()
+
+ const client = await this.pool.connect()
+ try {
+ await client.query('DELETE FROM sessions WHERE id = $1', [id])
+ } finally {
+ client.release()
+ }
+ }
+}
+```
+
+### Memory-Mapped Session Store
+
+```typescript
+class MemoryMappedStore implements SessionStore {
+ private sessions = new Map,
+ lastAccessed: Date
+ }>()
+
+ constructor(
+ private maxSessions = 1000,
+ private maxAge = 3600000 // 1 hour
+ ) {
+ // Periodic cleanup
+ setInterval(() => this.cleanup(), 60000)
+ }
+
+ get(id: string): Transport | undefined {
+ const session = this.sessions.get(id)
+ if (!session) return undefined
+
+ // Check if expired
+ if (Date.now() - session.lastAccessed.getTime() > this.maxAge) {
+ this.delete(id)
+ return undefined
+ }
+
+ session.lastAccessed = new Date()
+ return session.transport
+ }
+
+ set(id: string, transport: Transport): void {
+ // Evict if at capacity
+ if (!this.sessions.has(id) && this.sessions.size >= this.maxSessions) {
+ this.evictOldest()
+ }
+
+ this.sessions.set(id, {
+ transport,
+ data: new Map(),
+ lastAccessed: new Date()
+ })
+ }
+
+ delete(id: string): void {
+ const session = this.sessions.get(id)
+ session?.transport.close?.()
+ this.sessions.delete(id)
+ }
+
+ private cleanup(): void {
+ const now = Date.now()
+ for (const [id, session] of this.sessions) {
+ if (now - session.lastAccessed.getTime() > this.maxAge) {
+ this.delete(id)
+ }
+ }
+ }
+
+ private evictOldest(): void {
+ let oldestId: string | null = null
+ let oldestTime = Date.now()
+
+ for (const [id, session] of this.sessions) {
+ if (session.lastAccessed.getTime() < oldestTime) {
+ oldestTime = session.lastAccessed.getTime()
+ oldestId = id
+ }
+ }
+
+ if (oldestId) {
+ this.delete(oldestId)
+ }
+ }
+}
+```
+
+## Session Patterns
+
+### 1. Session with Context
+
+```typescript
+class ContextualSessionStore extends Map {
+ private contexts = new Map()
+
+ setContext(sessionId: string, key: string, value: any) {
+ const ctx = this.contexts.get(sessionId) || {}
+ ctx[key] = value
+ this.contexts.set(sessionId, ctx)
+ }
+
+ getContext(sessionId: string, key: string) {
+ return this.contexts.get(sessionId)?.[key]
+ }
+
+ delete(sessionId: string) {
+ super.delete(sessionId)
+ this.contexts.delete(sessionId)
+ }
+}
+
+// Usage in server
+function createMcpServer({ sessionId, config }) {
+ const store = getSessionStore() // Get injected store
+
+ const server = new Server(/* ... */)
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ if (request.params.name === "set_context") {
+ store.setContext(sessionId, "user", request.params.arguments.user)
+ return { content: [{ type: "text", text: "Context set" }] }
+ }
+
+ if (request.params.name === "get_context") {
+ const user = store.getContext(sessionId, "user")
+ return { content: [{ type: "text", text: `User: ${user}` }] }
+ }
+ })
+
+ return server
+}
+```
+
+### 2. Session Metrics
+
+```typescript
+class MetricsSessionStore implements SessionStore {
+ private base: SessionStore
+ private metrics = {
+ hits: 0,
+ misses: 0,
+ evictions: 0,
+ activeCount: 0
+ }
+
+ constructor(base: SessionStore) {
+ this.base = base
+ }
+
+ get(id: string): Transport | undefined {
+ const result = this.base.get(id)
+ if (result) {
+ this.metrics.hits++
+ } else {
+ this.metrics.misses++
+ }
+ return result
+ }
+
+ set(id: string, transport: Transport): void {
+ const exists = this.base.get(id) !== undefined
+ this.base.set(id, transport)
+ if (!exists) {
+ this.metrics.activeCount++
+ }
+ }
+
+ delete(id: string): void {
+ if (this.base.get(id)) {
+ this.metrics.activeCount--
+ this.metrics.evictions++
+ }
+ this.base.delete?.(id)
+ }
+
+ getMetrics() {
+ return {
+ ...this.metrics,
+ hitRate: this.metrics.hits / (this.metrics.hits + this.metrics.misses)
+ }
+ }
+}
+```
+
+### 3. Distributed Sessions
+
+```typescript
+import { Cluster } from "ioredis"
+
+class ClusteredSessionStore implements SessionStore {
+ private localCache = new Map()
+
+ constructor(
+ private cluster: Cluster,
+ private nodeId: string
+ ) {}
+
+ async get(id: string): Promise {
+ // Check local cache first
+ if (this.localCache.has(id)) {
+ return this.localCache.get(id)
+ }
+
+ // Check cluster
+ const owner = await this.cluster.get(`session-owner:${id}`)
+ if (owner === this.nodeId) {
+ // We should have it locally
+ return undefined
+ }
+
+ // Session exists on another node
+ throw new Error(`Session ${id} is on node ${owner}`)
+ }
+
+ async set(id: string, transport: Transport): Promise {
+ // Claim ownership
+ await this.cluster.set(`session-owner:${id}`, this.nodeId, "EX", 3600)
+ this.localCache.set(id, transport)
+ }
+}
+```
+
+## Best Practices
+
+1. **Choose appropriate limits**: Balance memory usage with user experience
+2. **Implement cleanup**: Remove stale sessions to free resources
+3. **Monitor metrics**: Track hit rates and eviction patterns
+4. **Handle failures gracefully**: Sessions may be lost unexpectedly
+5. **Consider persistence**: For critical applications, use persistent stores
+
+## Performance Tuning
+
+### Memory Optimization
+
+```typescript
+// Tune based on average session size and available memory
+const maxSessions = Math.floor(availableMemory / averageSessionSize * 0.8)
+const sessionStore = createLRUStore(maxSessions)
+```
+
+### TTL Configuration
+
+```typescript
+// Adjust based on usage patterns
+const ttlByUserType = {
+ free: 1800, // 30 minutes
+ premium: 7200, // 2 hours
+ enterprise: 0 // No expiry
+}
+```
+
+## Related
+
+- [Stateful servers](/sdk/server/stateful) - Using session stores
+- [Server patterns](/sdk/server) - Choosing server types
+- [Performance guide](/sdk/guides/performance) - Optimization tips
\ No newline at end of file
diff --git a/sdk/server/stateful.mdx b/sdk/server/stateful.mdx
new file mode 100644
index 0000000..9881c61
--- /dev/null
+++ b/sdk/server/stateful.mdx
@@ -0,0 +1,429 @@
+---
+title: "Stateful Servers (createStatefulServer)"
+description: "Build MCP servers with session persistence and state management"
+---
+
+The `createStatefulServer` function creates MCP servers that maintain state between requests within a session.
+
+## Function Signature
+
+```typescript
+function createStatefulServer>(
+ createMcpServer: CreateServerFn,
+ options?: StatefulServerOptions
+): { app: express.Application }
+```
+
+## Parameters
+
+### `createMcpServer`
+- **Type**: `CreateServerFn`
+- **Required**: Yes
+- **Description**: Factory function called for each new session
+
+```typescript
+type CreateServerFn = (arg: CreateServerArg) => Server
+
+interface CreateServerArg {
+ sessionId: string // Unique session identifier
+ config: T // Validated configuration
+}
+```
+
+### `options`
+- **Type**: `StatefulServerOptions`
+- **Required**: No
+- **Description**: Server configuration options
+
+```typescript
+interface StatefulServerOptions {
+ // Session store for managing active sessions
+ sessionStore?: SessionStore
+
+ // Zod schema for config validation
+ schema?: z.ZodSchema
+
+ // Express app instance (optional)
+ app?: express.Application
+}
+```
+
+## Examples
+
+### Basic Stateful Server
+
+```typescript
+import { createStatefulServer } from "@smithery/sdk"
+import { Server } from "@modelcontextprotocol/sdk/server/index.js"
+
+function createMcpServer({ sessionId, config }) {
+ console.log(`New session: ${sessionId}`)
+
+ // Create session-specific state
+ const messages = []
+ const context = {}
+
+ const server = new Server({
+ name: "chat-server",
+ version: "1.0.0"
+ })
+
+ // Session-aware tool
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ if (request.params.name === "send_message") {
+ const message = request.params.arguments.message
+ messages.push({
+ timestamp: new Date(),
+ content: message
+ })
+
+ return {
+ content: [{
+ type: "text",
+ text: `Message ${messages.length} saved in session ${sessionId}`
+ }]
+ }
+ }
+ })
+
+ return server
+}
+
+const { app } = createStatefulServer(createMcpServer)
+app.listen(3000)
+```
+
+### With Configuration Schema
+
+```typescript
+import { z } from "zod"
+
+const configSchema = z.object({
+ model: z.enum(["gpt-3.5", "gpt-4"]).default("gpt-3.5"),
+ temperature: z.number().min(0).max(2).default(0.7),
+ maxHistory: z.number().default(10)
+})
+
+function createMcpServer({ sessionId, config }) {
+ const server = new Server({
+ name: "ai-assistant",
+ version: "1.0.0"
+ })
+
+ // Use validated config
+ const history = []
+ const maxHistory = config.maxHistory
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ if (request.params.name === "chat") {
+ // Add to history with limit
+ history.push(request.params.arguments.message)
+ if (history.length > maxHistory) {
+ history.shift()
+ }
+
+ // Use configured model and temperature
+ const response = await callAI({
+ model: config.model,
+ temperature: config.temperature,
+ messages: history
+ })
+
+ return {
+ content: [{
+ type: "text",
+ text: response
+ }]
+ }
+ }
+ })
+
+ return server
+}
+
+const { app } = createStatefulServer(createMcpServer, {
+ schema: configSchema
+})
+```
+
+### Custom Session Store
+
+```typescript
+import { createLRUStore } from "@smithery/sdk"
+
+// Create custom store with 100 session limit
+const sessionStore = createLRUStore(100)
+
+// Or implement your own
+class RedisSessionStore {
+ constructor(private redis: RedisClient) {}
+
+ get(id: string) {
+ return this.redis.get(`session:${id}`)
+ }
+
+ set(id: string, transport: Transport) {
+ this.redis.set(`session:${id}`, transport, 'EX', 3600)
+ }
+
+ delete(id: string) {
+ this.redis.del(`session:${id}`)
+ }
+}
+
+const app = createStatefulServer(createMcpServer, {
+ sessionStore: new RedisSessionStore(redis),
+ schema: configSchema
+})
+```
+
+### With Express Middleware
+
+```typescript
+import express from "express"
+import cors from "cors"
+
+// Create Express app with middleware
+const app = express()
+app.use(cors())
+app.use(express.json())
+
+// Health check endpoint
+app.get("/health", (req, res) => {
+ res.json({ status: "ok" })
+})
+
+// Add stateful MCP server
+const { app: mcpApp } = createStatefulServer(createMcpServer, {
+ app, // Use existing Express app
+ schema: configSchema
+})
+
+mcpApp.listen(3000)
+```
+
+## Session Lifecycle
+
+### Session Creation
+
+1. Client connects to `/mcp` endpoint
+2. New session ID generated (UUID)
+3. `createMcpServer` called with session ID
+4. Server instance stored in session store
+
+### Session Usage
+
+```typescript
+function createMcpServer({ sessionId, config }) {
+ // Session state persists between requests
+ const state = {
+ startTime: new Date(),
+ requestCount: 0,
+ data: new Map()
+ }
+
+ const server = new Server({
+ name: "stateful-demo",
+ version: "1.0.0"
+ })
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ state.requestCount++
+
+ if (request.params.name === "get_stats") {
+ return {
+ content: [{
+ type: "text",
+ text: JSON.stringify({
+ sessionId,
+ uptime: Date.now() - state.startTime.getTime(),
+ requests: state.requestCount
+ })
+ }]
+ }
+ }
+ })
+
+ return server
+}
+```
+
+### Configuration Discovery Endpoint
+
+Stateful servers expose a configuration discovery endpoint for clients to retrieve the JSON Schema describing expected configuration:
+
+```text
+GET /.well-known/mcp-config
+Content-Type: application/schema+json; charset=utf-8
+```
+
+- Returns a JSON Schema reflecting the `schema` passed to `createStatefulServer`
+- Includes metadata such as `x-mcp-version` and `x-query-style`
+- Useful for tooling and UI to render config forms
+
+```typescript
+const { app } = createStatefulServer(createMcpServer, { schema: configSchema })
+// Now available at: /.well-known/mcp-config
+```
+
+### Session Cleanup
+
+```typescript
+function createMcpServer({ sessionId, config }) {
+ const server = new Server({
+ name: "cleanup-demo",
+ version: "1.0.0"
+ })
+
+ // Cleanup resources on close
+ server.close = async () => {
+ console.log(`Cleaning up session ${sessionId}`)
+ // Close database connections
+ // Clear timers
+ // Release resources
+ }
+
+ return server
+}
+```
+
+## Use Cases
+
+### 1. Conversational AI
+
+```typescript
+function createMcpServer({ sessionId, config }) {
+ const conversation = []
+
+ const server = new Server({
+ name: "chat-bot",
+ version: "1.0.0"
+ })
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ if (request.params.name === "chat") {
+ conversation.push({
+ role: "user",
+ content: request.params.arguments.message
+ })
+
+ const response = await generateResponse(conversation)
+
+ conversation.push({
+ role: "assistant",
+ content: response
+ })
+
+ return {
+ content: [{
+ type: "text",
+ text: response
+ }]
+ }
+ }
+ })
+
+ return server
+}
+```
+
+### 2. Multi-Step Workflows
+
+```typescript
+function createMcpServer({ sessionId, config }) {
+ const workflow = {
+ currentStep: 0,
+ data: {},
+ completed: false
+ }
+
+ const server = new Server({
+ name: "workflow-engine",
+ version: "1.0.0"
+ })
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const { name, arguments: args } = request.params
+
+ switch (name) {
+ case "next_step":
+ workflow.currentStep++
+ workflow.data[`step${workflow.currentStep}`] = args
+
+ if (workflow.currentStep >= 3) {
+ workflow.completed = true
+ return {
+ content: [{
+ type: "text",
+ text: "Workflow completed!"
+ }]
+ }
+ }
+
+ return {
+ content: [{
+ type: "text",
+ text: `Step ${workflow.currentStep} completed`
+ }]
+ }
+ }
+ })
+
+ return server
+}
+```
+
+### 3. Authenticated Sessions
+
+```typescript
+function createMcpServer({ sessionId, config }) {
+ let authenticated = false
+ let userData = null
+
+ const server = new Server({
+ name: "auth-server",
+ version: "1.0.0"
+ })
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const { name, arguments: args } = request.params
+
+ if (name === "login" && !authenticated) {
+ const user = await authenticateUser(args.username, args.password)
+ if (user) {
+ authenticated = true
+ userData = user
+ return {
+ content: [{
+ type: "text",
+ text: "Login successful"
+ }]
+ }
+ }
+ throw new Error("Invalid credentials")
+ }
+
+ if (!authenticated) {
+ throw new Error("Not authenticated")
+ }
+
+ // Authenticated-only tools...
+ })
+
+ return server
+}
+```
+
+## Best Practices
+
+1. **Initialize state in factory**: Create session state in `createMcpServer`
+2. **Avoid global state**: Each session should be isolated
+3. **Clean up resources**: Implement cleanup in server.close()
+4. **Limit session count**: Use appropriate session store limits
+5. **Handle errors gracefully**: Sessions may disconnect unexpectedly
+
+## Related
+
+- [Stateless servers](/sdk/server/stateless) - Simpler alternative
+- [Session management](/sdk/server/sessions) - Session store details
+- [Configuration validation](/sdk/configuration) - Config schemas
\ No newline at end of file
diff --git a/sdk/server/stateless.mdx b/sdk/server/stateless.mdx
new file mode 100644
index 0000000..24b0a45
--- /dev/null
+++ b/sdk/server/stateless.mdx
@@ -0,0 +1,377 @@
+---
+title: "Stateless Servers"
+description: "Build simple MCP servers without session management"
+---
+
+Stateless servers are the simplest way to build MCP servers. Each request creates a new server instance with no persistence between calls.
+
+## When to Use Stateless Servers
+
+Choose stateless servers when:
+- Tools don't need to remember previous interactions
+- You want minimal memory usage
+- Horizontal scaling is important
+- Server logic is purely functional
+
+## Basic Implementation
+
+While the SDK doesn't include a dedicated `createStatelessServer` function, you can easily create one:
+
+```typescript
+import express from "express"
+import { Server } from "@modelcontextprotocol/sdk/server/index.js"
+import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
+
+function createStatelessServer(createMcpServer: (config: any) => Server) {
+ const app = express()
+
+ app.post("/mcp", async (req, res) => {
+ // Parse config from request
+ const config = req.query.config
+ ? JSON.parse(Buffer.from(req.query.config as string, 'base64').toString())
+ : {}
+
+ // Create new server instance for this request
+ const server = createMcpServer({ config })
+
+ // Create transport
+ const transport = new StreamableHTTPServerTransport()
+ await server.connect(transport)
+
+ // Handle the request
+ transport.handleRequest(req, res)
+ })
+
+ return app
+}
+```
+
+## Usage Examples
+
+### Simple Tool Server
+
+```typescript
+function createMcpServer({ config }) {
+ const server = new Server({
+ name: "calculator",
+ version: "1.0.0"
+ }, {
+ capabilities: {
+ tools: {}
+ }
+ })
+
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
+ tools: [
+ {
+ name: "calculate",
+ description: "Perform mathematical calculations",
+ inputSchema: {
+ type: "object",
+ properties: {
+ expression: {
+ type: "string",
+ description: "Math expression to evaluate"
+ }
+ },
+ required: ["expression"]
+ }
+ }
+ ]
+ }))
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ if (request.params.name === "calculate") {
+ const result = evaluateExpression(request.params.arguments.expression)
+ return {
+ content: [{
+ type: "text",
+ text: `Result: ${result}`
+ }]
+ }
+ }
+ })
+
+ return server
+}
+
+const app = createStatelessServer(createMcpServer)
+app.listen(3000)
+```
+
+### API Wrapper Server
+
+```typescript
+import { z } from "zod"
+import axios from "axios"
+
+const configSchema = z.object({
+ apiKey: z.string().describe("API key for weather service"),
+ units: z.enum(["metric", "imperial"]).default("metric")
+})
+
+function createMcpServer({ config }) {
+ const validatedConfig = configSchema.parse(config)
+
+ const server = new Server({
+ name: "weather-api",
+ version: "1.0.0"
+ })
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ if (request.params.name === "get_weather") {
+ const { city } = request.params.arguments
+
+ const response = await axios.get("https://api.weather.com/v1/current", {
+ params: {
+ q: city,
+ units: validatedConfig.units,
+ appid: validatedConfig.apiKey
+ }
+ })
+
+ return {
+ content: [{
+ type: "text",
+ text: JSON.stringify(response.data, null, 2)
+ }]
+ }
+ }
+ })
+
+ return server
+}
+```
+
+### Database Query Server
+
+```typescript
+function createMcpServer({ config }) {
+ const server = new Server({
+ name: "database-query",
+ version: "1.0.0"
+ })
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ if (request.params.name === "query") {
+ // Create new connection for each request
+ const db = await createConnection(config.connectionString)
+
+ try {
+ const results = await db.query(request.params.arguments.sql)
+ return {
+ content: [{
+ type: "text",
+ text: JSON.stringify(results.rows, null, 2)
+ }]
+ }
+ } finally {
+ // Always close connection
+ await db.close()
+ }
+ }
+ })
+
+ return server
+}
+```
+
+## Comparison with Stateful Servers
+
+| Aspect | Stateless | Stateful |
+|--------|-----------|----------|
+| Memory usage | Low - new instance per request | Higher - instances persist |
+| Complexity | Simple - no state management | Complex - session handling |
+| Scaling | Easy - any instance can handle any request | Harder - needs session affinity |
+| Use cases | APIs, calculations, lookups | Chat, workflows, authentication |
+
+## Best Practices
+
+### 1. Keep It Functional
+
+```typescript
+// Good: Pure function
+function createMcpServer({ config }) {
+ const server = new Server(/* ... */)
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ // Process input, return output
+ const result = processData(request.params.arguments)
+ return formatResponse(result)
+ })
+
+ return server
+}
+
+// Avoid: Side effects outside request handling
+let globalCounter = 0 // This won't work as expected!
+```
+
+### 2. Resource Management
+
+```typescript
+function createMcpServer({ config }) {
+ const server = new Server(/* ... */)
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ // Create resources per request
+ const connection = await createConnection()
+
+ try {
+ return await processWithConnection(connection, request)
+ } finally {
+ // Always cleanup
+ await connection.close()
+ }
+ })
+
+ return server
+}
+```
+
+### 3. Configuration Validation
+
+```typescript
+import { z } from "zod"
+
+const configSchema = z.object({
+ endpoint: z.string().url(),
+ timeout: z.number().positive().default(5000),
+ retries: z.number().min(0).max(5).default(3)
+})
+
+function createMcpServer({ config }) {
+ // Validate on each request
+ const validated = configSchema.parse(config)
+
+ const server = new Server(/* ... */)
+ // Use validated config...
+
+ return server
+}
+```
+
+### 4. Error Handling
+
+```typescript
+function createMcpServer({ config }) {
+ const server = new Server(/* ... */)
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ try {
+ return await handleTool(request)
+ } catch (error) {
+ // Log error details
+ console.error(`Error in ${request.params.name}:`, error)
+
+ // Return user-friendly error
+ throw new McpError(
+ ErrorCode.InternalError,
+ "Tool execution failed. Please try again."
+ )
+ }
+ })
+
+ return server
+}
+```
+
+## Performance Considerations
+
+### Connection Pooling
+
+For stateless servers that need database or API connections:
+
+```typescript
+// Create a connection pool outside the server factory
+const pool = new Pool({
+ connectionString: process.env.DATABASE_URL,
+ max: 20
+})
+
+function createMcpServer({ config }) {
+ const server = new Server(/* ... */)
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ // Get connection from pool
+ const client = await pool.connect()
+
+ try {
+ const result = await client.query(request.params.arguments.query)
+ return formatResponse(result)
+ } finally {
+ // Return to pool
+ client.release()
+ }
+ })
+
+ return server
+}
+```
+
+### Caching
+
+Implement request-level caching:
+
+```typescript
+function createMcpServer({ config }) {
+ const server = new Server(/* ... */)
+ const cache = new Map()
+
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const cacheKey = JSON.stringify(request.params)
+
+ // Check cache
+ if (cache.has(cacheKey)) {
+ return cache.get(cacheKey)
+ }
+
+ // Compute result
+ const result = await computeExpensiveOperation(request.params)
+
+ // Cache for this request only
+ cache.set(cacheKey, result)
+
+ return result
+ })
+
+ return server
+}
+```
+
+## Deployment
+
+Stateless servers are ideal for:
+
+1. **Serverless functions** (AWS Lambda, Vercel Functions)
+2. **Container orchestration** (Kubernetes, ECS)
+3. **Load-balanced environments**
+4. **Auto-scaling deployments**
+
+Example Dockerfile:
+
+```dockerfile
+FROM node:18-alpine
+WORKDIR /app
+COPY package*.json ./
+RUN npm ci --only=production
+COPY . .
+EXPOSE 3000
+CMD ["node", "server.js"]
+```
+
+## When to Upgrade to Stateful
+
+Consider switching to stateful servers when you need:
+
+- User authentication that persists
+- Multi-step workflows
+- Conversation history
+- Progressive data collection
+- Session-specific caching
+
+## Related
+
+- [Stateful servers](/sdk/server/stateful) - For session persistence
+- [Server patterns](/sdk/server) - Overview of server types
+- [Configuration](/sdk/configuration) - Config validation patterns
\ No newline at end of file