在 AI 应用开发中,Model Context Protocol(MCP)已成为连接大模型与外部工具的桥梁。随着 MCP 服务在生产环境中的部署越来越多,鉴权问题也变得至关重要——如何确保只有合法用户才能访问你的 MCP 工具?如何实现细粒度的权限控制?

结合我的项目经验,这篇文章分享一下在 Go 语言中实现 MCP 鉴权的方案。

为什么 MCP 需要鉴权?

简单来说,MCP 是你 AI 系统的"工具箱"——它让 AI 能够调用各种外部工具和资源。如果这个"工具箱"没有任何保护,那相当于你家门钥匙挂在外面,任何人都能进来翻箱倒柜。

常见的鉴权场景包括:

  • 多租户 SaaS 服务:不同用户访问不同的工具和数据
  • 敏感工具保护:如支付、删除、写入等高危操作需要验证
  • 流量控制和审计:知道谁在什么时间调用了什么工具

MCP 官方认证规范

根据 MCP 官方规范,MCP 认证有以下关键要点:

  • 认证是可选的(OPTIONAL),但推荐在生产环境中启用
  • 唯一官方认证方式是 OAuth 2.1,支持 Authorization Code(授权码模式)和 Client Credentials(客户端凭证模式)两种授权类型
  • 客户端必须通过 Authorization: Bearer <access-token> 请求头发送访问令牌
  • 认证仅在 HTTP 传输层实现,STDIO 传输不应使用此规范,而应从环境变量获取凭证
  • 服务器通过 /.well-known/oauth-authorization-server 端点进行元数据发现

⚠️ 注意:MCP 规范没有将 JWT Token 或 API Key 定义为独立的认证方式。JWT 仅可作为 OAuth 2.1 访问令牌的载体格式出现。

Go SDK 的认证支持现状

目前 github.com/mark3labs/mcp-go(v0.48.0)的认证支持情况如下:

能力 支持情况
客户端 OAuth 2.1 支持 ✅ 完整支持(PKCE、元数据发现、动态客户端注册)
服务端内置认证中间件 ❌ 未提供,需自行实现
Authorization 头读取 ✅ 通过 CallToolRequest.Header 可获取

也就是说,服务端认证需要开发者自行实现。下面我们来看几种实战方案。

方案一:基于 OAuth 2.1 的完整认证(推荐)

OAuth 2.1 是 MCP 规范唯一推荐的认证方式,特别适合需要第三方登录和用户授权的场景。

工作原理

  1. 用户通过授权服务器登录(如 Google、GitHub、企业 IDP)
  2. 获取访问令牌(Access Token)和刷新令牌
  3. 携带令牌通过 Authorization: Bearer <token> 访问 MCP 服务
  4. 服务端验证令牌,可调用用户信息端点获取详情

Go 语言实现

第一步:创建 MCP Server 并注册工具

s := server.NewMCPServer("auth-server", "1.0.0",
    server.WithToolHandlerMiddleware(toolAuthMiddleware),
)
s.AddTool(mcp.NewTool("hello", mcp.WithDescription("Say hello")), handleHello)
// 创建 HTTP Server 并用认证中间件包装
httpServer := server.NewStreamableHTTPServer(s)
http.Handle("/mcp", authMiddleware(httpServer))
log.Fatal(http.ListenAndServe(":8080", nil))

第二步:HTTP 认证中间件 + Tool 级别中间件

// HTTP 中间件:拦截请求,验证 Bearer Token 并注入 context
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
        // ... verify token here
        userInfo, err := verifyToken(r.Context(), token) // OIDC 验证
        // ... handle err here
        ctx := context.WithValue(r.Context(), "user", userInfo)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// Tool 级别中间件(可选):类型为 func(ToolHandlerFunc) ToolHandlerFunc
func toolAuthMiddleware(next server.ToolHandlerFunc) server.ToolHandlerFunc {
    return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        // 可在此做额外权限检查(如 Scopes)
        return next(ctx, req)
    }
}

第三步:在 Tool Handler 中获取认证信息

func handleHello(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    userInfo, ok := ctx.Value("user").(*UserInfo)
    if !ok {
        return &mcp.CallToolResult{
            Content: []mcp.Content{&mcp.TextContent{Text: "Unauthorized"}},
            IsError: true,
        }, nil
    }
    return &mcp.CallToolResult{
        Content: []mcp.Content{&mcp.TextContent{
            Text: fmt.Sprintf("Hello, %s!", userInfo.Name),
        }},
    }, nil
}

第四步:OIDC Token 验证

import "github.com/coreos/go-oidc/v3/oidc"

