MCP 实战 07:让 AI 查询 SQLite,只开放一个查询入口

文件读过了,接口也接过了,这次试试数据库。

目标很具体:让 AI 查出一个演示项目里,还没关闭的两条缺陷。

我们使用 SQLite。它不需要单独安装数据库服务,Python 自带支持,适合先把流程跑通。

一、准备项目

新建文件夹 mcp-sqlite,按下面创建文件:

text 复制代码
mcp-sqlite/
├── prepare.py
├── run.py
├── check.py
└── fixtures/
    └── schema.sql

在项目文件夹打开终端,安装依赖。

Mac:

bash 复制代码
python3 -m venv .venv
.venv/bin/python -m pip install mcp==2.3.0 pydantic==2.14.1

Windows PowerShell:

powershell 复制代码
py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install mcp==2.3.0 pydantic==2.14.1

Python 使用 3.11 或更高版本。

二、建一个演示数据库

打开 fixtures/schema.sql,复制:

sql 复制代码
CREATE TABLE issues (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    project TEXT NOT NULL,
    status TEXT NOT NULL CHECK(status IN ('open', 'closed'))
);

INSERT INTO issues VALUES
('DB-001', '虚构登录问题', 'demo-project', 'open');

INSERT INTO issues VALUES
('DB-002', '虚构搜索问题', 'demo-project', 'open');

INSERT INTO issues VALUES
('DB-003', '虚构已修复问题', 'demo-project', 'closed');

这里有三条问题:两条未关闭,一条已关闭。

再打开 prepare.py,复制建库代码:

python 复制代码
from pathlib import Path
import sqlite3

BASE = Path(__file__).resolve().parent / "fixtures"


def prepare():
    target = BASE / "demo.db"

    # 已存在时直接报错,避免覆盖旧数据。
    with target.open("xb"):
        pass

    try:
        connection = sqlite3.connect(target)
        try:
            connection.executescript(
                (BASE / "schema.sql").read_text(encoding="utf-8")
            )
        finally:
            connection.close()
    except Exception:
        target.unlink(missing_ok=True)
        raise

    return target


if __name__ == "__main__":
    print("Created", prepare().name)

Mac 执行:

bash 复制代码
.venv/bin/python prepare.py

Windows 执行:

powershell 复制代码
.\.venv\Scripts\python.exe prepare.py

看到下面的输出,就说明建好了:

text 复制代码
Created demo.db

fixtures 文件夹中会多出一个 demo.db,它就是数据库。

建库只需要执行一次。 再次执行提示文件已存在,是为了防止覆盖数据。

三、写一个只读查询工具

打开 run.py,复制完整代码:

python 复制代码
from pathlib import Path
import sqlite3
from typing import Annotated, Any, Literal

from pydantic import Field
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
from mcp.types import ToolAnnotations

mcp = MCPServer("sqlite-reader-demo")

READ_ONLY = ToolAnnotations(
    read_only_hint=True,
    destructive_hint=False,
    idempotent_hint=True,
    open_world_hint=False,
)

DB = Path(__file__).resolve().parent / "fixtures" / "demo.db"


def connect_readonly():
    connection = sqlite3.connect(
        DB.resolve().as_uri() + "?mode=ro",
        uri=True,
        timeout=1,
    )
    connection.row_factory = sqlite3.Row
    connection.execute("PRAGMA query_only=ON")
    return connection


@mcp.tool(annotations=READ_ONLY)
def list_db_issues(
    project: Literal["demo-project", "empty-project"],
    status: Literal["open", "closed"] = "open",
    limit: Annotated[int, Field(strict=True, ge=1, le=20)] = 10,
) -> dict[str, Any]:
    """按项目和状态查询演示缺陷,不接受 SQL,不修改数据库。"""

    try:
        connection = connect_readonly()

        try:
            rows = connection.execute(
                """
                SELECT id, title, project, status
                FROM issues
                WHERE project = ? AND status = ?
                ORDER BY id
                LIMIT ?
                """,
                (project, status, limit),
            ).fetchall()
        finally:
            connection.close()

    except sqlite3.Error:
        raise ToolError(
            "database_unavailable_or_invalid_schema"
        ) from None

    return {
        "source": "local-demo-sqlite",
        "count": len(rows),
        "issues": [dict(row) for row in rows],
    }


if __name__ == "__main__":
    mcp.run(transport="stdio")

这段代码限制了三件事:

  • 数据库以只读方式打开。
  • SQL 写在代码里,调用方只能传查询条件。
  • 最多返回 20 条,避免一次取出太多数据。

