进阶主题

捕获 MCP 日志和宽事件

使用 useMcpLogger() 将日志流式传递给 MCP 客户端,并捕获结构化的宽事件

useMcpLogger() 暴露了两个面向不同受众的通道:

ChannelGoes toAPI
Client终端用户 / 代理 UI(Cursor、Claude、Inspector)log.notify(level, data) + .debug / .info / .warning / .error
Server你的开发终端以及 drains(Axiom、Sentry、OTLP,…)log.set / log.event / log.setUser / log.setSession / log.evlog

客户端通道始终可用。服务器通道需要 evlog/nuxt

为我的 Nuxt MCP 服务器添加 useMcpLogger 和 evlog 风格的 wide events

设置

客户端通知开箱即用。对于服务端 wide events,请安装 evlog 并注册 evlog/nuxt

pnpm add evlog
nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt', '@nuxtjs/mcp-toolkit'],
  evlog: {
    env: { service: 'my-app' },
  },
})

就是这样。你可以通过同一个 evlog: { … } 块来配置 drains、采样、脱敏和排除模式——完整 schema 请参阅 evlog 文档

自动标记字段

每个 MCP wide event 在进入你的 drain 时都会预先标记:

FieldSource
mcp.transport / mcp.route / mcp.session_id / mcp.method / mcp.request_idTransport headers
mcp.tool / mcp.resource / mcp.promptJSON-RPC payload
user.id / user.email / user.nameevent.context.user(来自你的认证中间件——better-auth、API key,…)
session.idevent.context.session.id
service<evlog.env.service>/mcp(或 slug 化后的 mcp.name)——会自动注入到 /mcp/mcp/**

批量 JSON-RPC 调用会将单数键(methodtool、…)切换为复数数组(methodstools、…)。

覆盖自动 service

将你自己的值固定下来,以退出派生的 <service>/mcp

nuxt.config.ts
evlog: {
  env: { service: 'my-app' },
  routes: {
    '/mcp': { service: 'my-app/agents' },
    '/mcp/**': { service: 'my-app/agents' },
  },
}

强制开启 / 选择退出

mcp.logging行为
undefined(默认)如果注册了 evlog/nuxt 则开启,否则关闭。
true断言已注册 evlog/nuxt。如果没有注册,构建会抛错。
false选择退出。log.notify(...) 仍然可用。

使用

useMcpLogger() 会自动导入。请在工具、资源或 prompt 处理器内部调用它:

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

export default defineMcpTool({
  name: 'charge_card',
  inputSchema: { userId: z.string(), amount: z.number().int() },
  handler: async ({ userId, amount }) => {
    const log = useMcpLogger('billing')

    log.set({ billing: { amount } })
    await log.notify.info({ msg: '开始扣款', amount })

    try {
      const receipt = await charge(userId, amount)
      log.event('charge_completed', { receiptId: receipt.id })
      return `已扣款 ${amount}`
    }
    catch (err) {
      log.evlog.error('扣款失败', err)
      await log.notify.error({ msg: '扣款失败', error: String(err) })
      throw err
    }
  },
})

在你的开发终端中:

INFO [my-app/mcp] POST /mcp 200 in 18ms
├─ mcp: transport=streamable-http route=/mcp method=tools/call tool=charge_card
├─ user: id=user-42 email=alice@example.com
├─ billing: amount=1000
└─ requestLogs: 0={"level":"info","message":"charge_completed",...}

API

MethodChannelDescription
notify(level, data, logger?)client发送一个 notifications/message。如果被 logging/setLevel 过滤掉,则静默丢弃。
notify.debug / info / warning / errorclient级别快捷方式。
set(fields)server将字段合并到 wide event 中。
event(name, fields?)server向 wide event 的 requestLogs 追加一个离散事件。
setUser({ id, email, name })server标记规范化的 user schema(会从 event.context.user 自动填充)。
setSession({ id })server标记 session.id(会从 event.context.session 自动填充)。
evlogserver底层的 RequestLogger,用于 forkerrorgetContext,…

log.notify 始终会 resolve,且不会抛错——适合用于高频热路径。若未注册 evlog/nuxt,服务器端方法会抛出 McpObservabilityNotEnabledError

Drains

通过一个 Nitro 插件将每个 MCP wide event 发送到 Axiom、Sentry、OTLP、HyperDX、Datadog、Better Stack 或 PostHog

server/plugins/evlog-axiom.ts
import { createAxiomDrain } from 'evlog/adapters/axiom'

export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
})
.env
NUXT_AXIOM_TOKEN=xaat-...
NUXT_AXIOM_DATASET=mcp-server

这个 hook 是可叠加的——可以并行注册多个 drains。自定义 drain 只是注册到同一个 hook 上的 (ctx) => Promise<void>

有关全部适配器、采样、脱敏以及如何构建自定义 drains,请参阅 evlog 文档
useMcpLogger() 需要 nitro.experimental.asyncContext: true(自 Nuxt 3.8+ 起默认为 true)。

下一步

  • 中间件 — 在你的处理器中间件里捕获 requestId、耗时和工具名称。
  • 会话 — 自动为每个 wide event 标记 session.id
  • 认证 — 软认证会在每个事件上填充 user.id / user.email
  • evlog 文档 — drains、采样、脱敏和自定义适配器。