为 MCP 服务器加上认证与权限控制:一套完整的架构实践
前言
随着大语言模型和 AI Agent 的快速发展,MCP(Model Context Protocol)正在成为 AI 应用连接外部工具的标准协议。然而,当 MCP 服务器暴露出越来越多的工具能力时,一个现实问题浮出水面:如何控制谁能访问哪些工具?
本文以一个实际的 MCP 认证授权系统为例,分享如何为 MCP 工具服务器添加基于 JWT 的用户认证和细粒度的工具级权限控制。项目代码量不大(核心约 400 行),但架构完整、职责清晰,适合作为理解 MCP 服务端开发的入门参考。
项目要解决什么问题
设想这样一个场景:你搭建了一个 MCP 服务器,上面有 20 个工具——文件读写、数据库查询、邮件发送、应用部署等等。你希望:
- 管理员可以创建用户,并为每个用户分配不同的工具访问权限
- AI Agent 连接 MCP 服务器时,需要携带身份凭证(JWT Token)
- 服务器根据身份自动过滤工具列表——用户只能看到和调用被授权的工具
这就是本项目实现的核心能力。
技术栈选型
| 后端 API | FastAPI | 异步高性能,原生支持依赖注入和 OpenAPI 文档 |
| MCP 服务器 | FastMCP | MCP 协议的 Python 服务端实现,支持中间件机制 |
| 管理后台 | Streamlit | Python 原生 Web UI,快速搭建管理界面 |
| 数据库 | MySQL | 成熟的关系型数据库,适合存储用户和权限关系 |
| ORM | SQLModel | 同时承担 ORM 模型和 Pydantic Schema,减少重复代码 |
| 认证 | PyJWT + bcrypt | JWT 无状态认证 + 密码安全哈希 |
整体架构
项目采用三服务分离架构,三个进程通过 HTTP 松耦合连接,各自独立部署和启动:
+———————–+
| Streamlit 管理后台 | 端口 8501
+———–+———–+
|
HTTP (httpx)
|
v
+——————-+ +———————–+ +——————-+
| AI Agent / MCP |–>| FastAPI 后端 API |–>| MySQL 数据库 |
| 客户端 | | 端口 8000 | | (mcp_auth) |
+———+———+ +———–+———–+ +——————-+
| ^
MCP 协议 | HTTP 内部调用
| | (验证权限)
v |
+———+————————-+—+
| FastMCP 服务器 |
| 端口 8001 |
| +————————————+ |
| | PermissionMiddleware | |
| | – on_list_tools: 过滤工具列表 | |
| | – on_call_tool: 鉴权工具调用 | |
| +————————————+ |
+—————————————–+
三个服务各自的职责:
- API 服务(FastAPI):业务逻辑中心,负责用户管理、工具注册、JWT 签发与验证、权限分配
- MCP 服务(FastMCP):协议层,面向 AI Agent 暴露 MCP 协议接口,通过中间件实现权限过滤
- 管理后台(Streamlit):展示层,提供可视化的用户和工具管理界面
这种分离设计的好处是,每个服务都可以独立扩展和部署。比如 MCP 服务器可以部署在网关侧,API 服务可以独立扩容,管理后台只在内部网络使用。
数据模型设计
数据库设计非常简洁,核心只有三张表:
┌──────────┐ ┌──────────────┐ ┌──────────┐
│ User │ │ UserTool │ │ Tool │
├──────────┤ ├──────────────┤ ├──────────┤
│ id │──┐ │ user_id (FK)│ ┌──│ id │
│ username │ └────│ tool_id (FK)│────┘ │ name │
│ password │ └──────────────┘ │ desc │
│ is_active│ │ enabled │
└──────────┘ └──────────┘
- User:用户表,存储用户名、bcrypt 哈希密码、启用状态
- Tool:工具注册表,记录 MCP 工具的名称、描述、是否启用
- UserTool:用户-工具多对多关联表,通过 user_id + tool_id 联合主键实现
用 SQLModel 定义这些模型非常简洁,同一个类既可以作为数据库表模型(table=True),也可以派生出 API 请求/响应的 Schema(table=False):
class Tool(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(max_length=100)
description: str = Field(default="", max_length=500)
enabled: bool = Field(default=True)
class ToolCreate(SQLModel):
"""工具创建请求 Schema"""
name: str
description: str
enabled: bool = True
这种双重角色设计减少了模型定义的重复代码,是 SQLModel 相比单独使用 SQLAlchemy + Pydantic 的一大优势。
JWT 认证:无状态但有实时性
认证流程采用经典的 JWT 方案:
一个有趣的设计权衡是:JWT 本身是无状态的(Token 中已经包含了权限信息),但 MCP 中间件在每次工具调用时仍然会通过 HTTP 请求 API 服务来验证权限。这看起来似乎矛盾,但实际上兼顾了两个需求:
- JWT 中的 tool_ids:减少了基础验证时对数据库的查询
- 实时调用 API 验证:确保管理员修改权限后立即生效,不需要等 Token 过期
# MCP 中间件中的权限验证流程
async def on_list_tools(ctx, call_next):
# 1. 从 HTTP 请求头提取 JWT
token = ctx.request.headers.get("authorization", "")
# 2. 调用 API 服务获取授权工具列表
async with httpx.AsyncClient() as client:
resp = await client.get(
"http://localhost:8000/api/tools",
headers={"Authorization": token}
)
# 3. 过滤工具列表,只返回授权工具
authorized = {t["name"] for t in resp.json()}
tools = [t for t in tools if t.name in authorized]
return await call_next(ctx, tools)
权限中间件:MCP 服务器的核心创新
整个项目最有意思的部分是 MCP 服务器的权限中间件设计。FastMCP 提供了中间件机制,可以在工具列表和工具调用两个环节插入自定义逻辑。
项目实现了 PermissionMiddleware,拦截两个关键事件:
- on_list_tools:当 AI Agent 请求工具列表时,先通过 JWT 获取授权工具集合,过滤后只返回用户有权访问的工具。Agent 甚至不知道其他工具的存在。
- on_call_tool:当 Agent 调用某个工具时,再次验证该工具是否在授权列表中。这是一道安全兜底——即使 Agent 通过某种方式知道了工具名称,没有权限也无法调用。
这种设计遵循了关注点分离原则:权限逻辑以中间件的形式横切关注,不侵入任何工具的实现代码。每个工具的函数只需要专注于自己的业务逻辑,完全不需要关心"谁有权限调用我"这个问题。
# 工具定义完全不需要关心权限
@mcp.tool()
def read_file(path: str) –> str:
"""读取文件内容"""
return f"Contents of {path}"
# 权限控制由中间件统一处理
如果未来需要增加新的权限维度(比如基于时间的访问控制、调用频率限制等),只需要新增中间件,不需要修改任何工具代码。
管理后台:Streamlit 快速搭建
管理后台使用 Streamlit 构建,提供两个核心功能页签:
工具管理:展示所有已注册的工具列表,支持启用/禁用切换,支持添加新工具。
用户管理:展示所有用户(可展开查看详情),支持以下操作:
- 为用户生成 MCP Token
- 通过复选框分配/取消工具权限
- 修改密码、启用/禁用用户
- 创建新用户
Streamlit 的优势在于,纯 Python 就能写出交互式的 Web 界面,不需要写任何前端代码。对于内部管理系统来说,开发效率非常高。
种子数据:模拟真实场景
项目预置了 20 个模拟工具和 3 个角色分明的用户,方便快速体验和测试:
| alice | 数据分析师(只读为主) | 文件读写、数据库查询、邮件查看、日志查看、数据分析 |
| bob | 运维工程师(管理为主) | 应用部署、回滚、日志管理、用户管理、系统配置 |
| charlie | 全栈开发(全面操作) | 文件全套操作、数据库全套操作、邮件收发、日历管理 |
这三个角色的工具权限互有重叠又各有侧重,很好地演示了多对多权限模型的灵活性。
项目目录结构
ai-mcp-auth/
├── admin/
│ └── app.py # Streamlit 管理后台
├── api/
│ └── main.py # FastAPI 后端 API(核心业务逻辑)
├── database.py # 数据库连接与会话管理
├── models.py # SQLModel 数据模型 + API Schema
├── init_db.py # 数据库初始化脚本(建库建表 + 种子数据)
├── mcp_server.py # MCP 工具服务器(带权限中间件)
├── requirements.txt # Python 依赖清单
├── start.bat # 一键启动脚本
└── .env.example # 环境变量模板
核心文件只有 6 个,各司其职,没有过度分层。
数据流:一次完整的工具调用
让我们跟踪一次完整的 MCP 工具调用流程,看看各个组件是如何协作的:
1. AI Agent 携带 JWT Token 连接 MCP 服务器
│
▼
2. PermissionMiddleware.on_list_tools 拦截
│
├── 提取 JWT Token
├── 调用 API 服务: GET /api/tools (带 Token)
├── API 验证 JWT,返回授权工具列表
│
▼
3. 过滤后只返回授权工具给 Agent
│
▼
4. Agent 调用某个工具 (如 read_file)
│
▼
5. PermissionMiddleware.on_call_tool 拦截
│
├── 验证 JWT 有效性
├── 检查 read_file 是否在授权列表中
├── 通过 → 执行工具逻辑
└── 未通过 → 抛出 PermissionError
总结与思考
这个项目虽然规模不大,但完整地覆盖了 MCP 服务器认证授权的几个关键问题:
几个值得分享的设计决策:
- 中间件模式做权限:权限逻辑与工具实现完全解耦,新增工具零成本
- 三服务分离:API、MCP、Admin 各自独立,通过 HTTP 松耦合
- SQLModel 双重角色:一套模型同时服务 ORM 和 API Schema,减少样板代码
- 无状态 + 实时验证:JWT 减少基础查询,实时 API 调用保证权限时效性
如果你正在开发 MCP 服务器并需要认证授权能力,希望这个项目的架构思路能给你一些参考。





