快速手搓一个MCP服务指南(五):FastMCP 中 Bearer Token 认证的实现与最佳实践

在构建现代 API 服务时,安全认证是不可忽视的关键环节。FastMCP 作为一个强大的工具调用框架,提供了对 Bearer Token 认证的支持,使开发者能够轻松保护 HTTP 端点的安全。本文将深入解析 FastMCP 中 Bearer Token 认证的实现原理、配置方法及最佳实践,帮助你在实际项目中构建安全可靠的认证体系。

认证机制概述

Bearer Token 认证是一种基于令牌的身份验证方式,客户端通过在请求头中携带 Authorization: Bearer <token> 格式的令牌来证明身份。FastMCP 从 2.6.0 版本开始引入这一特性,为 HTTP 传输(httpsse)提供了实用的安全保护方案。

需要注意的是,FastMCP 的 Bearer Token 认证实现并未完全遵循 MCP 规范中的 OAuth 2.1 流程,而是针对服务间通信和编程环境设计的简化方案。这种设计避免了复杂的 OAuth 流程,同时通过非对称加密确保了认证的安全性。

非对称加密策略

FastMCP 采用非对称加密实现令牌验证,这种设计带来了显著的安全优势:

  • 无共享密钥:服务器无需访问私钥或客户端密钥,仅需公钥即可验证令牌
  • 公钥验证机制:支持通过静态公钥或 JWKS 端点获取公钥进行签名验证
  • 安全的令牌发行:令牌由外部服务使用私钥签名,私钥不暴露给 FastMCP 服务器
  • 可扩展架构:多台服务器可独立验证令牌,无需协调密钥

这种设计使得 FastMCP 能够无缝集成到现有认证基础设施中,同时保持严格的安全边界。

核心配置指南

基本配置流程

在 FastMCP 中启用 Bearer Token 认证非常简单,通过 BearerAuthProvider 类即可完成配置。以下是基本的配置示例:

from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider

# 配置认证提供器
auth = BearerAuthProvider(
    jwks_uri="https://my-identity-provider.com/.well-known/jwks.json",
    issuer="https://my-identity-provider.com/",
    audience="my-mcp-server"
)

# 将认证提供器应用到 FastMCP 实例
mcp = FastMCP(name="My MCP Server", auth=auth)

配置参数详解

BearerAuthProvider 支持多种配置参数,满足不同场景的认证需求:

参数名称类型说明
public_keystrPEM 格式的 RSA 公钥,用于静态密钥验证,与 jwks_uri 二选一
jwks_uristrJSON Web Key Set 端点 URL,推荐生产环境使用,支持自动密钥轮换
issuerstr期望的 JWT 签发者(iss)声明值,用于验证令牌发行者
audiencestr期望的 JWT 受众(aud)声明值,验证令牌的目标接收者
required_scopeslist[str]所有请求必需的全局权限范围,未满足将拒绝访问

公钥与 JWKS 配置

静态公钥配置

当拥有 PEM 格式的公钥时,可以直接提供给认证提供器:

from fastmcp.server.auth import BearerAuthProvider
import inspect

# PEM 格式公钥
public_key_pem = inspect.cleandoc("""
    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy...
    -----END PUBLIC KEY-----
""")

# 使用公钥配置认证提供器
auth = BearerAuthProvider(public_key=public_key_pem)
JWKS URI 配置

推荐在生产环境中使用 JWKS URI,以支持密钥自动轮换:

from fastmcp.server.auth import BearerAuthProvider

# 配置 JWKS 端点
provider = BearerAuthProvider(
    jwks_uri="https://idp.example.com/.well-known/jwks.json"
)

令牌生成与管理

开发测试令牌生成

为了便于开发和测试,FastMCP 提供了 RSAKeyPair 工具类用于生成测试令牌:

from fastmcp.server.auth.providers.bearer import RSAKeyPair

# 生成新的密钥对
key_pair = RSAKeyPair.generate()

# 配置认证提供器使用公钥
auth = BearerAuthProvider(
    public_key=key_pair.public_key,
    issuer="https://dev.example.com",
    audience="my-dev-server"
)

# 生成测试令牌
token = key_pair.create_token(
    subject="dev-user",
    issuer="https://dev.example.com",
    audience="my-dev-server",
    scopes=["read", "write"],
    expires_in_seconds=3600  # 令牌有效期1小时
)

print(f"Test token: {token}")