SQL 中的 ? 用来绑定参数,别把查询条件直接拼进 SQL 字符串。

这里的 count 是本次返回数量,没有额外统计全部匹配记录。

四、检查能不能查到数据

打开 check.py,复制:

python 复制代码
import asyncio
import json
import sys
from pathlib import Path

from mcp import Client, StdioServerParameters


async def main():
    config = {
        "command": sys.executable,
        "args": [
            str(Path(__file__).with_name("run.py").resolve())
        ],
        "env": {
            "PYTHONUTF8": "1",
            "PYTHONIOENCODING": "utf-8",
        },
    }

    async with Client(StdioServerParameters(**config)) as client:
        result = await client.call_tool("list_db_issues", {
            "project": "demo-project",
            "status": "open",
            "limit": 10,
        })

        if result.is_error:
            for item in result.content:
                print(getattr(item, "text", ""))
            raise SystemExit(1)

        data = result.structured_content

        assert data["source"] == "local-demo-sqlite"
        assert data["count"] == 2
        assert [x["id"] for x in data["issues"]] == [
            "DB-001", "DB-002",
        ]

        print(json.dumps(data, ensure_ascii=False, indent=2))

        empty = await client.call_tool("list_db_issues", {
            "project": "empty-project",
        })
        assert not empty.is_error
        assert empty.structured_content["count"] == 0

    print("检查通过:查到两条未关闭问题,空项目返回零条。")
    print("客户端配置:")
    print(json.dumps(config, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    asyncio.run(main())

Mac 执行:

bash 复制代码
.venv/bin/python check.py

Windows 执行:

powershell 复制代码
.\.venv\Scripts\python.exe check.py

正常情况下,应该查到:

text 复制代码
DB-001:虚构登录问题
DB-002:虚构搜索问题

最后显示:

text 复制代码
检查通过:查到两条未关闭问题,空项目返回零条。

五、接进 AI 客户端

按照第 04 篇的方法,添加本地 stdio 服务。

名称可以填 sqlite-issues,其他字段使用 check.py 输出的配置。

给当前 Agent 启用服务,发送:

text 复制代码
请调用 list_db_issues。

project 填 demo-project
status 填 open
limit 填 10

列出本次查到的问题编号和标题。
只根据工具结果回答。

确认调用记录中出现 list_db_issues,返回两条未关闭问题。

再把 status 改成 closed,应该查到:

text 复制代码
DB-003:虚构已修复问题

这样就能确认,查询条件确实生效了。

六、别忽略这两个区别

查不到数据,不等于数据库出错。

查询 empty-project,正常结果是零条;数据库不存在或表结构不对,则应该报错。

允许查询,不等于允许执行任意 SQL。

这篇只开放了"按项目和状态查问题"的入口,没有开放改表、删库或自定义 SQL 的能力。

如果修改了 schema.sql,已有的 demo.db 不会自动更新。需要重建时,先确认它确实是本篇生成的演示库,再处理旧文件,别误删自己的数据库。

本文已在 Mac 上完成建库和 MCP stdio 查询验证。Windows、真实业务数据库和模型调用未实测。

相关推荐
骑着蜗牛撵大象32713 小时前
2026 后端 AI 工程化:Spring AI 管调用、MCP 管工具、Agent 内嵌管自治
java·人工智能·后端·spring·ai·可观测性·mcp
Danny转行做跨境14 小时前
亚马逊选品接 MCP 到 Claude Code 的四个坑
跨境电商·mcp·sorftime
只睡四小时1 天前
MCP 弃用 SSE?Node 分帧漏 81.7% 事件
node.js·webassembly·实时通信·mcp·streamable http
跨境Jacky2 天前
选品数据采集,MCP比爬虫稳在哪?亚马逊到1688实测
跨境电商·mcp·sorftime
vjmap2 天前
Cesium + MCP:在 Cursor、Claude Code 里一句话生成地图视频,唯杰 AI 地图动画上线
gis·cesium·mcp
ynchyong2 天前
MCP Server 与 Function Calling
functioncalling·mcp·mcp server
猫头_2 天前
MCP 开发实战:把你自己的后端 API 接入 Agent
人工智能·typescript·mcp
第404块砖头2 天前
Spring Boot 4.1 + Spring AI 2.0:我如何用 Java 微服务把 RAG、知识图谱和 MCP 协议串成一个 Agent 平台
知识图谱·知识库·graph·rag·mcp·spring boot 4.1·spring ai 2.0
xrlfreedom2 天前
MCP 面试高频考点:把知识库 RAG 封装成 Tool 时,提示注入防护与人机协同确认怎么答?
mcp·提示注入防护·人机协同确认