<!-- title: 用FastMCP造SQLite查询MCP服务器 -->

## 概述

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

## 前置条件

- 已安装 Claude Code（终端能跑 `claude --version`）
- Python 3.11+，建议安装 **uv**（`curl -LsSf https://astral.sh/uv/install.sh | sh`），它是 2026 年构建 MCP 服务器的事实标准，能一键解决依赖与虚拟环境
- 基本能读懂 Python 语法；不需要先学过 MCP 协议细节，本教程会边做边讲
- 一台本地终端环境（macOS / Linux / WSL 均可）

## 一、先搞懂：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 初始化项目

```bash
uv init sqlite-mcp && cd sqlite-mcp
uv add fastmcp
```

`uv init` 会生成 `pyproject.toml` 和一个隔离的虚拟环境。`uv add fastmcp` 会把最新版 FastMCP 3.x 拉进来。确认版本：

```bash
uv run python -c "import fastmcp; print(fastmcp.__version__)"
```

预期输出形如 `3.2.x`。

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

为了让例子可运行，先造一张订单表。在同目录建 `seed.py`：

```python
# 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`，先用工具把数据库"开口"：

```python
# 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（只读元数据）

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

```python
@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"固化下来：

```python
@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），在浏览器里你能：

- 看到所有 Tool / Resource / Prompt 是否被正确识别
- 手动点一个工具、填参数、点 **Run**，看返回
- 检查生成的 JSON Schema 对不对

**常见问题**：如果工具没出现，九成是函数没有完整类型注解，或 docstring 为空——FastMCP 靠这两样生成 schema，缺了就静默跳过。

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

两种方式，按作用范围选：

**A. 命令行注册（用户级，全局可用）**

```bash
claude mcp add sqlite-analytics -- uv run --directory $(pwd) server.py
```

**B. 项目级 `.mcp.json`（团队共享，随仓库走）**

在项目根建 `.mcp.json`：

```json
{
  "mcpServers": {
    "sqlite-analytics": {
      "command": "uv",
      "args": ["run", "--directory", ".", "server.py"]
    }
  }
}
```

注意 `command` 必须是**绝对可执行**的。用 `uv run` 而不是裸 `python`，是因为 `uv` 会自动激活项目虚拟环境、装好依赖；裸 `python` 经常因找不到 `fastmcp` 而启动失败——这是排第一的高频坑。

注册完验证：

```bash
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 表格转成人话回答。整个过程模型自己编排工具链，你只负责提问。

## 十、整体架构与调用时序

```mermaid
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
```

```mermaid
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 *` 把几百万行灌进模型上下文。
- **结构化返回**：复杂场景可返回 dict，FastMCP 会自动序列化；本例刻意返回字符串/Markdown，是因为表格对模型最友好。

## 十二、常见问题速查

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

## 延伸阅读

- **MCP 官方规范与构建指南**：modelcontextprotocol.io/docs/develop/build-server —— 协议权威来源，三原语定义
- **FastMCP 文档**：gofastmcp.com —— 装饰器 API、Context、传输层（stdio / streamable-http）全覆盖
- **Claude Code MCP 配置**：docs.claude.com/en/docs/claude-code/mcp —— `claude mcp add` 与 `.mcp.json` 三种 scope 详解
- 想接 REST API 而非 SQLite？把 `run_query` 换成 `httpx.get(...)` 即可，其余骨架完全复用

## 训练营适用性评估

- **适合**：AI 工具链实战营、企业内部 AI 平台开发营、Python 后端转型 AI 工程营。难度中等偏入门，全程有可运行产物。
- **预计时长**：讲授 60 分钟 + 动手 90 分钟。若学员 Python 基础薄弱，可省略 Prompt 模块，专注 Tool + Resource 也能在一节课内出成果。
- **可扩展练习**：① 把只读护栏升级为按账号鉴权；② 加 `streamable-http` 传输改成远程服务器；③ 接真实业务库（如 Postgres），对比只读策略差异。

## 参考来源

1. [How to Build an MCP Server in Python — DigitalOcean](https://www.digitalocean.com/community/tutorials/mcp-server-python) — 用 FastMCP 把 SQLite 接入 Cursor 与 Claude Desktop 的端到端教程（2026-06 更新）
2. [Build an MCP Server in Python: From Zero to Claude-Ready — DevToolLab](https://devtoollab.com/blog/build-mcp-server-python) — 含 SDK 下载量增长数据与官方 SDK 脚手架实践
3. [Build an MCP server — Model Context Protocol 官方文档](https://modelcontextprotocol.io/docs/develop/build-server) — 三原语与 stdio 传输的协议级权威说明
4. [FastMCP in Python: Build a Real MCP Server (2026 Guide)](https://www.danilchenko.dev/posts/fastmcp-mcp-server/) — FastMCP 3.x 工具/资源/提示词与 GitHub OAuth 实战
5. [How to Build an MCP Server with Python, Docker, and Claude Code — freeCodeCamp](https://www.freecodecamp.org/news/how-to-build-an-mcp-server-with-python-docker-and-claude-code/) — 含 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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
