示例

身份验证

使用 Bearer 令牌身份验证保护您的 MCP 端点。

概述

可以使用 Bearer 令牌身份验证来保护 MCP 端点。本指南将展示如何:

  1. 为用户生成和管理 API 密钥
  2. 在 MCP 中间件中验证令牌
  3. 在您的工具中访问用户上下文
  4. 配置带有身份验证的 MCP 客户端
提示
为我的 Nuxt MCP 端点添加身份验证 (@nuxtjs/mcp-toolkit)。

1. 选择身份验证策略:Better Auth API 密钥或自定义令牌验证
2. 使用 defineMcpHandler 和中间件函数创建 server/mcp/index.ts
3. 在中间件中,从 getHeader(event, 'authorization') 或 getHeader(event, 'x-api-key') 读取令牌
4. 验证令牌并使用已验证的用户设置 event.context.user
5. 不要抛出 401 错误 — MCP 客户端将进入 OAuth 发现模式。请改用“温和”的方式
6. 通过 useEvent().context.user 在工具中访问用户上下文
7. 在工具上使用 enabled 守卫,向未验证用户隐藏它们
8. 配置 MCP 客户端以在请求头中发送令牌

文档:https://mcp-toolkit.nuxt.dev/examples/authentication
中间件:https://mcp-toolkit.nuxt.dev/advanced/middleware
重要提示: 对于缺失或无效的身份验证,MCP 中间件不应抛出错误。抛出 401 错误会导致 MCP 客户端进入 OAuth 发现模式,去寻找不存在的 .well-known/oauth-* 端点。相反,请使用一种“温和”的方法:在身份验证成功时设置上下文,否则允许请求继续。

使用 Better Auth API 密钥

如果您正在使用 Better Auth,可以利用内置的 API Key 插件 获得完整的解决方案。

服务器配置

将 API Key 插件添加到您的 Better Auth 配置中:

server/utils/auth.ts
import { betterAuth } from 'better-auth'
import { apiKey } from '@better-auth/api-key'

export const auth = betterAuth({
  // ... 您现有的配置
  plugins: [
    apiKey({
      rateLimit: {
        enabled: false, // 禁用速率限制(如果不需要)
      },
    }),
  ],
})
API Key 插件默认启用了速率限制。请在开发环境中禁用它,或为生产环境配置适当的限制。

客户端配置

添加客户端插件以使用 API 密钥方法:

composables/auth.ts
import { createAuthClient } from 'better-auth/client'
import { apiKeyClient } from '@better-auth/api-key/client'

const client = createAuthClient({
  plugins: [
    apiKeyClient(),
  ],
})

// 创建 API 密钥
const { data } = await client.apiKey.create({ name: 'My MCP Key' })
console.log(data.key) // 保存此密钥 - 仅显示一次!

// 列出 API 密钥
const { data: keys } = await client.apiKey.list()

// 删除 API 密钥
await client.apiKey.delete({ keyId: 'key-id' })

辅助函数

创建一个验证 API 密钥且不抛出错误的辅助函数:

server/utils/auth.ts
export async function getApiKeyUser(event: H3Event) {
  const authHeader = getHeader(event, 'authorization')

  if (!authHeader?.startsWith('Bearer ')) {
    return null
  }

  const key = authHeader.slice(7)
  const result = await auth.api.verifyApiKey({ body: { key } })

  if (!result.valid || !result.key) {
    return null
  }

  const user = await db.query.user.findFirst({
    where: (users, { eq }) => eq(users.id, result.key!.referenceId),
  })

  if (!user) {
    return null
  }

  return { user, apiKey: result.key }
}

带身份验证的 MCP 处理程序

创建一个处理程序,在提供有效 API 密钥时设置用户上下文:

server/mcp/index.ts
export default defineMcpHandler({
  middleware: async (event) => {
    const result = await getApiKeyUser(event)
    if (result) {
      event.context.user = result.user
      event.context.userId = result.user.id
    }
  },
})

此方法:

  • 在身份验证成功时设置 event.context.userevent.context.userId
  • 当未提供有效令牌时,将上下文保留为 undefined
  • 工具必须检查用户上下文,如果未通过身份验证则返回错误

在工具中使用上下文

