Sessions
What are Sessions?
By default, the MCP server is stateless — a fresh server instance is created for every request. This works fine for simple request/response tools, but some scenarios require the server to remember context across multiple tool calls.
When sessions are enabled, the server assigns a unique MCP-Session-Id to each client connection. This ID is included in every subsequent request, allowing the server to:
- Maintain state across multiple tool calls within the same conversation
- Enable SSE streaming for real-time server-to-client communication
- Support resumability so clients can reconnect to an existing session
When to Use Sessions
Sessions are useful when your MCP tools need to:
| Use Case | Example |
|---|---|
| Track conversation context | Remember user preferences, language, or prior answers within a session |
| Accumulate data | Build up a shopping cart, a list of notes, or a set of selections across tool calls |
| Multi-step workflows | Guide users through a wizard (e.g. form builder, deployment pipeline) where each step depends on previous inputs |
| Per-session counters | Track API usage, rate limits, or progress within a session |
| Temporary caches | Cache expensive computation results that are only relevant to the current session |
Setup
Enable sessions in your nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@nuxtjs/mcp-toolkit'],
mcp: {
sessions: true,
},
})
You can also configure the session timeout:
export default defineNuxtConfig({
modules: ['@nuxtjs/mcp-toolkit'],
mcp: {
sessions: {
enabled: true,
maxDuration: 60 * 60 * 1000, // 1 hour (default: 30 minutes)
},
},
})
useMcpSession()
The useMcpSession() server utility provides a typed, per-session key-value store. It is auto-imported and backed by unstorage, so it works with any storage driver out of the box.
Typed Session (Recommended)
Define an interface for your session data and pass it as a generic. Keys and values are fully type-checked:
interface CounterSession {
counter: number
}
export default defineMcpTool({
name: 'increment',
description: 'Increment a per-session counter',
handler: async () => {
const session = useMcpSession<CounterSession>()
const count = await session.get('counter') ?? 0
await session.set('counter', count + 1)
return textResult(`Counter: ${count + 1}`)
},
})
TypeScript will enforce that:
session.get('counter')returnsnumber | nullsession.set('counter', 'wrong')is a compile errorsession.get('unknown_key')is a compile error
Untyped Session
Without generics, the store accepts any string key and untyped values:
const session = useMcpSession()
await session.set('key', { any: 'value' })
const data = await session.get('key')
API Reference
| Method | Description |
|---|---|
get(key) | Retrieve a value by key (returns null if missing) |
set(key, value) | Store a value for the given key |
remove(key) | Delete a key from the session |
has(key) | Check if a key exists |
keys() | List all keys in the session |
clear() | Remove all data from the session |
storage | Access the underlying unstorage instance |
All methods except storage are asynchronous and return a Promise.
Examples
Notepad
A pair of tools that let the AI take notes during a conversation and retrieve them later:
import { z } from 'zod'
interface NotesSession {
notes: { text: string, createdAt: string }[]
}
export default defineMcpTool({
name: 'add_note',
description: 'Add a note to the session notepad',
inputSchema: {
note: z.string().describe('The note content'),
},
handler: async ({ note }) => {
const session = useMcpSession<NotesSession>()
const notes = await session.get('notes') ?? []
notes.push({ text: note, createdAt: new Date().toISOString() })
await session.set('notes', notes)
return textResult(`Note added (${notes.length} total).`)
},
})
interface NotesSession {
notes: { text: string, createdAt: string }[]
}
export default defineMcpTool({
name: 'get_notes',
description: 'Retrieve all notes from the session notepad',
handler: async () => {
const session = useMcpSession<NotesSession>()
const notes = await session.get('notes') ?? []
if (notes.length === 0) {
return textResult('No notes yet.')
}
return jsonResult(notes)
},
})
Multi-Step Wizard
Guide the user through a multi-step form where each step depends on the previous one:
import { z } from 'zod'
interface WizardSession {
step: number
projectName: string
framework: string
}
export default defineMcpTool({
name: 'wizard_next',
description: 'Advance to the next step of the project setup wizard',
inputSchema: {
answer: z.string().describe('Answer for the current step'),
},
handler: async ({ answer }) => {
const session = useMcpSession<WizardSession>()
const step = await session.get('step') ?? 1
if (step === 1) {
await session.set('projectName', answer)
await session.set('step', 2)
return textResult(`Project name set to "${answer}". Step 2: Choose a framework (nuxt, next, svelte).`)
}
if (step === 2) {
await session.set('framework', answer)
await session.set('step', 3)
const name = await session.get('projectName')
return textResult(`Creating "${name}" with ${answer}. Setup complete!`)
}
return textResult('Wizard already completed. Use session.clear() to restart.')
},
})
User Preferences
Remember user preferences for the duration of the session:
import { z } from 'zod'
interface PreferencesSession {
language: string
verbose: boolean
}
export default defineMcpTool({
name: 'set_preference',
description: 'Set a user preference for this session',
inputSchema: {
language: z.string().optional().describe('Preferred response language'),
verbose: z.boolean().optional().describe('Enable verbose output'),
},
handler: async ({ language, verbose }) => {
const session = useMcpSession<PreferencesSession>()
if (language) await session.set('language', language)
if (verbose !== undefined) await session.set('verbose', verbose)
return textResult('Preferences updated.')
},
})
Custom Storage Driver
By default, session data is stored in memory. Data is lost when the server restarts, which is fine for development and most use cases.
For production environments where you need persistence or shared state across multiple server instances, configure a different storage backend via the standard Nitro storage config:
export default defineNuxtConfig({
mcp: { sessions: true },
nitro: {
storage: {
'mcp:sessions': {
driver: 'redis',
url: 'redis://localhost:6379',
},
},
},
})
Any unstorage driver can be used: Redis, filesystem, Cloudflare KV, Vercel KV, etc.
Lifecycle and Cleanup
Session data is automatically cleaned up when:
- A session is closed by the client (transport
onclose) - A session expires after
maxDurationof inactivity (default: 30 minutes)
You don't need to manage cleanup manually.
Requirements
useMcpSession() requires:mcp.sessionsto be enabled in your confignitro.experimental.asyncContextto betrue(default since Nuxt 3.8+)