进阶主题

会话 (Sessions)

使用 useMcpSession() 在工具调用之间持久化每个会话的状态。

什么是会话 (Sessions)?

默认情况下,MCP 服务器是无状态的——每次请求都会创建一个全新的服务器实例。这对于简单的请求/响应工具来说没问题,但某些场景要求服务器在多次工具调用之间记住上下文。

提示词
为我的 Nuxt MCP 服务器 (@nuxtjs/mcp-toolkit) 添加会话支持。

- 在 nuxt.config.ts 中启用会话:mcp: { sessions: true }
- 在工具处理程序中使用 useMcpSession<T>()(自动导入)来获取/设置每个会话的状态
- await session.get(key) 和 await session.set(key, value) 可在同一会话的工具调用之间持久化数据
- 为你的会话数据定义一个 TypeScript 接口,并将其作为泛型参数传递给 useMcpSession<MySession>()
- 会话由自动分配的 MCP-Session-Id 请求头标识
- 自定义超时设置:mcp: { sessions: { enabled: true, maxDuration: 3600000 } }
- 会话还支持 SSE 流式传输和可恢复性

文档:https://mcp-toolkit.nuxt.dev/advanced/sessions

启用会话后,服务器会为每个客户端连接分配一个唯一的 MCP-Session-Id。该 ID 会包含在后续的每个请求中,从而使服务器能够:

  • 在同一对话的多次工具调用之间保持状态
  • 启用 SSE 流式传输以实现服务器到客户端的实时通信
  • 支持可恢复性,以便客户端可以重新连接到现有会话

何时使用会话

当你的 MCP 工具需要以下功能时,会话将非常有用:

使用场景示例
跟踪对话上下文在会话中记住用户偏好、语言或之前的回答
累积数据在多次工具调用中构建购物车、笔记列表或一组选项
多步骤工作流引导用户完成向导(例如表单构建器、部署流水线),其中每个步骤都依赖于之前的输入
按会话计数器跟踪会话内的 API 使用量、速率限制或进度
临时缓存缓存仅与当前会话相关的昂贵计算结果
如果你的工具完全是无状态的(例如获取数据、执行计算、读取文件),则不需要会话。仅当跨工具调用的状态能带来实际价值时才启用它们。

设置

在你的 nuxt.config.ts 中启用会话:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/mcp-toolkit'],
  mcp: {
    sessions: true,
  },
})

你还可以配置会话超时时间:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/mcp-toolkit'],
  mcp: {
    sessions: {
      enabled: true,
      maxDuration: 60 * 60 * 1000, // 1 小时(默认:30 分钟)
    },
  },
})

useMcpSession()

useMcpSession() 服务器工具函数提供了一个类型安全的、按会话划分的键值存储。它支持自动导入,并由 unstorage 提供支持,因此开箱即可与任何存储驱动配合使用。

类型化会话(推荐)

为你的会话数据定义一个接口并将其作为泛型传入。键和值将进行完整的类型检查:

server/mcp/tools/counter.ts
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

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 `Counter: ${count + 1}`
  },
})

TypeScript 将强制执行以下规则:

  • session.get('counter') 返回 number | null
  • session.set('counter', 'wrong') 会导致编译错误
  • session.get('unknown_key') 会导致编译错误

非类型化会话

不使用泛型时,存储接受任何字符串键和未类型化的值:

const session = useMcpSession()
await session.set('key', { any: 'value' })
const data = await session.get('key')

API 参考

方法描述
get(key)按键检索值(如果不存在则返回 null
set(key, value)为指定键存储一个值
remove(key)从会话中删除一个键
has(key)检查键是否存在
keys()列出会话中的所有键
clear()移除会话中的所有数据
storage访问底层的 unstorage 实例

storage 外,所有方法均为异步方法并返回一个 Promise

示例

记事本

一对工具,允许 AI 在对话期间做笔记并在稍后检索它们:

server/mcp/tools/add-note.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

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 `Note added (${notes.length} total).`
  },
})
server/mcp/tools/get-notes.ts
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

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 'No notes yet.'
    return notes
  },
})

多步骤向导

引导用户完成多步骤表单,其中每个步骤都依赖于上一步:

server/mcp/tools/wizard.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

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 `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 `Creating "${name}" with ${answer}. Setup complete!`
    }

    return 'Wizard already completed. Use session.clear() to restart.'
  },
})

用户偏好

在会话持续期间记住用户偏好:

server/mcp/tools/set-preference.ts
import { z } from 'zod'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

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 'Preferences updated.'
  },
})

自定义存储驱动

默认情况下,会话数据存储在内存中。服务器重启时数据会丢失,这对于开发和大多数用例来说是可以接受的。

对于需要持久化或在多个服务器实例间共享状态的生产环境,可以通过标准的 Nitro storage 配置来配置不同的存储后端:

nuxt.config.ts
export default defineNuxtConfig({
  mcp: { sessions: true },
  nitro: {
    storage: {
      'mcp:sessions': {
        driver: 'redis',
        url: 'redis://localhost:6379',
      },
    },
  },
})

可以使用任何 unstorage 驱动:Redis、文件系统、Cloudflare KV、Vercel KV 等。

生命周期与清理

会话数据会在以下情况下自动清理

  • 客户端关闭会话(传输层 onclose
  • 会话在 maxDuration 的不活动期后过期(默认:30 分钟)

你无需手动管理清理工作。

要求

useMcpSession() 需要:
  • 在配置中启用 mcp.sessions
  • nitro.experimental.asyncContexttrue(Nuxt 3.8+ 起默认开启)