云计算百科
云计算领域专业知识百科平台

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

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

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

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

一、准备项目

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

mcp-sqlite/
├── prepare.py
├── run.py
├── check.py
└── fixtures/
└── schema.sql

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

Mac:

python3 -m venv .venv
.venv/bin/python -m pip install mcp==2.3.0 pydantic==2.14.1

Windows 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,复制:

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,复制建库代码:

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 执行:

.venv/bin/python prepare.py

Windows 执行:

.\\.venv\\Scripts\\python.exe prepare.py

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

Created demo.db

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

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

三、写一个只读查询工具

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

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,复制:

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 执行:

.venv/bin/python check.py

Windows 执行:

.\\.venv\\Scripts\\python.exe check.py

正常情况下,应该查到:

DB-001:虚构登录问题
DB-002:虚构搜索问题

最后显示:

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

五、接进 AI 客户端

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

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

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

请调用 list_db_issues。

project 填 demo-project
status 填 open
limit 填 10

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

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

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

DB-003:虚构已修复问题

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

六、别忽略这两个区别

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

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

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

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

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

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

赞(0)
未经允许不得转载:网硕互联帮助中心 » MCP 实战 07:让 AI 查询 SQLite,只开放一个查询入口
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!