返回市场
MCP认证服务器

MCP认证服务器

作者:SerhiiBorozenets3 星标更新:2025-10-22

项目介绍

MCP Auth

OAuth 2.1 授权用于 Rails 应用程序中的模型上下文协议(MCP)服务器。

什么是 MCP 授权?

模型上下文协议(MCP)是一个开放标准,使AI助手能够安全地连接到外部数据源和工具。MCP Auth 实现了 MCP 授权规范,为 MCP 服务器提供基于 OAuth 2.1 的身份验证。

为什么 MCP 需要 OAuth?

MCP 服务器经常需要访问用户数据或代表用户执行操作。OAuth 2.1 提供了:

  • 委托授权:用户可以授予 MCP 客户端访问权限而不共享凭据。
  • 基于范围的权限:对允许的数据/操作进行细粒度控制。
  • 基于令牌的安全性:短暂的访问令牌并自动刷新。
  • PKCE 保护:增强公共客户端(AI助手)的安全性。

MCP Auth 工作原理

架构概述

┌─────────────────┐         ┌──────────────────┐         ┌─────────────────┐
│                 │         │                  │         │                 │
│   MCP 客户端    │────────▶│  您的 Rails 应用  │◀────────│   最终用户      │
│  (AI 助手)      │         │  (MCP 服务器 +   │         │   (浏览器)      │
│                 │         │   OAuth 服务器)  │         │                 │
└─────────────────┘         └──────────────────┘         └─────────────────┘
      │                             │                             │
      │                             │                             │
      │  1. 发现 OAuth 服务器      │                             │
      │────────────────────────────▶│                             │
      │                             │                             │
      │  2. 请求授权               │                             │
      │────────────────────────────▶│                             │
      │                             │                             │
      │                             │   3. 显示同意屏幕          │
      │                             │────────────────────────────▶│
      │                             │                             │
      │                             │   4. 用户批准              │
      │                             │◀────────────────────────────│
      │                             │                             │
      │  5. 接收授权码             │                             │
      │◀────────────────────────────│                             │
      │                             │                             │
      │  6. 交换令牌                │                             │
      │────────────────────────────▶│                             │
      │                             │                             │
      │  7. 访问 MCP 服务器        │                             │
      │────────────────────────────▶│                             │

OAuth 2.1 流程序列图

sequenceDiagram
    participant Client as MCP 客户端
    participant Server as MCP 服务器
    participant AuthServer as OAuth 服务器
    participant User as 最终用户

    Note over Client,User: 发现阶段
    Client->>Server: 请求无令牌
    Server-->>Client: 401 + WWW-Authenticate 头
    Client->>Server: GET /.well-known/oauth-protected-resource
    Server-->>Client: 受保护资源元数据
    Client->>AuthServer: GET /.well-known/oauth-authorization-server
    AuthServer-->>Client: OAuth 服务器元数据

    Note over Client,User: 授权阶段
    Client->>Client: 生成 PKCE 挑战
    Client->>User: 打开浏览器中的授权 URL
    User->>AuthServer: 授权请求 (+ PKCE 挑战)
    AuthServer->>User: 显示同意屏幕
    User->>AuthServer: 批准访问
    AuthServer-->>User: 重定向带有授权码
    User->>Client: 返回授权码
    
    Note over Client,User: 令牌交换
    Client->>AuthServer: 交换码以获取令牌 (+ PKCE 验证器)
    AuthServer->>AuthServer: 验证 PKCE
    AuthServer-->>Client: 访问令牌 + 刷新令牌

    Note over Client,User: 访问 MCP 资源
    Client->>Server: MCP 请求 + Bearer 令牌
    Server->>Server: 验证令牌受众
    Server-->>Client: MCP 响应

关键安全特性

  1. PKCE (用于代码交换的证明密钥):所有授权流程都需要

    • 防止授权码被拦截
    • 对公共客户端(AI助手)安全
  2. 资源指示符 (RFC 8707):令牌受众绑定

    • 令牌明确绑定到您的 MCP 服务器
    • 防止不同服务之间的令牌滥用
  3. 令牌轮换:OAuth 2.1 要求

    • 每次使用刷新令牌都会轮换
    • 限制被盗令牌的影响
  4. 短期访问令牌:默认 1 小时有效期

    • 减少被盗令牌的安全窗口
    • 通过刷新令牌自动刷新

特性

  • 符合 OAuth 2.1 - 最新草案规范
  • 强制 PKCE - 使用 S256 方法增强安全性
  • 动态客户端注册 - 支持 RFC 7591
  • 资源指示符 - 使用 RFC 8707 进行令牌受众绑定
  • 受保护资源元数据 - 使用 RFC 9728 进行服务器发现
  • 令牌撤销 - 支持 RFC 7009
  • 令牌检查 - 支持 RFC 7662
  • OpenID Connect - 基本 OIDC 发现支持
  • 刷新令牌轮换 - OAuth 2.1 安全要求
  • 完全可配置 - 路径、URL、生命周期和用户数据
  • 美观的同意屏幕 - 自定义 UI

