0%

将FastAPI接口转化为MCP Server

背景介绍

在当前大模型蓬勃发展的背景下,专为大模型调用设计的”API工具”——MCP,已成为研究领域的焦点。在之前的两个教程中,我们从零开始指导大家实现了MCP ServerMCP Client。通过这个过程,相信大家已经意识到MCP Server本质上是为大模型提供的一种格式化API接口。

说到这里,很多同学可能会思考:市场上已有海量API资源,但它们大多未被转化为MCP格式。如果想调用这些API,还需要重新编写一遍,确实比较繁琐。那么,有没有一种工具能够将我们现有的API一键转换为MCP格式呢?

答案是肯定的。今天我们要向大家介绍的正是这样一个项目——FastAPI-MCP。该项目实现了从FastAPIMCP 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

image-20250423100130118

创建虚拟环境

1
uv venv

image-20250423100204077

激活虚拟环境

1
.venv\Scripts\activate

image-20250423100242653

添加依赖

1
uv add fastapi-mcp

查看依赖树

1
uv tree

image-20250424100110607

到此工程和依赖搭建完成。

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 FastAPI
from pydantic import BaseModel
from utils import format_alert, get_weather_from_cityname, get_weather_from_latitude_longitude
import uvicorn

app = 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)

执行代码

image-20250424100226517

接口成功开启。

使用Postman测试接口的连通性

image-20250424100251374

接口可以正常返回。

将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 FastApiMCP
import uvicorn
from app import app

mcp = FastApiMCP(
fastapi=app,
name="weather-sse",
description="通过城市名称(中国城市使用拼音)或经纬度获取天气信息",
describe_all_responses=True, # Include all possible response schemas in tool descriptions
describe_full_response_schema=True # Include full JSON schema in tool descriptions
)
mcp.mount()

if __name__ == "__main__":
uvicorn.run(app, host='0.0.0.0', port=8001)

直接通过import语句将app.py中的FastAPI app对象导入过来即可。

执行app2mcp.py脚本。

image-20250424100612367

通过简单的补丁,我们就已经将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

image-20250424100857447

并且服务器终端也有连接请求。

image-20250424100916378

接下来我们在Cursor对话窗口测试MCP Server功能

image-20250424100953677

可以看到,可以成功调用。

其他MCP Client应用(例如:Claude-DesktopCherry StudioCline等等)的导入类似,在此不再演示,各位可自行测试。

通过我们自写的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

image-20250424101630567

同样可以调用成功。

如果大家对于这块的代码有不清楚的,请看我前面发的文章《从0开始实现MCP-Client》。

同时,原来的FastAPI接口也是不影响使用的。

image-20250424104655834

控制接口端点的暴露

默认的情况下,FastAPI-MCP会将所有的FastAPI接口全部暴露给大模型,大模型全部都可以去调用。但是这样其实是不安全的,有些接口可能涉及到一些法律和隐私问题,不便于公开。那么这个时候,我们可以自己去选择要暴露给MCP Server的接口就显得非常重要了。

我们可以通过在定义FastAPI接口时指定的,operation_idtags进行过滤和筛选。

通过operation_id进行筛选

通过以下方式,选择operation_idget_weather_cityname的接口进行暴露,这个时候其他接口就都不会暴露。

image-20250424105111967

还可以通过exclude_operations对某些接口进行反选,这个大家自行尝试。

可以通过添加打印的方式,获取暴露的所有接口。

image-20250424105305540

可以看到此时只有get_weather_cityname这个接口放入了MCP Server

通过tags进行筛选

operation_id一般用于控制单个接口的暴露与否,如果我们先要批量去控制某些接口的暴露与否,我们可以使用tags进行控制。

我们给前面FastAPI中的get_weather_cityname工具添加tags='mcp'

image-20250424105707199

然后就可以在FastAPI-MCP中通过tags进行筛选。

image-20250424105746803

通过这样的方式就可以把所有标记为tags='mcp'的接口全部暴露出来,没有标记的接口全部都不会暴露给MCP Server

同样在这里也可以使用exclude_tags进行tags的反选,大家可以自行尝试。

image-20250424105922623

可以看到此时,同样只有get_weather_cityname这个接口放入了MCP Server

值得改进的思考

现在的FastAPI-MCP还是非常依赖接口注释的详细程度,本质上还是将接口注释机械的放入到提示词中。这个我们可以通过调试看到。

在图示位置添加断点,然后开启调试。

image-20250424102753910

在调试控制台中输入以下内容,打印工具描述。

1
print(mcp.tools[0].description)

image-20250424102833646

工具描述详情

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 Error
Content-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, Field

class CityName(BaseModel):
cityname: str = Field(..., description="城市名称,如果是中国城市,需要使用拼音。")

在定义变量时,用户就给出了详细的字段解释,实际上这里的解释是真正需要写入到提示词中的内容,现在的FastAPI-MCP版本没有对这样的数据进行获取。用户如果为了适配,FastAPI-MCP就比如在每个接口中重复的把字段解释编写一边,显得不太智能和繁琐。

其次,如果是将接口修改为MCP Server有可能还会存在一个问题,我们在编写接口时,基本上都是一个格式化的JSON作为输出内容。如果直接把JSON返回给到大模型,那么返回结果中的每个字段都必须在接口的注释中详细写清楚,如果没有写清楚,并且JSON字段的命名可解释性不好,可能就会导致大模型理解出现问题。

如果FastAPI-MCP能够对字段解释进行解读,这个问题实际上也是可以得到解决的,因为在定义FastAPI接口时,可以指定response_model

image-20250424104329754

-------------本文结束感谢您的阅读-------------