FastMCP 实战:30 行 Python 给 AI 造一个数据库查询工具
上周帮团队搞了个内部知识库检索的需求------产品经理想在 Claude 里直接查数据库里的用户反馈数据。我一开始想的是写个 API,再套个 function calling 的 wrapper。折腾了半天发现 MCP(Model Context Protocol)+ FastMCP 这条路简单得多。30 来行代码,Claude Desktop 里直接就能查 SQLite 了。
这篇把我从零搭建的过程记下来,包括踩的坑。
MCP 是什么,一句话说清楚
MCP 是 Anthropic 搞的一个开放协议,干的事情很简单:让 AI 应用和外部工具之间有个统一的通信标准。你可以把它理解成 AI 世界的 USB-C 接口------不管你是数据库、文件系统还是 API 服务,只要实现了 MCP 协议,Claude、VS Code、Cursor 这些客户端都能直接连上。
协议本身基于 JSON-RPC 2.0,分两层:数据层管消息格式和语义,传输层管通信方式(本地 stdio 或远程 HTTP)。
FastMCP 是目前最主流的 Python MCP 框架。2024 年 FastMCP 1.0 被合并进了官方 Python SDK,现在独立维护的版本日下载量破百万,大概 70% 的 MCP Server 底层都在跑它。
环境准备
bash
# 用 uv 安装(推荐)
uv pip install fastmcp
# 或者 pip
pip install fastmcp
# 验证安装
python -c "import fastmcp; print(fastmcp.__version__)"
我本地环境是 Python 3.11 + macOS,FastMCP 3.x 版本。Windows 和 Linux 一样能跑。
第一个 MCP Server:5 分钟跑通
先从最简单的开始,造一个能算数的工具:
python
# calc_server.py
from fastmcp import FastMCP
mcp = FastMCP("计算器")
@mcp.tool
def add(a: float, b: float) -> float:
"""两个数相加"""
return a + b
@mcp.tool
def multiply(a: float, b: float) -> float:
"""两个数相乘"""
return a * b
if __name__ == "__main__":
mcp.run()
跑起来:
bash
python calc_server.py
默认走 stdio 传输,这时候 Server 在等客户端连接。想用 HTTP 的话:
bash
python calc_server.py # 或者在代码里改成 mcp.run(transport="http", port=8000)
FastMCP 做了三件事:把函数名当工具名,把 docstring 当工具描述,把参数类型标注转成 JSON Schema。你什么都不用手动配。
正经项目:给 AI 接一个 SQLite 数据库
下面是我在实际项目里用的代码,做了简化。需求是让 AI 能查一个用户反馈数据库。
python
# feedback_server.py
import sqlite3
from fastmcp import FastMCP
DB_PATH = "feedback.db"
mcp = FastMCP(
"用户反馈查询",
instructions="这个服务器提供用户反馈数据的查询功能。"
"用 search_feedback 按关键词搜索,用 get_stats 看整体统计。"
)
def get_conn():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
@mcp.tool
def search_feedback(keyword: str, limit: int = 20) -> list[dict]:
"""按关键词搜索用户反馈。keyword 是搜索词,limit 控制返回数量,默认 20 条。"""
conn = get_conn()
rows = conn.execute(
"SELECT id, user_id, content, created_at, rating "
"FROM feedback WHERE content LIKE ? ORDER BY created_at DESC LIMIT ?",
(f"%{keyword}%", limit)
).fetchall()
conn.close()
return [dict(r) for r in rows]
@mcp.tool
def get_stats() -> dict:
"""获取反馈数据的整体统计:总数、平均评分、最近 7 天数量。"""
conn = get_conn()
total = conn.execute("SELECT COUNT(*) FROM feedback").fetchone()[0]
avg_rating = conn.execute("SELECT AVG(rating) FROM feedback").fetchone()[0]
recent = conn.execute(
"SELECT COUNT(*) FROM feedback "
"WHERE created_at > datetime('now', '-7 days')"
).fetchone()[0]
conn.close()
return {
"total": total,
"avg_rating": round(avg_rating, 2) if avg_rating else 0,
"recent_7d": recent
}
@mcp.resource("data://schema")
def get_schema() -> str:
"""返回 feedback 表的建表语句,方便 AI 理解数据结构。"""
conn = get_conn()
schema = conn.execute(
"SELECT sql FROM sqlite_master WHERE type='table' AND name='feedback'"
).fetchone()
conn.close()
return schema[0] if schema else "表不存在"
if __name__ == "__main__":
mcp.run(transport="http", port=8000)
这里用了三个 MCP 概念:
- Tool (工具):
search_feedback和get_stats,AI 可以主动调用,执行查询操作 - Resource (资源):
get_schema,被动数据源,AI 可以读取但不触发副作用 - instructions 参数:告诉 AI 这个 Server 是干什么的、该怎么用
客户端连接:两种方式
方式一:配置到 Claude Desktop
编辑 ~/Library/Application Support/Claude/claude_desktop_config.json:
json
{
"mcpServers": {
"feedback": {
"command": "python",
"args": ["/path/to/feedback_server.py"]
}
}
}
重启 Claude Desktop,聊天界面右上角会多出一个锤子图标,点开能看到你注册的工具。直接在对话里问"最近有哪些关于登录问题的反馈?",Claude 会自动调用 search_feedback。
方式二:用 FastMCP Client 写代码连
python
# client.py
import asyncio
from fastmcp import Client
client = Client("http://localhost:8000/mcp")
async def main():
async with client:
# 调用工具
result = await client.call_tool("search_feedback", {"keyword": "登录"})
print(result)
# 读取资源
schema = await client.read_resource("data://schema")
print(schema)
asyncio.run(main())
FastMCP Client 是异步的,必须在 async with 上下文里用。
进阶:加个 Prompt 模板
MCP 还支持 Prompt(提示词模板),让 AI 知道面对特定任务该怎么组织对话:
python
@mcp.prompt
def analyze_negative(min_rating: int = 3) -> str:
"""分析低评分反馈的提示词模板。"""
return (
f"请帮我分析评分低于 {min_rating} 的用户反馈。"
"步骤:1. 先用 get_stats 看整体情况;"
"2. 搜索评分低的反馈内容;"
"3. 归纳主要问题类别;"
"4. 给出改进建议。"
)
Prompt 不是工具,它不执行操作。它给 AI 一个"操作手册",告诉它面对某类任务时应该按什么顺序调用哪些工具。
踩坑记录
坑 1:sync 函数会阻塞吗?
不会。FastMCP 3.x 默认把同步函数扔到线程池里跑(run_in_thread=True),所以 sqlite3 这种同步库可以直接用,不需要改成 aiosqlite。多个请求同时来也不会互相卡住。
但如果你的函数涉及线程亲和性(比如 Windows 上的 COM 组件),需要设 @mcp.tool(run_in_thread=False) 让它在事件循环线程里跑。
坑 2:*args 和 **kwargs 不能用
FastMCP 需要完整的参数签名来生成 JSON Schema,所以工具函数不能有 *args 或 **kwargs。所有参数必须明确声明类型。
这个限制一开始没注意,我写了个 def query(sql: str, **params) 结果启动直接报错。
坑 3:HTTP 传输的 URL 路径
用 HTTP 传输时,MCP 端点的路径是 /mcp,不是根路径。Client 连接地址要写 http://localhost:8000/mcp,不是 http://localhost:8000/。我第一次连的时候 404 了好一会儿才反应过来。
坑 4:Resource URI 格式
Resource 的 URI 必须带 scheme,比如 data://schema 或 file:///path。写成 schema 会报 invalid URI。这个在官方文档里有说,但很容易忽略。
stdio vs HTTP:什么时候用哪个
| 场景 | 传输方式 | 原因 |
|---|---|---|
| Claude Desktop 本地连接 | stdio | 无网络开销,启动快 |
| 多个客户端共享一个 Server | HTTP | stdio 只支持单客户端 |
| 远程部署 | HTTP | 必须走网络 |
| 开发调试 | stdio | 简单,不需要管端口 |
stdio 是默认值,适合本地场景。HTTP 用的是 Streamable HTTP 协议(不是普通的 REST API),支持 Server-Sent Events 做流式响应。
生产环境补充
真要上生产,还需要考虑几件事:
认证:FastMCP 支持 OAuth 和 Token 验证。HTTP 传输时建议加上,不然谁都能连:
python
from fastmcp.server.auth import BearerTokenVerifier
mcp = FastMCP(
"生产服务",
auth=BearerTokenVerifier(token="your-secret-token")
)
超时控制:给工具加超时,防止慢查询拖垮整个 Server:
python
@mcp.tool(timeout=10.0) # 10 秒超时
def slow_query(sql: str) -> list[dict]:
...
中间件:FastMCP 3.x 支持 Middleware,可以做日志、限流、错误处理这些横切关注点。跟 Web 框架的中间件概念一样。
和直接写 function calling 比,MCP 好在哪
我之前用 OpenAI 的 function calling 也做过类似的事。对比下来 MCP 的优势在于:
- 协议标准化。function calling 每家 API 格式不一样,MCP 是开放协议,写一次 Server 所有支持 MCP 的客户端都能用
- Server 独立部署。工具逻辑和 AI 应用解耦,改数据库查询不用动 AI 那边的代码
- 发现机制。客户端可以自动发现 Server 提供了哪些工具、资源、提示词,不需要手动维护 schema 列表
- 传输灵活。同一套代码可以跑在本地(stdio)也可以跑在远端(HTTP),不用改业务逻辑
当然 MCP 也不是万能的。如果你只用 OpenAI 一家的 API、工具逻辑很简单,function calling 直接搞反而更快。MCP 适合工具多、客户端多、需要复用的场景。
小结
FastMCP 把 MCP 协议的复杂度基本屏蔽掉了。写个 Python 函数加个装饰器,就是一个 MCP 工具。对于想给 AI 接外部数据源的场景,这比自己封 API + function calling 省事不少。
代码都能直接跑。想试的话先装个 FastMCP,照着上面的例子改改数据库路径就行。
GitHub 地址:github.com/PrefectHQ/f... 官方文档:gofastmcp.com