本教程带你用 Python 的 FastMCP 框架,从零构建一个真实可用的 MCP 服务器:把一个本地 SQLite 数据库"翻译"成 Claude Code 能直接调用的工具、资源和提示词。完成后,你可以在 Claude Code 对话里直接说"查一下最近 7 天订单总额",模型会自动调用你写的 run_query 工具、读取 sqlite://tables 资源、套用你定义的分析提示词。适合已经跑通过 Claude Code 基础操作、想真正动手扩展它能力的开发者。
claude --version)curl -LsSf https://astral.sh/uv/install.sh | sh),它是 2026 年构建 MCP 服务器的事实标准,能一键解决依赖与虚拟环境MCP(Model Context Protocol)把"AI 模型如何使用外部能力"标准化成了三原语。Anthropic 在 2025 年底把规范捐给了 Linux 基金会,SDK 月下载量从 10 万飙升到 9700 万,Claude Code、Cursor、Windsurf、VS Code Copilot、JetBrains AI 现在全部原生支持。也就是说,你今天写的这个服务器,明天插到任何主流 AI 编程工具里都能用。
| 原语 | 作用 | 在 FastMCP 里 | 本教程对应 |
|---|---|---|---|
| Tool(工具) | 模型可"执行"的函数,有副作用、返回结果 | @mcp.tool |
run_query、list_tables |
| Resource(资源) | 只读、用 URI 寻址的数据,模型可"读取" | @mcp.resource("uri") |
sqlite://tables、sqlite://schema/orders |
| Prompt(提示词) | 可复用的对话模板,参数化注入 | @mcp.prompt |
SQL 审计提示词 |
一句话区分:Tool 是"做",Resource 是"读",Prompt 是"教模型怎么问"。把它们都塞进一个服务器,才算把 MCP 用满。
uv init sqlite-mcp && cd sqlite-mcp
uv add fastmcp
uv init 会生成 pyproject.toml 和一个隔离的虚拟环境。uv add fastmcp 会把最新版 FastMCP 3.x 拉进来。确认版本:
uv run python -c "import fastmcp; print(fastmcp.__version__)"
预期输出形如 3.2.x。
为了让例子可运行,先造一张订单表。在同目录建 seed.py:
# seed.py —— 生成示例数据库,只跑一次
import sqlite3, random, datetime, uuid
con = sqlite3.connect("analytics.db")
cur = con.cursor()
cur.executescript("""
CREATE TABLE IF NOT EXISTS orders (
id TEXT PRIMARY KEY,
customer TEXT,
amount REAL,
status TEXT,
created_at TEXT
);
""")
today = datetime.date(2026, 7, 1)
for i in range(500):
d = today - datetime.timedelta(days=random.randint(0, 30))
cur.execute("INSERT INTO orders VALUES (?,?,?,?,?)", (
str(uuid.uuid4()),
random.choice(["alice", "bob", "carol", "dave"]),
round(random.uniform(10, 999), 2),
random.choice(["paid", "refunded", "pending"]),
d.isoformat(),
))
con.commit(); con.close()
print("seeded analytics.db")
跑一次:uv run python seed.py,目录下会出现 analytics.db。
新建 server.py,先用工具把数据库"开口":
# server.py
import sqlite3
from fastmcp import FastMCP, Context
DB = "analytics.db"
mcp = FastMCP("SQLite Analytics") # 服务器名,会显示在客户端里
def _conn():
# 每次查询用独立连接,避免跨线程共享问题
return sqlite3.connect(DB)
@mcp.tool
def list_tables() -> list[str]:
"""列出数据库里的所有表名。"""
cur = _conn().cursor()
cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
return [r[0] for r in cur.fetchall()]
@mcp.tool
def run_query(sql: str, limit: int = 100) -> str:
"""
对 analytics.db 执行【只读】SQL 查询并返回结果。
参数:
sql: 一条 SELECT 语句
limit: 最大返回行数,默认 100
"""
sql_stripped = sql.strip().rstrip(";")
if not sql_stripped.lower().startswith("select"):
return "错误:本工具仅允许 SELECT 查询。"
cur = _conn().cursor()
cur.execute(sql_stripped)
cols = [d[0] for d in cur.description]
rows = cur.fetchmany(limit)
# 拼成 Markdown 表格,模型读起来更直观
md = "| " + " | ".join(cols) + " |\n|" + "---|" * len(cols) + "\n"
md += "\n".join("| " + " | ".join(str(c) for c in r) + " |" for r in rows)
return md or "(无结果)"
几个关键设计点:
@mcp.tool(不带括号)即可,函数名即工具名,docstring 即工具描述——模型正是靠这段描述决定要不要调用你的工具,所以要把 docstring 当 API 文档来写。sql: str、limit: int = 100 会自动转成 JSON Schema 传给模型,模型据此生成正确参数。SELECT 开头,把写操作挡死。这是给模型暴露数据库时最重要的一道防线。工具是"会动的",资源是"静态供翻阅的"。把表结构和最近查询日志做成资源:
@mcp.resource("sqlite://tables")
def tables_resource() -> str:
"""所有表及其行数的概览。"""
cur = _conn().cursor()
cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
out = []
for (t,) in cur.fetchall():
cur.execute(f'SELECT COUNT(*) FROM "{t}"')
out.append(f"- {t}: {cur.fetchone()[0]} 行")
return "\n".join(out)
@mcp.resource("sqlite://schema/{table}")
def schema_resource(table: str) -> str:
"""指定表的建表语句。table 参数即表名。"""
cur = _conn().cursor()
cur.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (table,))
row = cur.fetchone()
return row[0] if row else f"找不到表 {table}"
注意第二个资源用了 URI 模板 sqlite://schema/{table},{table} 会自动映射成函数参数。模型可以请求 sqlite://schema/orders 来拿建表语句。
最后加一个提示词,把"如何审计 SQL"固化下来:
@mcp.prompt
def audit_sql(sql: str) -> str:
"""生成一段审计给定 SQL 的提示词:检查性能与安全风险。"""
return f"""你是一名资深 DBA。请审计下面这条 SQL:
```sql
{sql}
从三方面给出结论: 1. 是否存在 SQL 注入风险或越权读取; 2. 是否缺索引、有无全表扫描隐患; 3. 一句话优化建议。"""
## 七、步骤 6:用 MCP Inspector 调试(不用 Claude Code 也能测)
写完先别急着接 Claude Code,用官方调试器单步验证三原语:
```bash
uv run fastmcp dev server.py
它会拉起一个本地 Web UI(MCP Inspector),在浏览器里你能:
常见问题:如果工具没出现,九成是函数没有完整类型注解,或 docstring 为空——FastMCP 靠这两样生成 schema,缺了就静默跳过。
两种方式,按作用范围选:
A. 命令行注册(用户级,全局可用)
claude mcp add sqlite-analytics -- uv run --directory $(pwd) server.py
B. 项目级 .mcp.json(团队共享,随仓库走)
在项目根建 .mcp.json:
{
"mcpServers": {
"sqlite-analytics": {
"command": "uv",
"args": ["run", "--directory", ".", "server.py"]
}
}
}
注意 command 必须是绝对可执行的。用 uv run 而不是裸 python,是因为 uv 会自动激活项目虚拟环境、装好依赖;裸 python 经常因找不到 fastmcp 而启动失败——这是排第一的高频坑。
注册完验证:
claude mcp list # 列出已注册服务器
claude mcp get sqlite-analytics # 看详情
启动 claude,在会话里输入 /mcp 能看到 sqlite-analytics 处于 connected 状态。然后直接用自然语言驱动:
> 帮我看下 analytics.db 有哪些表,orders 表大概多大?
> 查最近 7 天 paid 订单的总金额,按客户分组
你会观察到模型:先调 list_tables / 读 sqlite://tables 摸清结构 → 读 sqlite://schema/orders 看字段 → 调 run_query 下发 SQL → 把 Markdown 表格转成人话回答。整个过程模型自己编排工具链,你只负责提问。
flowchart LR
U[用户对话] --> CC[Claude Code]
CC -->|stdio JSON-RPC| MCP[SQLite Analytics MCP]
MCP --> T[Tools: run_query / list_tables]
MCP --> R[Resources: tables / schema]
MCP --> P[Prompts: audit_sql]
T --> DB[(analytics.db)]
R --> DB
sequenceDiagram
participant U as 用户
participant C as Claude Code
participant S as MCP 服务器
participant DB as SQLite
U->>C: "最近7天paid订单总额按客户分组"
C->>S: tools/call list_tables
S->>DB: SELECT name FROM sqlite_master
DB-->>S: ["orders"]
S-->>C: ["orders"]
C->>S: resources/read sqlite://schema/orders
S-->>C: CREATE TABLE orders(...)
C->>S: tools/call run_query("SELECT customer, SUM(amount)...")
S->>DB: 执行只读查询
DB-->>S: 结果集
S-->>C: Markdown 表格
C-->>U: 自然语言汇总答案
SELECT 前缀白名单拦截写操作。生产里更稳的做法是开一个权限受限的数据库账号,或在 _conn() 里强制 PRAGMA query_only = ON。schema_resource 用了 ? 占位符而非字符串拼接,避免表名注入。limit 参数 + fetchmany 防止 SELECT * 把几百万行灌进模型上下文。| 现象 | 原因 | 解决 |
|---|---|---|
/mcp 显示 failed |
command 找不到依赖 |
改用 uv run server.py;确认 uv 在 PATH |
| 工具没出现在列表 | 缺类型注解或缺 docstring | 给每个参数加注解、补 docstring |
调用报 database is locked |
连接未关闭 | 每次查询用独立连接并 close(),或加超时 |
| 修改 server.py 后没生效 | Claude Code 缓存了旧进程 | 重启 claude 会话重新握手 |
claude mcp add 与 .mcp.json 三种 scope 详解run_query 换成 httpx.get(...) 即可,其余骨架完全复用streamable-http 传输改成远程服务器;③ 接真实业务库(如 Postgres),对比只读策略差异。[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
