
MCP Server怎么做权限控制?5分钟教会你快速给MCP 服务成功添加授权 原创
2024 年,Anthropic[1] 发布 Claude 3[2] 系列的同时推出了 MCP[3](Model Context Protocol),定位更底层:不是“调函数”,而是“定义模型理解系统的结构协议”。
在企业级应用场景下,对于一些私有环境,MCP服务器可能不需要严格的身份认证。但如果在企业级别进行部署,对这些接口的安全性和权限管理就显得至关重要。MCP服务器可以通过两种方式运行:
- stdio
- http + sse
这两种运行方式的权限控制策略各不相同。在深入讨论MCP服务器的权限控制之前,我们先简单回顾一下MCP的基本原理。
MCP组成和执行流程
MCP 架构分为以下 3 部分:
- 客户端:大模型应用(如 DeepSeek、ChatGPT)发起 MCP 协议请求。
- 服务器端:服务器端响应客户端的请求,并查询自己的业务实现请求处理和结果返回。
运行流程:
- 用户提问 LLM。
- LLM 查询 MCP 服务列表。
- 找到需要调用 MCP 服务,调用 MCP 服务器端。
- MCP 服务器接收到指令。
- 调用对应工具(如数据库)执行。
- 返回结果给 LLM。
stdio模式
在 stdio 这种模式下,mcp server是作为mcp client 的一个子进程运行的。在这种模式下,可以在mcp client 中配置环境变量,mcp server读取环境变量。
环境变量是操作系统为每个进程维护的一组键值对(如
PATH
、PYTHONPATH
等),存储在进程的内存空间中。子进程启动时,会继承其父进程的环境变量(如 Shell 启动的程序会继承 Shell 的环境变量)。
下面是cursor中mcp的配置:
{
"mcpServers": [
{
"name": "my-mcp-server",
"command": "/home/cy/Downloads/anaconda3/envs/deep-searcher/bin/python3",
"args": ["/home/cy/Desktop/git/deep-searcher/mcp_server.py"],
"env": {"token": "value"}
}
]
}
在生产中,我们一般会把mcp server单独部署成一个服务,所以对这个服务的权限验证才是重点。
HTTP + SSE
2025 年 3 月发布的最新 MCP 规范引入了安全基础,借助了广泛使用的 OAuth2 框架[4]。
- 作为资源托管中心,MCP的职责在于审查所有请求中的Authorization头部信息。这个头部信息必须携带一个OAuth2 access_token(代表客户端权限的令牌)。通常情况下,这个令牌是一个JWT(JSON Web Token),或者只是一串无法解读的随机字符。如果令牌丢失或无效(例如无法解析、已经过期、不属于该服务器等),请求将会被驳回。
- 同时,作为授权中心,MCP还需要具备为客户端安全签发access_token的能力。在签发令牌之前,服务器会验证客户端的证明,有时还需要确认访问用户的身份。授权中心会确定令牌的有效时间、权限范围、目标听众等属性。
在我们的内部应用体系中,token 的签发并非由我们的 MCP 服务器完成,而是由其他服务提供。因此,MCP服务器并不完全实现了OAuth2服务器的所有功能,它主要承担着授权校验的职责。为了实现这一目标,我们使用了一个专门处理token相关操作的 JWT类。
# pip install python-jose[cryptography]
from jose import jwt, JWTError
class JWTManager:
def __init__(
self,
secret_key: str,
algorithm: str = "HS256",
access_token_expire_minutes: int = 30,
refresh_token_expire_days: int = 30
):
"""
初始化JWT管理器
Args:
secret_key: 用于签名JWT的密钥
algorithm: 加密算法,默认HS256
access_token_expire_minutes: 访问令牌过期时间(分钟)
refresh_token_expire_days: 刷新令牌过期时间(天)
"""
self.secret_key = secret_key
self.algorithm = algorithm
self.access_token_expire_minutes = access_token_expire_minutes
self.refresh_token_expire_days = refresh_token_expire_days
def create_access_token(
self,
data: Dict[str, Any],
expires_delta: Optional[timedelta] = None
) -> str:
"""
创建访问令牌
Args:
data: 要编码到令牌中的数据
expires_delta: 可选的过期时间增量
Returns:
str: 编码后的JWT令牌
"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=self.access_token_expire_minutes)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
return encoded_jwt
def create_refresh_token(
self,
data: Dict[str, Any],
expires_delta: Optional[timedelta] = None
) -> str:
"""
创建刷新令牌
Args:
data: 要编码到令牌中的数据
expires_delta: 可选的过期时间增量
Returns:
str: 编码后的JWT令牌
"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(days=self.refresh_token_expire_days)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
return encoded_jwt
def verify_token(self, token: str) -> Dict[str, Any]:
"""
验证并解码JWT令牌
Args:
token: 要验证的JWT令牌
Returns:
Dict[str, Any]: 解码后的令牌数据
Raises:
HTTPException: 当令牌无效或过期时
"""
try:
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
return payload
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
def is_token_expired(self, token: str) -> bool:
"""
检查令牌是否过期
Args:
token: 要检查的JWT令牌
Returns:
bool: 如果令牌过期返回True,否则返回False
"""
try:
payload = self.verify_token(token)
exp = payload.get("exp")
if exp isNone:
returnTrue
return datetime.utcnow() > datetime.fromtimestamp(exp)
except HTTPException:
returnTrue
在这个环境下,我们采用了中间件的方法。通过深入研究mcp这个package的源代码,我们了解到其底层实际上还是利用starlette创建的app来提供服务。因此,我们需要对这个app添加自定义的中间件。
class JwtMiddleware(BaseHTTPMiddleware):
def __init__(self, app: ASGIApp, dispatch: DispatchFunction | None = None) -> None:
super().__init__(app, dispatch)
self.jwt_manager = JWTManager(
secret_key="your-secret-key",
access_token_expire_minutes=30,
refresh_token_expire_days=30
)
def verify_token(self, token):
try:
payload = self.jwt_manager.verify_token(token)
return payload
except Exception:
raise
asyncdef dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
token = request.headers.get("Authorization")
ifnot token:
return JSONResponse({"error": "unauthorized"}, status_code=401)
token = token.lstrip("Bearer").strip()
try:
payload = self.verify_token(token)
logging.info(payload)
except Exception as e:
logging.error(e)
return JSONResponse({"error": "unauthorized"}, status_code=401)
else:
response = await call_next(request)
return response
然而,在MCP这个包的源代码中,并没有提供直接添加中间件的入口。因此,我们需要自行进行扩展。为了实现这一目标,我们决定继承原有的FastMCP类来完成这个扩展。
class CustomFastMCP(FastMCP):
def __init__(
self, name: str | None = None, instructions: str | None = None, **settings: Any
):
super().__init__(name, instructions, **settings)
self.app = self.sse_app()
# 添加的方法
def add_middleware(
self,
middleware_class
) -> None:
self.app.add_middleware(middleware_class)
asyncdef run_sse_async(self) -> None:
"""Run the server using SSE transport."""
starlette_app = self.app
config = uvicorn.Config(
starlette_app,
host=self.settings.host,
port=self.settings.port,
log_level=self.settings.log_level.lower(),
)
server = uvicorn.Server(config)
await server.serve()
在此新类中,我们对run_sse_async和init方法进行了重写,并添加了一个新的函数add_middleware来支持中间件的添加。现在,我们可以通过使用这个自定义的CustomFastMCP类来运行MCP server。如果请求头中未带有Authorization或其值错误,系统会返回一个错误信息:
{"error":"unauthorized"}
一旦正确地携带了Authorization,请求将正常处理。
curl -X GET "http://127.0.0.1:8000/sse" \
> -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE3NDg5NDExMzd9.GadUzsaiJYK215KIzl1c3ACQYiwQAmWaheb2o4ZI8d8"
event: endpoint
data: /messages/?session_id=eb18beb2ff4e4e70a61066d74f59a064
: ping - 2025-06-03 08:29:47.996746+00:00
: ping - 2025-06-03 08:30:02.997993+00:00
: ping - 2025-06-03 08:30:17.998910+00:00
总结
至此,我们已成功地为MCP服务加入了认证机制。现在,只有经过授权的用户才能获取我们MCP服务的接入权,进一步确保了我们服务的安全性和管理效率。尽管存在多种实现MCP服务权限认证的方法,但值得注意的是,当前MCP在这个领域的研究还处于草案阶段,我们会继续关注并优化这一块的工作。
参考资料
[1] Anthropic: https://zhida.zhihu.com/search?content_id=256440304&content_type=Article&match_order=1&q=Anthropic&zhida_source=entity
[2] Claude 3: https://zhida.zhihu.com/search?content_id=256440304&content_type=Article&match_order=1&q=Claude+3&zhida_source=entity
[4] OAuth2 框架: https://modelcontextprotocol.info/specification/draft/basic/authorization/
本文转载自AI 博物院 作者:longyunfeigu
