为你的mcp server 添加认证功能
使用 python 开发mcp server 非常简单,但是官方文档对于在sse 或者 streamable 服务里如何添加认证写的很不清晰,我们可以非常快速的开发一个mcp server
| from mcp.server.fastmcp import FastMCP
import datetime
mcp = FastMCP("simple-mcp-server")
@mcp.tool(name="获取当前时间", description="获取当前时间")
async def get_current_time() -> str:
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def run():
mcp.run(transport="sse")
if __name__ == "__main__":
run()
|
以上是一个获取当前时间的mcp server ,很简单,只作为演示,但是如果我们不想让所有人都可以访问,需要添加一些认证信息,目前来讲,由于FastMCP 封装的太高级了,不太好添加,官方有提供基于 OAuth 的认证方式,但是有点复杂,我还没有测试成功过,本文介绍一种简单的在header 中添加认证信息。
这里分别演示 SSE 和 streamable 两种类型如何改造,原理差不太多, 通过创建一个自定义的中间件,在请求处理之前获取一下header 中的 Authorization 参数。
SSE 类型
| from mcp.server.fastmcp import FastMCP
import datetime
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
from starlette.requests import Request
import uvicorn
import anyio
mcp = FastMCP("auth-mcp-server")
class VerifyHeaderMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
if request.url.path == "/messages/":
authorization = request.headers.get("authorization")
if not authorization:
return Response("Unauthorized", status_code=401)
else:
# 做一些校验工作
pass
return await call_next(request)
@mcp.tool(name="获取当前时间", description="获取当前时间")
async def get_current_time() -> str:
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
app = mcp.sse_app()
app.add_middleware(VerifyHeaderMiddleware)
async def run():
config = uvicorn.Config(
app,
host="0.0.0.0",
port=8000,
log_level="info",
)
server = uvicorn.Server(config)
await server.serve()
if __name__ == "__main__":
anyio.run(run)
|
定义认证中间件
| class VerifyHeaderMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
...
|
通过这个中间件,我们可以拿到 request 对象,这个对象包含请求的各种信息,其中 request.headers
为请求的headers 信息。通过
| authorization = request.headers.get("authorization")
|
拿到认证信息以后,在做一些自定义的处理,如果认证不通过的话,那么就返回401
自定义app
之前是直接使用
来启动mcp server, 查看源码,
| async def run_sse_async(self, mount_path: str | None = None) -> None:
"""Run the server using SSE transport."""
import uvicorn
starlette_app = self.sse_app(mount_path)
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()
|
创建了一个 Starlette 对象starlette_app,之后再创建 uvicorn.Server
创建一个 server 对象,最后启动服务。
这里我们不能直接使用 mcp.run(transport="sse")
来启动服务,这种方式是没有机会将上面的认证中间件添加进去, 需要自定义 Starlette app
| app = mcp.sse_app()
app.add_middleware(VerifyHeaderMiddleware)
|
通过 app = mcp.sse_app()
拿到 Starlette 对象以后,就可以通过 app.add_middleware(VerifyHeaderMiddleware)
将中间件添加到app中。
启动服务
| async def run():
config = uvicorn.Config(
app,
host="0.0.0.0",
port=8000,
log_level="info",
)
server = uvicorn.Server(config)
await server.serve()
|
最后,通过server = uvicorn.Server(config)
自定义server, 然后就可以正常启动带有认证中间件的 mcp server 了。
streamable 类型
streamable 和 sse 很类似,只是请求的路径不同,我们创建一个带有认证功能的streamable mcp server.
| from mcp.server.fastmcp import FastMCP
import datetime
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
from starlette.requests import Request
import uvicorn
import anyio
mcp = FastMCP("auth-mcp-server")
class VerifyHeaderMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
if request.url.path == "/mcp/":
authorization = request.headers.get("authorization")
if not authorization:
return Response("Unauthorized", status_code=401)
else:
# 做一些校验工作
pass
return await call_next(request)
@mcp.tool(name="获取当前时间", description="获取当前时间")
async def get_current_time() -> str:
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
app = mcp.streamable_http_app()
app.add_middleware(VerifyHeaderMiddleware)
async def run():
config = uvicorn.Config(
app,
host="0.0.0.0",
port=8000,
log_level="info",
)
server = uvicorn.Server(config)
await server.serve()
if __name__ == "__main__":
anyio.run(run)
|
和 sse 就两点不同
- app 初始化不同: sse 通过
app = mcp.sse_app()
初始化, streamable 通过 app = mcp.streamable_http_app()
初始化。
- 路径判断不同: sse 的请求路径为
if request.url.path == "/messages/":
, streamable 是 if request.url.path == "/mcp/":
。
其他的代码都相同。
验证
使用 cherry studio 做个测试

如果在请求头处没有配置 Authorization 参数,会提示错误

配置上请求头则可以正常的添加 mcp server
