☰
FastAPI-MCP 构建自定义 MCP 工具实操指南:从 SSE 到 uv 部署
2026/10/10 22:53:27 网站建设 项目流程

1. 为什么要把 FastAPI 接口改造成 MCP 工具

如果你手里已经有一堆跑得好好的 FastAPI 接口,现在想让大模型直接调用它们,最直接的想法可能是重写一套 MCP Server。但真动手就会发现,参数描述、鉴权、异步并发、部署方式全都要再写一遍,维护两套代码非常痛苦。FastAPI-MCP 解决的正是这个问题:它把已有的 FastAPI 路由自动识别并暴露成 MCP 工具,你几乎不用改业务逻辑,就能让 Claude、Cline、Cherry Studio 这类支持 MCP 协议的客户端调用你的接口。

FastAPI-MCP 是一个基于 Python FastAPI 框架的开源项目,核心能力是自动扫描 FastAPI 的operation_id和路由信息,生成对应的 MCP 工具描述。它继承了 FastAPI 的全部优点:异步高并发、可独立远程部署、自带 OpenAPI 文档。传输层支持 SSE 和 mcp-remote 两种接入方式,也支持设置授权访问,适配各种支持 MCP 协议的客户端。简单说,你写接口的方式不变,只是多挂载了一个 MCP 服务。

这篇内容适合三类人:一是已经有 FastAPI 项目、想快速接入 MCP 的后端开发者;二是想搭建私有 MCP 工具集、又不想从零写协议层的工程师;三是正在用 uv 管理 Python 依赖、希望部署流程干净可复现的团队。下面我会从项目结构、工具注册代码、SSE 传输配置、uv 依赖管理到客户端调用与返回校验,完整跑通一条自定义 MCP 工具链路。整个过程你可以直接复制命令和代码跟做。

需要提前说明的是,MCP 工具的本质是让模型知道「有哪些能力可以调用、参数是什么、返回什么」。FastAPI-MCP 通过operation_id和函数签名自动生成这些元信息,所以接口命名和参数类型标注越清晰,模型调用越准确。这也是为什么后面代码里我会强调operation_id必须显式设置。

2. 用 uv 管理依赖并搭建 FastAPI-MCP 项目结构

先说环境。FastAPI-MCP 要求 Python 3.10 及以上,依赖管理我推荐用 uv,因为它安装快、锁文件清晰、虚拟环境隔离干净。如果你还没装 uv,可以用官方脚本安装,装好后uv --version能输出版本号即可。这里不展开系统级安装细节,重点放在项目初始化。

我习惯的项目结构是这样的,一个app包放业务接口,一个mcp_server.py负责挂载 MCP,根目录放pyproject.toml和uv.lock:

fastapi-mcp-demo/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 实例与路由 │ └── auth.py # 鉴权依赖 ├── mcp_server.py # 挂载 MCP 并启动 ├── pyproject.toml └── uv.lock

初始化命令如下,uv init会生成基础pyproject.toml,然后添加依赖:

uv init fastapi-mcp-demo cd fastapi-mcp-demo uv add fastapi fastapi-mcp uvicorn

执行完uv add后,pyproject.toml里会出现类似下面的依赖声明,版本号以你实际拉到的为准:

[project] name = "fastapi-mcp-demo" version = "0.1.0" requires-python = ">=3.10" dependencies = [ "fastapi>=0.115.0", "fastapi-mcp>=0.3.0", "uvicorn>=0.30.0", ]

这里有个容易踩的坑:fastapi-mcp的版本迭代比较快,早期版本和 0.3 之后的 API 在mount参数上有差异。如果你照着旧教程写mcp.mount("/mcp")报参数错误,先确认版本。用uv tree可以看清实际安装的版本链路。

接下来写业务接口。我在app/main.py里定义两个工具:一个获取当前时间,不需要授权;一个模拟获取用户信息,需要授权。注意每个路由都要显式写operation_id,这是模型理解工具用途的关键。

# app/main.py from datetime import datetime from fastapi import FastAPI, Depends, HTTPException, Header app = FastAPI(title="FastAPI-MCP Demo") async def verify_token(authorization: str | None = Header(None)): valid_tokens = {"123456", "abcdef"} if authorization not in valid_tokens: raise HTTPException(status_code=403, detail="Invalid Token") return True @app.get("/getCurrentTime", operation_id="get_current_time") async def get_current_time(): return {"current_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")} @app.get("/users/{user_id}", operation_id="get_user_info") async def get_user_info(user_id: int, is_auth: bool = Depends(verify_token)): data = { "user_id": user_id, "name": "小狗狗", "sex": "男", "birthday": "2002-07-06", } return data