安装

# 添加到 Gemfile
gem 'mcp-auth'

# 安装
bundle install

# 生成迁移和配置
rails generate mcp:auth:install

# 运行迁移
rails db:migrate

快速开始

1. 挂载路由

关键:在 config/routes.rb 的顶部挂载:

Rails.application.routes.draw do
  # 首先挂载 MCP Auth - 在任何通配符路由之前
  mount Mcp::Auth::Engine => '/'
  
  # 然后是其他路由
  devise_for :users
  root to: 'dashboard#index'
  
  # ... 其他路由
end

⚠️ 为什么在顶部? 该 gem 的路由(如 /.well-known/oauth-*/oauth/*)需要在任何通配符路由之前注册,否则会被应用程序的路由拦截。

2. 配置 MCP Auth

编辑 config/initializers/mcp_auth.rb

Mcp::Auth.configure do |config|
  # 用于 JWT 签名的 OAuth 密钥
  config.oauth_secret = ENV.fetch('MCP_HMAC_SECRET', Rails.application.secret_key_base)
  
  # 授权服务器 URL(可选 - 默认与资源服务器相同)
  config.authorization_server_url = ENV.fetch('MCP_AUTHORIZATION_SERVER_URL', nil)
  
  # MCP 服务器路径 - 您的 MCP 服务器挂载的位置
  # 如果您的 MCP 服务器不在 '/mcp',请更改此设置
  config.mcp_server_path = ENV.fetch('MCP_SERVER_PATH', '/mcp')
  
  # MCP 文档 URL(可选)
  # 默认:{mcp_server_path}/docs(例如,/mcp/docs)
  # 可以是路径或完整 URL:
  config.mcp_docs_url = ENV.fetch('MCP_DOCS_URL', nil)
  # 示例:
  #   config.mcp_docs_url = '/docs/mcp-api'
  #   config.mcp_docs_url = 'https://docs.example.com/mcp'
  
  # 令牌生命周期(秒)
  config.access_token_lifetime = 3600        # 1 小时
  config.refresh_token_lifetime = 2_592_000  # 30 天
  config.authorization_code_lifetime = 1800  # 30 分钟

  # 用户数据获取器 - 自定义此设置
  config.fetch_user_data = proc do |data|
    user = User.find(data[:user_id])
    org = Org.find(data[:org_id]) if data[:org_id]
    
    # 返回用户数据 + API 密钥(如果有)
    {
      email: user.email,
      api_key_id: org&.api_key&.id,
      api_key_secret: org&.api_key&.secret
    }
  rescue ActiveRecord::RecordNotFound
    { email: 'unknown@example.com', api_key_id: nil, api_key_secret: nil }
  end

  # 认证方法
  config.current_user_method = :current_user
  config.current_org_method = :current_org
end

3. 确保认证方法

您的 ApplicationController 应该有这些方法:

class ApplicationController < ActionController::Base
  # 对于 Devise 用户,这些方法已经定义
  # 对于自定义认证,实现这些方法:
  
  def current_user
    # 获取当前登录用户的逻辑
    @current_user ||= User.find_by(id: session[:user_id])
  end

  def current_org
    # 获取当前组织的逻辑(如果适用)
    @current_org ||= current_user&.current_org
  end
end

4. 设置环境变量

# .env
MCP_HMAC_SECRET=your_secure_random_string_here
MCP_SERVER_PATH=/mcp               # 或 /api/mcp, /assistant/api 等
MCP_DOCS_URL=/docs/mcp             # 可选

5. 重启服务器

spring stop  # 清除 spring 缓存
rails server

OAuth 端点(自动提供)

MCP Auth 自动提供以下端点:

发现阶段端点

  • GET /.well-known/oauth-protected-resource - RFC 9728 受保护资源元数据
  • GET /.well-known/oauth-authorization-server - RFC 8414 授权服务器元数据
  • GET /.well-known/openid-configuration - OpenID Connect 发现
  • GET /.well-known/jwks.json - JSON Web Key Set(对于 HMAC 是空的)

OAuth 流程端点

  • GET/POST /oauth/authorize - 授权端点(需要 PKCE)
  • POST /oauth/approve - 同意批准端点
  • POST /oauth/token - 令牌端点(授权码、刷新令牌)
  • POST /oauth/register - 动态客户端注册(RFC 7591)
  • POST /oauth/revoke - 令牌撤销(RFC 7009)
  • POST /oauth/introspect - 令牌检查(RFC 7662)
  • GET /oauth/userinfo - OpenID Connect UserInfo 端点

使用

保护您的 MCP 服务器

MCP Auth 自动保护匹配您配置的 mcp_server_path 的路由:

# 如果 mcp_server_path = '/mcp'
# 所有以 /mcp/* 开头的路由都需要 OAuth 令牌

GET /mcp/tools        # 受保护 ✅
GET /mcp/resources    # 受保护 ✅
GET /mcp/prompts      # 受保护 ✅
GET /other/endpoint       # 不受保护 ❌

OAuth 2.1 授权流程

1. 注册客户端(动态注册)

curl -X POST http://localhost:3000/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "My MCP Client",
    "redirect_uris": ["https://client.example.com/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "scope": "mcp:read mcp:write"
  }'

响应:

{
  "client_id": "550e8400-e29b-41d4-a716-446655440000",
  "client_secret": "a1b2c3d4...",
  "client_id_issued_at": 1234567890,
  "client_secret_expires_at": 0,
  "redirect_uris": ["https://client.example.com/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "mcp:read mcp:write"
}

2. 带 PKCE 的授权请求

生成 PKCE 参数:

// 生成 code_verifier(43-128 字符)
const codeVerifier = base64URLEncode(randomBytes(32));

// 生成 code_challenge
const codeChallenge = base64URLEncode(
  sha256(codeVerifier)
);

重定向用户到授权 URL:

GET /oauth/authorize?
  response_type=code&
  client_id=550e8400-e29b-41d4-a716-446655440000&
  redirect_uri=https://client.example.com/callback&
  scope=mcp:read+mcp:write&
  state=random_state_string&
  code_challenge=CODE_CHALLENGE&
  code_challenge_method=S256&
  resource=https://example.com/mcp

用户将看到同意屏幕并批准/拒绝访问。

3. 令牌交换

用户批准后,交换授权码以获取令牌:

curl -X POST https://example.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTHORIZATION_CODE" \
  -d "redirect_uri=https://client.example.com/callback" \
  -d "code_verifier=CODE_VERIFIER" \
  -d "client_id=550e8400-e29b-41d4-a716-446655440000" \
  -d "resource=https://example.com/mcp"

响应:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "a1b2c3d4e5f6...",
  "scope": "mcp:read mcp:write"
}

4. 访问受保护资源

curl https://example.com/mcp/tools \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

5. 刷新令牌

当访问令牌过期时:

curl -X POST https://example.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=a1b2c3d4e5f6..." \
  -d "client_id=550e8400-e29b-41d4-a716-446655440000"

控制器中的辅助方法

在控制器中访问认证数据:

class MyController < ApplicationController
  def index
    if mcp_authenticated?
      user_id = mcp_user_id      # 从令牌获取用户 ID
      org_id = mcp_org_id        # 从令牌获取组织 ID
      email = mcp_email          # 从令牌获取电子邮件
      scope = mcp_scope          # 令牌范围
      api_key = mcp_api_key      # 如果已配置,则获取 API 密钥
      
      # 您的逻辑
      render json: { user_id: user_id, email: email }
    else
      render json: { error: '未经授权' }, status: :unauthorized
    end
  end
end

可用的辅助方法:

  • mcp_authenticated? - 如果请求具有有效令牌则返回 true
  • mcp_user_id - 从令牌获取用户 ID
  • mcp_org_id - 从令牌获取组织 ID
  • mcp_email - 从令牌获取电子邮件
  • mcp_scope - 令牌范围(空格分隔的字符串)
  • mcp_token - 访问令牌本身
  • mcp_api_key - 如果在 fetch_user_data 中配置,则获取 API 密钥

高级配置

自定义 MCP 服务器路径

如果您的 MCP 服务器挂载在不同的路径上:

# config/initializers/mcp_auth.rb
config.mcp_server_path = '/api/v1/assistant'  # 自定义路径

# 您的 MCP 服务器配置
FastMcp.mount_in_rails(
  Rails.application,
  path_prefix: '/api/v1/assistant'  # 必须与 mcp_server_path 匹配
)

自定义文档 URL

指向您的 API 文档:

# 基于路径(将在您的域前缀下)
config.mcp_docs_url = '/docs/mcp-api'

# 指向外部文档的完整 URL
config.mcp_docs_url = 'https://docs.example.com/mcp-api'

# 默认(如果没有设置):{mcp_server_path}/docs
# 示例:/mcp/docs

自定义同意屏幕

自定义 OAuth 同意屏幕以匹配您的品牌:

# 1. 在 config/initializers/mcp_auth.rb 中启用自定义同意视图
config.use_custom_consent_view = true

# 2. 编辑 app/views/mcp/auth/consent.html.erb

视图中可用的变量:

  • @client_name - 请求