首页 › tutorials › 用FastMCP造SQLite查询MCP服务器

概述

本教程带你用 Python 的 FastMCP 框架,从零构建一个真实可用的 MCP 服务器:把一个本地 SQLite 数据库"翻译"成 Claude Code 能直接调用的工具、资源和提示词。完成后,你可以在 Claude Code 对话里直接说"查一下最近 7 天订单总额",模型会自动调用你写的 run_query 工具、读取 sqlite://tables 资源、套用你定义的分析提示词。适合已经跑通过 Claude Code 基础操作、想真正动手扩展它能力的开发者。

前置条件

一、先搞懂: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 用满。

二、步骤 1:用 uv 初始化项目

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。

三、步骤 2:准备一个 SQLite 示例库

为了让例子可运行,先造一张订单表。在同目录建 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。

四、步骤 3:写 server.py —— 定义工具

新建 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 "(无结果)"

几个关键设计点:

  1. 装饰器:FastMCP 3.x 里 @mcp.tool(不带括号)即可,函数名即工具名,docstring 即工具描述——模型正是靠这段描述决定要不要调用你的工具,所以要把 docstring 当 API 文档来写。
  2. 类型注解是契约:sql: str、limit: int = 100 会自动转成 JSON Schema 传给模型,模型据此生成正确参数。
  3. 只读护栏:开头判断必须 SELECT 开头,把写操作挡死。这是给模型暴露数据库时最重要的一道防线。
  4. 返回 Markdown:相比裸 list,Markdown 表格让模型"看一眼就懂",后续总结更准。

五、步骤 4:挂上 Resource(只读元数据)

工具是"会动的",资源是"静态供翻阅的"。把表结构和最近查询日志做成资源:

@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 来拿建表语句。

六、步骤 5:挂上 Prompt(复用对话模板)

最后加一个提示词,把"如何审计 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,缺了就静默跳过。

八、步骤 7:把服务器接入 Claude Code

两种方式,按作用范围选:

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   # 看详情

九、步骤 8:在 Claude Code 对话里实测

启动 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: 自然语言汇总答案

十一、安全与生产化要点

十二、常见问题速查

现象 原因 解决
/mcp 显示 failed command 找不到依赖 改用 uv run server.py;确认 uv 在 PATH
工具没出现在列表 缺类型注解或缺 docstring 给每个参数加注解、补 docstring
调用报 database is locked 连接未关闭 每次查询用独立连接并 close(),或加超时
修改 server.py 后没生效 Claude Code 缓存了旧进程 重启 claude 会话重新握手

延伸阅读

训练营适用性评估

参考来源

  1. How to Build an MCP Server in Python — DigitalOcean — 用 FastMCP 把 SQLite 接入 Cursor 与 Claude Desktop 的端到端教程(2026-06 更新)
  2. Build an MCP Server in Python: From Zero to Claude-Ready — DevToolLab — 含 SDK 下载量增长数据与官方 SDK 脚手架实践
  3. Build an MCP server — Model Context Protocol 官方文档 — 三原语与 stdio 传输的协议级权威说明
  4. FastMCP in Python: Build a Real MCP Server (2026 Guide) — FastMCP 3.x 工具/资源/提示词与 GitHub OAuth 实战
  5. How to Build an MCP Server with Python, Docker, and Claude Code — freeCodeCamp — 含 Docker 容器化与接入 Claude Code 的完整闭环

[!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 等场景全覆盖。

51学通信知识星球海报-扫码加入