生产环境令牌管理

需要特别注意:RSAKeyPair 仅适用于开发测试环境。在生产环境中,应使用正规的 OAuth 2.1 授权服务器或身份提供商(IdP)来生成令牌,确保安全性和合规性。

访问用户信息与权限控制

获取令牌声明

在认证通过后,工具函数可以通过 get_access_token() 依赖函数获取令牌信息:

from fastmcp import FastMCP, ToolError
from fastmcp.server.dependencies import get_access_token

@mcp.tool async def protected_resource(ctx: Context) -> dict:
    # 获取访问令牌
    access_token = get_access_token()
    
    # 从令牌中获取用户ID(来自JWT的sub或client_id声明)
    user_id = access_token.client_id
    
    # 获取用户权限范围
    user_scopes = access_token.scopes
    
    # 权限检查
    if "data:read_sensitive" not in user_scopes:
        raise ToolError("权限不足,需要 'data:read_sensitive' 权限")
    
    return {
        "user": user_id,
        "message": "访问受保护资源成功",
        "granted_scopes": user_scopes
    }

AccessToken 数据结构

AccessToken 对象包含以下属性,帮助你获取令牌的关键信息:

属性名称类型说明
tokenstr原始 JWT 字符串
client_idstr认证主体标识符
scopeslist[str]授予的权限范围列表
expires_atdatetime令牌过期时间戳(可选)

最佳实践与安全建议

生产环境配置建议

  1. 使用 JWKS 而非静态公钥:JWKS 支持自动密钥轮换,避免因密钥更新导致的服务中断
  2. 严格配置 issuer 和 audience:确保只接受预期的令牌发行者和目标受众
  3. 最小权限原则:通过 required_scopes 配置每个工具所需的最小权限
  4. 短令牌有效期:设置合理的令牌过期时间,建议不超过 1 小时

开发测试注意事项

  1. 绝不将测试密钥用于生产RSAKeyPair 生成的密钥仅用于开发测试
  2. 模拟真实权限场景:在测试时应生成包含不同权限范围的令牌,全面测试权限控制逻辑
  3. 定期更新测试密钥:避免长期使用同一测试密钥,降低安全风险

权限控制策略

  1. 多层级权限检查:在工具函数中进行细粒度的权限检查,而不仅仅依赖全局配置
  2. 动态权限验证:根据不同的操作和资源,验证相应的权限范围
  3. 错误处理优化:提供清晰的权限错误信息,帮助客户端调试

集成案例:构建安全的 API 服务

下面通过一个完整示例,展示如何在 FastMCP 中实现基于 Bearer Token 的安全认证:

from fastmcp import FastMCP, ToolError
from fastmcp.server.auth import BearerAuthProvider
from fastmcp.server.dependencies import get_access_token
from fastmcp.server.auth.providers.bearer import RSAKeyPair
import inspect

# 生成测试用密钥对(生产环境应使用正式的OAuth服务器)
key_pair = RSAKeyPair.generate()

# 配置认证提供器
auth = BearerAuthProvider(
    public_key=key_pair.public_key,
    issuer="https://auth.example.com",
    audience="data-service",
    required_scopes=["data:read"]  # 所有请求必需的全局权限
)

# 初始化FastMCP服务器
mcp = FastMCP(
    name="Secure Data Service",
    auth=auth
)

# 受保护的工具函数:需要特定权限才能访问
@mcp.tool async def get_user_data(user_id: str) -> dict:
    """获取用户数据,需要 data:read 和 data:read:user 权限"""
    access_token = get_access_token()
    
    # 检查是否有必要的权限
    if "data:read" not in access_token.scopes:
        raise ToolError("缺少 data:read 基本权限")
    if f"data:read:user:{user_id}" not in access_token.scopes:
        raise ToolError(f"缺少访问用户 {user_id} 的权限")
    
    # 模拟获取用户数据
    return {
        "user_id": user_id,
        "name": "John Doe",
        "email": "john@example.com",
        "message": "数据访问成功"
    }

# 生成测试令牌(包含必要的权限范围)
test_token = key_pair.create_token(
    subject="user-123",
    issuer="https://auth.example.com",
    audience="data-service",
    scopes=["data:read", "data:read:user:123"],
    expires_in_seconds=3600
)

# 打印测试令牌供客户端使用
print(f"测试令牌: {test_token}")
print("使用方式: Authorization: Bearer <token>")

