背景介绍 在当前大模型蓬勃发展的背景下,专为大模型调用设计的”API
工具”——MCP
,已成为研究领域的焦点。在之前的两个教程中,我们从零开始指导大家实现了MCP Server
和MCP Client
。通过这个过程,相信大家已经意识到MCP Server
本质上是为大模型提供的一种格式化API
接口。
说到这里,很多同学可能会思考:市场上已有海量API资源,但它们大多未被转化为MCP
格式。如果想调用这些API
,还需要重新编写一遍,确实比较繁琐。那么,有没有一种工具能够将我们现有的API
一键转换为MCP
格式呢?
答案是肯定的。今天我们要向大家介绍的正是这样一个项目——FastAPI-MCP
。该项目实现了从FastAPI
到MCP Server
的无缝转换,让我们之前使用FastAPI
开发的接口能够轻松转化为MCP Server
,供大模型直接调用,大大提高了开发效率。
FastAPI-MCP
项目地址:https://github.com/tadata-org/fastapi_mcp
本来这个文章昨天就准备写的,但是经过测试,昨天的FastAPI-MCP
版本为0.3.2
版本,有很多的BUG
,代码无法正常演示。提交Issues
后,作者也是非常给力,今天就升级到了0.3.3
版本,BUG
也修复了。
效果演示 创建工程 首先初始化工程,使用Python3.10
环境,并进入工程。
1 2 uv init fastapi2mcp -p 3.10 cd fastapi2mcp
创建虚拟环境
激活虚拟环境
添加依赖
查看依赖树
到此工程和依赖搭建完成。
FastAPI接口构建 首先我们使用以下代码,将我们前面介绍的天气查询,封装为一个FastAPI
接口:
app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 from fastapi import FastAPIfrom pydantic import BaseModelfrom utils import format_alert, get_weather_from_cityname, get_weather_from_latitude_longitudeimport uvicornapp = FastAPI() class CityName (BaseModel ): cityname: str class CityLatLon (BaseModel ): latitude: float longitude: float class Weather (BaseModel ): cityname: str weather: str temperature: str humidity: str wind_speed: str class WeatherResponse (BaseModel ): code: int weather: Weather | None = None msg: str @app.post("/get_weather/cityname" , response_model=WeatherResponse, operation_id="get_weather_cityname" ) async def get_weather_cityname (city: CityName ): """ 通过城市名称(中国城市使用拼音)获取天气信息 Args: cityname: 城市名称(中国城市使用拼音) Returns: code: 0 成功 -1 失败 weather: 天气信息 msg: 成功或失败信息 """ weather_data = await get_weather_from_cityname(city.cityname) if weather_data: return {"code" : 0 , "weather" : format_alert(weather_data), "msg" : "success" } else : return {"code" : -1 , "msg" : "Failed to fetch weather data" } @app.post("/get_weather/latitude_longitude" , response_model=WeatherResponse, operation_id="get_weather_latitude_longitude" ) async def get_weather_latitude_longitude (citylatlon: CityLatLon ): """ 通过经纬度获取天气信息 Args: latitude: 纬度 longitude: 经度 Returns: code: 0 成功 -1 失败 weather: 天气信息 msg: 成功或失败信息 """ weather_data = await get_weather_from_latitude_longitude(citylatlon.latitude, citylatlon.longitude) if weather_data: return {"code" : 0 , "weather" : format_alert(weather_data), "msg" : "success" } else : return {"code" : -1 , "msg" : "Failed to fetch weather data" } if __name__ == "__main__" : uvicorn.run(app, host='0.0.0.0' , port=8001 )
执行代码
接口成功开启。
使用Postman
测试接口的连通性
接口可以正常返回。
将FastAPI接口转化为MCP Server 接下来,给FastAPI
接口转化为MCP Server
,转化的方式很简单,类似于给FastAPI
打上一个补丁。
在app.py
同级目录下新建脚本app2mcp.py
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from fastapi_mcp import FastApiMCPimport uvicornfrom app import appmcp = FastApiMCP( fastapi=app, name="weather-sse" , description="通过城市名称(中国城市使用拼音)或经纬度获取天气信息" , describe_all_responses=True , describe_full_response_schema=True ) mcp.mount() if __name__ == "__main__" : uvicorn.run(app, host='0.0.0.0' , port=8001 )
直接通过import
语句将app.py
中的FastAPI app
对象导入过来即可。
执行app2mcp.py
脚本。
通过简单的补丁,我们就已经将FastAPI
转化为了MCP Server
。
MCP Server调用 使用Cursor测试MCP Server 在Cursor
中的MCP
配置i文件中写入以下内容。
1 2 3 4 5 6 7 8 { "mcpServers" : { "weather-sse" : { "url" : "http://127.0.0.1:8001/mcp" , "name" : "weather-sse" } } }
保存后,可以看到Cursor
已经成功连接MCP Server
并且服务器终端也有连接请求。
接下来我们在Cursor
对话窗口测试MCP Server
功能
可以看到,可以成功调用。
其他MCP Client
应用(例如:Claude-Desktop
、Cherry Studio
、Cline
等等)的导入类似,在此不再演示,各位可自行测试。
通过我们自写的MCP Client调用 在我们的配置文件中输入以下内容
1 2 3 4 5 6 7 8 9 10 { "mcpServers" : { "weather-sse" : { "isActive" : true , "type" : "sse" , "url" : "http://127.0.0.1:8001/mcp" , "name" : "weather-sse" } } }
执行client_openai_mix.py
同样可以调用成功。
如果大家对于这块的代码有不清楚的,请看我前面发的文章《从0开始实现MCP-Client
》。
同时,原来的FastAPI
接口也是不影响使用的。
控制接口端点的暴露 默认的情况下,FastAPI-MCP
会将所有的FastAPI
接口全部暴露给大模型,大模型全部都可以去调用。但是这样其实是不安全的,有些接口可能涉及到一些法律和隐私问题,不便于公开。那么这个时候,我们可以自己去选择要暴露给MCP Server
的接口就显得非常重要了。
我们可以通过在定义FastAPI
接口时指定的,operation_id
和tags
进行过滤和筛选。
通过operation_id进行筛选 通过以下方式,选择operation_id
为get_weather_cityname
的接口进行暴露,这个时候其他接口就都不会暴露。
还可以通过exclude_operations
对某些接口进行反选,这个大家自行尝试。
可以通过添加打印的方式,获取暴露的所有接口。
可以看到此时只有get_weather_cityname
这个接口放入了MCP Server
。
operation_id
一般用于控制单个接口的暴露与否,如果我们先要批量去控制某些接口的暴露与否,我们可以使用tags
进行控制。
我们给前面FastAPI
中的get_weather_cityname
工具添加tags='mcp'
。
然后就可以在FastAPI-MCP
中通过tags
进行筛选。
通过这样的方式就可以把所有标记为tags='mcp'
的接口全部暴露出来,没有标记的接口全部都不会暴露给MCP Server
。
同样在这里也可以使用exclude_tags
进行tags
的反选,大家可以自行尝试。
可以看到此时,同样只有get_weather_cityname
这个接口放入了MCP Server
。
值得改进的思考 现在的FastAPI-MCP
还是非常依赖接口注释的详细程度,本质上还是将接口注释机械的放入到提示词中。这个我们可以通过调试看到。
在图示位置添加断点,然后开启调试。
在调试控制台中输入以下内容,打印工具描述。
1 print (mcp.tools[0 ].description)
工具描述详情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 Get Weather Cityname 通过城市名称(中国城市使用拼音)获取天气信息 Args: cityname: 城市名称(中国城市使用拼音) Returns: code: 0 成功 -1 失败 weather: 天气信息 msg: 成功或失败信息 ### Responses: **200** : Successful Response (Success Response)Content-Type: application/json **Example Response:** ```json { "code": 1, "msg": "Msg" } ``` **Output Schema:** ```json { "properties": { "code": { "type": "integer", "title": "Code" }, "weather": {}, "msg": { "type": "string", "title": "Msg" } }, "type": "object", "required": [ "code", "msg" ], "title": "WeatherResponse" } ``` **422** : Validation ErrorContent-Type: application/json **Example Response:** ```json { "detail": [ { "loc": [], "msg": "Message", "type": "Error Type" } ] } ``` **Output Schema:** ```json { "properties": { "detail": { "items": { "properties": { "loc": { "items": {}, "type": "array", "title": "Location" }, "msg": { "type": "string", "title": "Message" }, "type": { "type": "string", "title": "Error Type" } }, "type": "object", "required": [ "loc", "msg", "type" ], "title": "ValidationError" }, "type": "array", "title": "Detail" } }, "type": "object", "title": "HTTPValidationError" } ```
实际上,FastAPI
对于pydantic
工具集中度非常高,用户如果通过以下方式去定义变量。
1 2 3 4 from pydantic import BaseModel, Fieldclass CityName (BaseModel ): cityname: str = Field(..., description="城市名称,如果是中国城市,需要使用拼音。" )
在定义变量时,用户就给出了详细的字段解释,实际上这里的解释是真正需要写入到提示词中的内容,现在的FastAPI-MCP
版本没有对这样的数据进行获取。用户如果为了适配,FastAPI-MCP
就比如在每个接口中重复的把字段解释编写一边,显得不太智能和繁琐。
其次,如果是将接口修改为MCP Server
有可能还会存在一个问题,我们在编写接口时,基本上都是一个格式化的JSON
作为输出内容。如果直接把JSON
返回给到大模型,那么返回结果中的每个字段都必须在接口的注释中详细写清楚,如果没有写清楚,并且JSON
字段的命名可解释性不好,可能就会导致大模型理解出现问题。
如果FastAPI-MCP
能够对字段解释进行解读,这个问题实际上也是可以得到解决的,因为在定义FastAPI
接口时,可以指定response_model
。