operation_id用下划线命名,模型读起来更自然。参数类型标注user_id: int会被转成 MCP 工具的输入 schema,模型就知道这里要传整数。鉴权用 FastAPI 的Depends注入,MCP 调用时会带上请求头,逻辑和普通接口完全一致。

3. 挂载 SSE 传输并写出可复制的 MCP 配置

业务接口写好后,新建mcp_server.py,把 FastAPI 实例交给 FastApiMCP,然后挂载。默认挂载路径是/mcp,SSE 传输就绪后客户端通过这个地址建立长连接。

# mcp_server.py import uvicorn from app.main import app from fastapi_mcp import FastApiMCP mcp = FastApiMCP(app) mcp.mount() if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)

启动命令用 uv 运行,保证依赖来自项目锁文件:

uv run python mcp_server.py

启动后你会看到 uvicorn 监听 8000 端口。此时有两套入口同时可用:普通 REST 接口在http://localhost:8000/getCurrentTime,MCP SSE 端点在http://localhost:8000/mcp。Swagger 文档在http://localhost:8000/docs,可以先用它确认接口本身正常。

接下来是客户端配置。以支持 SSE 的 MCP 客户端为例,配置片段如下,注意baseUrl指向/mcp,Authorization头填有效 token:

{ "mcpServers": { "fastapi-mcp": { "name": "fastapi-mcp", "type": "sse", "description": "本地 FastAPI 接口封装的 MCP 工具", "isActive": true, "baseUrl": "http://localhost:8000/mcp", "headers": { "Content-Type": "application/json", "Authorization": "123456" } } } }

这里三件套必须齐全:Base URL 是http://localhost:8000/mcp,Key 是Authorization头里的123456,Model ID 取决于你客户端里选的对话模型,和 MCP 服务本身无关,但调用工具时需要模型支持 function calling。如果你用的是 Cline 或 Claude Code 这类工具,配置字段名可能略有不同,但 Base URL、鉴权头、传输类型这三个信息是一致的。

如果你希望把 MCP 服务部署到远程,让团队共用,可以把uvicorn.run的 host 保持0.0.0.0,然后用反向代理暴露 HTTPS。此时客户端配置里的baseUrl换成你的域名加/mcp。注意 SSE 是长连接,反向代理要关闭缓冲、拉长超时,否则连接会被掐断。

另外,FastAPI-MCP 也支持 mcp-remote 接入方式,适合客户端只支持 stdio 的场景。原理是在本地起一个转发进程,把 stdio 转成 SSE。配置里用npx mcp-remote http://localhost:8000/mcp这类命令即可,具体参数看客户端文档。我实测下来,SSE 直连最省事,mcp-remote 适合临时兼容。

4. 验证请求与返回校验:从 Swagger 到模型链式调用

配置完成后,先做两层验证。第一层验证接口本身,第二层验证 MCP 工具是否被正确识别。

第一层,直接访问 REST 接口。获取时间不需要鉴权:

curl http://localhost:8000/getCurrentTime

返回:

{"current_time": "2025-01-15 14:32:07"}

获取用户信息需要带 token:

curl -H "Authorization: 123456" http://localhost:8000/users/888888

返回:

{"user_id": 888888, "name": "小狗狗", "sex": "男", "birthday": "2002-07-06"}

如果 token 不对,会返回 403 和Invalid Token。这一步确认业务逻辑没问题。

第二层,在 MCP 客户端里刷新工具列表。正常情况下客户端会自动请求/mcp并拉取到两个工具:get_current_time和get_user_info。你可以在对话里直接说「现在几点了」,模型会调用get_current_time并返回时间。再说「帮我查一下用户 888888 的信息」,模型会调用get_user_info,并在请求头带上配置里的Authorization。

更值得演示的是链式调用。你可以问:「帮我看看用户 ID 为 888888 的用户多少岁了」。模型会先调用get_user_info拿到birthday是2002-07-06,然后自己计算年龄。这个过程里,MCP 工具只负责返回原始数据,推理和计算由模型完成。这就是把接口封装成工具的价值:模型按需组合能力,而不是你写死一个「算年龄」的接口。

返回校验要注意两点。一是工具返回必须是 JSON 可序列化的结构,FastAPI 默认会做,但如果你返回了datetime对象或自定义类,要手动转成字符串。二是错误处理,接口抛HTTPException时,MCP 客户端会收到错误信息,模型能感知到调用失败。建议在detail里写清楚原因,比如Invalid Token,方便模型决定是否重试或提示用户。

