在 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 规范唯一推荐的认证方式,特别适合需要第三方登录和用户授权的场景。
工作原理
- 用户通过授权服务器登录(如 Google、GitHub、企业 IDP)
- 获取访问令牌(Access Token)和刷新令牌
- 携带令牌通过
Authorization: Bearer <token>访问 MCP 服务 - 服务端验证令牌,可调用用户信息端点获取详情
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 规范定义的标准认证,但可以作为快速上手的过渡方案。
工作原理
- 服务端为每个用户/应用生成唯一的 Token
- 客户端请求时放在
Authorization: Bearer <token>头中 - 服务端验证 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,而应从环境变量中获取凭证。
工作原理
- 客户端启动 MCP 服务时传入环境变量(如 API Key、Token)
- MCP 服务从环境变量读取凭证
- 在工具处理函数中使用凭证访问受保护资源
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 支持,但服务端认证中间件需要开发者自行实现。