总结

FastMCP 提供的 Bearer Token 认证机制为 HTTP 端点提供了实用的安全保护方案,通过非对称加密和灵活的配置选项,使开发者能够轻松构建安全的 API 服务。尽管它不完全符合 MCP 规范中的 OAuth 2.1 流程,但在服务间通信和编程环境中表现出色。

在实际应用中,开发者应根据环境选择合适的配置方式:开发测试时使用 RSAKeyPair 工具类,生产环境则集成正规的身份提供商或 OAuth 服务器。同时,遵循最小权限原则和最佳安全实践,确保系统的安全性和可靠性。

通过本文介绍的配置方法和示例,你可以在 FastMCP 中快速实现 Bearer Token 认证,为你的工具服务添加坚实的安全屏障。

<think>我们正在讨论FastMCP如何调用外部MCP服务。根据引用[3]和[5],FastMCP主要是一个用于构建MCP服务器的框架。然而,用户的问题是关于如何调用外部的MCP服务(即作为客户端)。 在MCP架构中(引用[3]): - MCP Host: 使用AI的应用程序(如Claude客户端) - MCP Client: 负责MCP Server通信的组件 - MCP Server: 提供功能接口的组件 FastMCP主要用于构建MCP Server(引用[2][5])。但是,调用外部MCP服务需要扮演MCP Client的角色。 然而,查看引用[5]的示例代码,它只展示了如何创建MCP Server(使用`FastMCP`类,定义工具和资源,然后运行)。并没有直接展示如何作为客户端调用外部服务。 但是,我们注意到引用[3]中提到“MCP Client:MCP Server建立连接的组件,负责处理通信细节”。因此,要调用外部MCP服务,我们需要一个MCP客户端。 由于FastMCP的文档和引用中主要关注服务端,可能没有直接提供客户端库。但我们可以根据MCP协议(一种基于JSON的协议)来实现客户端调用。 步骤: 1. 了解MCP协议:MCP协议通常通过标准输入输出(stdio)或HTTP等传输层进行通信。消息是JSON格式。 2. 作为客户端,我们需要向MCP服务器发送请求并接收响应。 例如,如果我们有一个运行在HTTP上的MCP服务器,我们可以发送HTTP POST请求到服务器的端点,请求体为JSON格式的MCP请求。 但是,引用[5]中的示例使用的是`stdio`传输(即标准输入输出),这意味着它设计用于父进程通过管道通信。在这种情况下,调用外部MCP服务(作为另一个进程)需要以子进程方式启动它,并通过管道进行通信。 然而,用户的问题可能是:在FastMCP构建的服务中,如何调用另一个外部的MCP服务?即我们构建的FastMCP服务器需要作为客户端去调用其他MCP服务器。 由于FastMCP主要关注服务端,它可能没有内置的客户端功能。因此,我们需要自己实现一个MCP客户端。 实现MCP客户端的基本步骤(以HTTP传输为例): 1. 确定外部MCP服务的端点(URL)。 2. 根据MCP协议构造请求(JSON格式)。 3. 发送HTTP POST请求。 4. 解析响应。 但是,如果外部MCP服务使用stdio传输(如引用[5]的示例),则调用方式不同:我们需要启动该外部服务作为子进程,并通过标准输入输出其通信。 由于MCP协议的具体消息格式在引用中没有给出,我们可以参考MCP的官方文档或标准。但根据常见实现一个简单的MCP请求可能如下: 请求: ```json { "id": "unique-request-id", "method": "方法名", "params": { "param1": value1, "param2": value2 } } ``` 响应: ```json { "id": "unique-request-id", "result": ..., "error": null } ``` 因此,如果我们使用HTTP传输,可以这样调用(使用Python的requests库): ```python import requests url = "http://external-mcp-server/endpoint" payload = { "id": "1", "method": "add", # 假设外部服务有一个add方法 "params": { "a": 2, "b": 3 } } response = requests.post(url, json=payload) result = response.json()['result'] ``` 如果是stdio传输,则更复杂,需要管理子进程的输入输出。例如: ```python import subprocess import json # 启动外部MCP服务进程(假设它是一个可以通过命令行启动的程序) process = subprocess.Popen(['python', 'external_mcp_server.py'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True) # 构造请求 request = { "id": "1", "method": "add", "params": { "a": 2, "b": 3 } } # 发送请求 process.stdin.write(json.dumps(request) + '\n') process.stdin.flush() # 获取响应 response_line = process.stdout.readline() response = json.loads(response_line) result = response['result'] ``` 注意:stdio模式下,通常以换行符分隔的JSON消息进行通信。 在FastMCP构建的服务中,如果我们想要调用外部服务,我们可以在FastMCP定义的工具函数中实现上述客户端逻辑。例如: ```python from mcp.server.fastmcp import FastMCP import requests mcp = FastMCP("My Server") @mcp.tool() def call_external_service(a: int, b: int) -> int: """调用外部MCP服务的add方法""" # 假设外部服务是HTTP传输 url = "http://localhost:8000/mcp" payload = { "id": "1", "method": "add", "params": {"a": a, "b": b} } response = requests.post(url, json=payload) if response.status_code == 200: return response.json()['result'] else: raise Exception("调用外部服务失败") # ... 其他工具和资源 ``` 总结:FastMCP本身没有提供调用外部MCP服务的客户端库,但我们可以根据传输方式(HTTP或stdio)使用常规的HTTP客户端或子进程管理来实现。 注意:在实现客户端时,需要了解目标MCP服务的具体协议细节(方法名、参数、传输方式等)。 因此,回答用户问题:FastMCP如何调用外部MCP服务?我们可以通过自行实现MCP客户端(使用HTTP请求或子进程通信)来调用。</think>### FastMCP 调用外部 MCP 服务的技术实现FastMCP 中调用外部 MCP 服务需要遵循 MCP 协议的客户端-服务器架构。以下是实现步骤和技术要点: #### 1. **理解 MCP 架构角色** - **MCP Host**:发起请求的应用程序(如 AI 客户端)[^3] - **MCP Client**:负责连接外部服务的组件(需自行实现) - **MCP Server**:外部服务提供方(已存在) ```mermaid graph LR A[MCP Host] --> B[MCP Client] B --> C[外部 MCP Server] ``` #### 2. **实现 MCP 客户端** FastMCP 主要提供**服务端开发能力**,调用外部服务需自行构建客户端逻辑。核心步骤: ```python import requests import json class MCPClient: def __init__(self, server_url: str): self.base_url = server_url # 外部服务地址,如 http://external-mcp:8000 def call_tool(self, tool_name: str, params: dict) -> dict: """调用外部服务的工具""" payload = { "method": tool_name, "params": params, "id": "req_123" # 唯一请求ID } response = requests.post( f"{self.base_url}/invoke", json=payload, headers={"Content-Type": "application/json"} ) return response.json() ``` #### 3. **在 FastMCP 服务中集成调用** 在 FastMCP 定义的工具中嵌入客户端调用: ```python from mcp.server.fastmcp import FastMCP mcp = FastMCP("GatewayService") # 创建客户端实例 external_client = MCPClient("http://external-service:8000") @mcp.tool() def combined_calculation(a: int, b: int) -> dict: """调用外部服务的加法工具并本地处理""" # 调用外部服务 external_res = external_client.call_tool("add", {"a": a, "b": b}) # 本地处理 local_calc = a * b return { "external_sum": external_res["result"], "local_product": local_calc } ``` #### 4. **通信协议要点 - **传输格式**:JSON-RPC 风格协议[^3] - **典型请求**: ```json { "method": "add", "params": {"a": 5, "b": 3}, "id": "req_2023" } ``` - **响应结构**: ```json { "result": 8, "error": null, "id": "req_2023" } ``` #### 5. **高级场景处理 - **异步调用**:使用 `asyncio` + `aiohttp` 实现非阻塞 - **错误处理**: ```python try: result = external_client.call_tool("divide", {"x": 10, "y": 0}) except MCPError as e: return {"error": f"External service failed: {e.code}"} ``` - **安全认证**:在请求头添加 JWT 或 API Key ```python headers={"Authorization": "Bearer <TOKEN>"} ``` ### 关键注意事项 1. **协议兼容性**:确保外部服务遵循标准 MCP 协议规范 2. **传输层适配**:根据目标服务支持 `HTTP`/`WebSocket`/`stdio`[^5] 3. **超时控制**:设置合理超时(建议 3-5 秒) 4. **服务发现**:复杂系统需集成服务注册中心(如 Consul) > 提示:对于复杂集成场景,建议封装可复用的 MCP Client SDK,统一处理序列化、重试和监控逻辑[^2]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

炼丹上岸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值