func verifyToken(ctx context.Context, token string) (*UserInfo, error) {
    provider, _ := oidc.NewProvider(ctx, "https://your-org.okta.com")
    verifier := provider.Verifier(&oidc.Config{ClientID: "your-client-id"})
    idToken, err := verifier.Verify(ctx, token) // 验证签名和有效期
    // ... handle err here
    var claims struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }
    idToken.Claims(&claims) // 提取用户信息
    return &UserInfo{Name: claims.Name, Email: claims.Email}, nil
}

适用场景

  • 需要支持第三方登录(Google、GitHub 等)
  • 企业级应用,需要与现有 IDP 集成
  • 需要用户授权特定权限范围
  • MCP 规范唯一推荐的认证方式

优缺点

优点 缺点
MCP 规范唯一推荐,标准化程度最高 实现复杂度最高
安全性最高,支持令牌撤销和刷新 依赖外部授权服务器
用户体验好,支持单点登录(SSO) 需要处理 PKCE、元数据发现等流程

方案二:自定义 Bearer Token 认证

如果暂时不想引入完整的 OAuth 2.1 基础设施,可以自行实现一个轻量级的 Bearer Token 认证。这种方式不属于 MCP 规范定义的标准认证,但可以作为快速上手的过渡方案。

工作原理

  1. 服务端为每个用户/应用生成唯一的 Token
  2. 客户端请求时放在 Authorization: Bearer <token> 头中
  3. 服务端验证 Token 的有效性(查数据库或缓存)

Go 语言实现

// 简单的 Token 验证中间件
func simpleAuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
        user, err := db.FindUserByToken(r.Context(), token) // 查库验证
        // ... handle err here
        ctx := context.WithValue(r.Context(), "user", user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

适用场景

  • 内部服务间调用
  • 开发和测试环境
  • 快速原型验证

优缺点

优点 缺点
实现简单,快速上手 不符合 MCP 规范标准
验证高效(直接查表) Token 难以安全存储和轮换
适合机器对机器通信 权限控制粒度粗,缺乏标准化

方案三:基于环境变量的认证(STDIO 传输)

对于使用 STDIO 传输的本地 MCP 工具,MCP 规范明确指出不应使用 OAuth,而应从环境变量中获取凭证。

工作原理

  1. 客户端启动 MCP 服务时传入环境变量(如 API Key、Token)
  2. MCP 服务从环境变量读取凭证
  3. 在工具处理函数中使用凭证访问受保护资源

Go 语言实现

apiKey := os.Getenv("MCP_API_KEY") // 从环境变量获取凭证
s := server.NewMCPServer("stdio-auth-server", "1.0.0")

// 通过闭包捕获 apiKey,在 Tool 中使用
s.AddTool(mcp.NewTool("query_data",
    mcp.WithDescription("Query protected data"),
    mcp.WithString("query", mcp.Required(), mcp.Description("Search query")),
), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    result, err := queryProtectedAPI(apiKey, req.Params.Arguments)
    if err != nil {
        return &mcp.CallToolResult{
            Content: []mcp.Content{&mcp.TextContent{Text: err.Error()}},
            IsError: true,
        }, nil
    }
    return &mcp.CallToolResult{
        Content: []mcp.Content{&mcp.TextContent{Text: result}},
    }, nil
})

server.ServeStdio(s) // 启动 STDIO 服务

适用场景

  • 本地开发工具(如 IDE 插件)
  • 单用户桌面应用
  • 不需要多用户隔离的场景

优缺点

优点 缺点
实现最简单 仅适用于 STDIO 传输
MCP 规范推荐的 STDIO 认证方式 凭证暴露在进程环境中
无需额外基础设施 不支持多用户权限隔离

三种方案对比

维度 OAuth 2.1(推荐) 自定义 Bearer Token 环境变量
MCP 规范支持 ✅ 唯一官方方式 ❌ 不符合规范 ✅ STDIO 推荐
实现复杂度 中等 简单
安全性 最高
令牌撤销 ✅ 支持 需自行实现 ❌ 需重启进程
第三方登录 ✅ 支持 ❌ 不支持 ❌ 不支持
适合传输层 HTTP(SSE/Streamable) HTTP STDIO
适合场景 SaaS、多租户、企业级 内部工具、快速原型 本地工具、IDE 插件

选型建议

选 OAuth 2.1:生产环境、面向外部用户、需要符合 MCP 规范——这是唯一正确的选择。

选自定义 Bearer Token:内部服务、快速验证阶段,但请注意这不属于 MCP 标准认证,未来可能需要迁移到 OAuth 2.1。

选环境变量:本地 STDIO 工具、IDE 插件等单用户场景。

写在最后

MCP 鉴权没有银弹,关键是匹配你的业务场景和技术团队的能力。需要特别强调的是:MCP 规范唯一推荐的认证方式是 OAuth 2.1,Go SDK(mcp-go)目前提供了完整的客户端 OAuth 支持,但服务端认证中间件需要开发者自行实现。