如果你在客户端里看到工具列表为空,先确认/mcp端点能返回 SSE 事件流,可以用curl -N http://localhost:8000/mcp观察是否有event: endpoint之类的输出。没有输出说明挂载没生效,检查mcp.mount()是否在uvicorn.run之前执行。

5. 常见报错排查:401、local proxy failed 与 reading choices

实际接入时,报错基本集中在鉴权、传输和客户端解析三类。下面按真实错误信息对照排查。

401 Unauthorized 或 403 Invalid Token:这是鉴权头没带上或 token 不对。检查客户端配置里的headers.Authorization是否和verify_token里的有效集合一致。注意有些客户端会把 header 名小写化,FastAPI 的Header(None)默认大小写不敏感,一般没问题。如果你用的是 Bearer 格式,记得在verify_token里去掉Bearer前缀再比对。

local proxy failed / connection refused:SSE 连接建立失败。先确认uvicorn还在运行,端口没被占用。如果客户端和 MCP 服务不在同一台机器,baseUrl不能写localhost,要写实际 IP 或域名。反向代理场景下,检查是否关闭了响应缓冲,Nginx 需要proxy_buffering off;和较长的proxy_read_timeout。

reading choices / unexpected end of JSON input:客户端解析模型返回时出错,通常不是 MCP 服务本身的问题,而是模型输出被截断或格式不合法。排查顺序是:先确认模型支持 function calling;再确认工具返回的 JSON 没有超长字段;最后看客户端日志里模型原始输出。如果工具返回了嵌套很深的 JSON,某些客户端解析会出问题,可以精简返回结构。

OAuth 相关报错:如果你给 MCP 服务加了 OAuth 鉴权,客户端需要走授权码流程。报错通常是invalid_client或redirect_uri mismatch。检查客户端注册的回调地址和服务端配置是否完全一致,包括末尾斜杠。本地调试时,OAuth 回调地址用http://localhost通常可以,但部分客户端要求 HTTPS。

工具列表为空:除了前面说的挂载顺序,还要确认 FastAPI 路由是在FastApiMCP(app)之前注册的。如果你在挂载之后才include_router,新路由不会被扫描到。解决办法是把 MCP 挂载放在所有路由注册之后。

uv 依赖冲突:uv add时如果报版本冲突,先看uv tree找出冲突链路。FastAPI-MCP 对 FastAPI 版本有下限要求,太老的 FastAPI 缺少某些 OpenAPI 字段,会导致工具描述生成不全。升级 FastAPI 到 0.115 以上通常能解决。

排查时我习惯先隔离变量:用 curl 直接打 REST 接口,确认业务正常;再用 curl 打/mcp,确认 SSE 正常;最后才在客户端里调。这样能快速定位是服务端、传输层还是客户端的问题。

6. 长期编码与 Agent 场景下的接入建议

如果你只是临时验证,本地跑通就够了。但如果你打算把 FastAPI-MCP 用在长期编码或 Agent 场景里,有几个点值得提前规划。

第一,工具粒度要控制。不要把几十个接口一股脑全暴露,模型面对太多工具会选错。按业务域拆分多个 MCP 服务,或者用include_operations参数只暴露必要接口。FastAPI-MCP 支持筛选,具体参数看版本文档。

第二,鉴权要可轮换。示例里的valid_tokens是硬编码,生产环境应该接数据库或 JWT。MCP 客户端配置里的 Key 也要能定期更换,避免泄露。如果你用 TaoToken 这类平台做模型接入,API Key 和 MCP 服务的鉴权是两套东西,别混在一起。

第三,部署要独立。MCP 服务建议和业务接口分开部署,因为 SSE 长连接会占用连接数,和普通 REST 请求的资源模型不同。用 uv 的锁文件保证部署环境一致,配合容器化,uv sync --frozen可以精确还原依赖。

第四,日志要可观测。MCP 调用失败时,客户端往往只显示一句「工具调用失败」,具体原因在服务端日志里。建议在verify_token和每个工具函数里加结构化日志,记录operation_id、参数和耗时,方便排查。

如果你需要长期跑编码 Agent,可以考虑用 Coding Plan 这类方案管理模型调用额度,把 MCP 服务和模型接入分开配置。模型对话调试可以用模型对话页面快速验证工具是否被正确调用。接入文档里有完整的 Base URL、Key 和 Model ID 配置说明,照着填即可。

最后说个实用技巧:FastAPI-MCP 生成的工具描述来自路由的summary和description。你可以在装饰器里补上summary="获取当前时间",模型理解会更准。这个细节在工具多了以后特别明显,值得一开始就养成习惯。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询