您的工具可以从 event.context 访问已验证的用户。请始终检查用户是否存在,并在未通过身份验证时返回错误消息:

server/mcp/tools/create-todo.ts
import { z } from 'zod'
import { useEvent, createError } from 'h3'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'create_todo',
  description: 'Create a new todo for the authenticated user',
  inputSchema: {
    title: z.string().describe('The title of the todo'),
    content: z.string().optional().describe('Optional description or content'),
  },
  handler: async ({ title, content }) => {
    const event = useEvent()
    const userId = event.context.userId as string

    if (!userId) {
      throw createError({ statusCode: 401, message: 'Authentication required. Please provide a valid API key.' })
    }

    const [todo] = await db.insert(schema.todos).values({
      title,
      content: content || null,
      userId,
      createdAt: new Date(),
      updatedAt: new Date(),
    }).returning()

    return `Todo created: ${todo.title}`
  },
})
server/mcp/tools/list-todos.ts
import { useEvent, createError } from 'h3'
import { defineMcpTool } from '@nuxtjs/mcp-toolkit/server'

export default defineMcpTool({
  name: 'list_todos',
  description: 'List all todos for the authenticated user',
  inputSchema: {},
  handler: async () => {
    const event = useEvent()
    const userId = event.context.userId as string

    if (!userId) {
      throw createError({ statusCode: 401, message: 'Authentication required. Please provide a valid API key.' })
    }

    const todos = await db.query.todos.findMany({
      where: (todos, { eq }) => eq(todos.userId, userId),
    })

    return todos
  },
})
请记住在 Nuxt 配置中启用 asyncContext 以使用 useEvent()
nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    experimental: {
      asyncContext: true,
    },
  },
})

自定义令牌验证

如果您没有使用 Better Auth,可以实现自己的令牌验证。请记住使用不抛出错误的“温和”方法:

server/utils/auth.ts
import { createHash } from 'node:crypto'

export async function getTokenUser(event: H3Event) {
  const authHeader = getHeader(event, 'authorization')

  if (!authHeader?.startsWith('Bearer ')) {
    return null
  }

  const token = authHeader.slice(7)
  const tokenHash = createHash('sha256').update(token).digest('hex')

  // 在您的数据库中查找令牌
  const apiToken = await db.query.apiTokens.findFirst({
    where: (tokens, { eq }) => eq(tokens.hash, tokenHash),
  })

  if (!apiToken) {
    return null
  }

  // 检查过期时间
  if (apiToken.expiresAt && apiToken.expiresAt < new Date()) {
    return null
  }

  return { userId: apiToken.userId }
}
server/mcp/index.ts
export default defineMcpHandler({
  middleware: async (event) => {
    const result = await getTokenUser(event)
    if (result) {
      event.context.userId = result.userId
    }
  },
})

配置 MCP 客户端

Cursor

将您的 MCP 服务器添加到 .cursor/mcp.json

.cursor/mcp.json
{
  "mcpServers": {
    "my-app": {
      "url": "http://localhost:3000/mcp",
      "headers": {
        "Authorization": "Bearer your-api-key-here"
      }
    }
  }
}

Claude Desktop

添加到您的 Claude Desktop 配置中:

claude_desktop_config.json
{
  "mcpServers": {
    "my-app": {
      "url": "http://localhost:3000/mcp",
      "headers": {
        "Authorization": "Bearer your-api-key-here"
      }
    }
  }
}

其他客户端

大多数 MCP 客户端支持自定义请求头。请查阅您所用客户端的文档以获取确切的配置格式。

TypeScript

为了实现类型安全的上下文,请扩展 H3 事件上下文:

server/types.ts
declare module 'h3' {
  interface H3EventContext {
    user?: {
      id: string
      name: string
      email: string
    }
    userId?: string
  }
}

安全最佳实践

  1. 始终对令牌进行哈希处理 - 在数据库中存储哈希后的令牌,而非明文
  2. 设置过期日期 - API 密钥应设置过期时间以限制暴露风险
  3. 实施速率限制 - 通过每个密钥的请求限制来防止滥用
  4. 允许撤销密钥 - 用户应能够删除已泄露的密钥
  5. 记录密钥使用情况 - 跟踪密钥的使用时间以便进行安全审计

后续步骤