关于 DB Explorer
DB Explorer 是面向 PostgreSQL、MySQL 和 SQLite 的只读 MCP 服务,让 AI 编码助手检查数据库结构、查询数据并审视迁移。它在解析层拒绝非单条只读 SELECT,并限制结果行数,同时提供执行计划和索引建议。
主要功能
- 01检查表、视图、列、键和索引
- 02解析并执行受限的 SELECT 查询
- 03查看原生执行计划
- 04检测模式问题并建议索引
- 05验证迁移脚本但不执行
适用场景
- 01用自然语言探索关系数据库结构
- 02查询数据并排查慢查询
- 03识别缺失主键或外键索引
- 04起草并检查数据库迁移
MCP 相关
MCP 服务为客户端提供 7 个数据库工具,通过 SQLAlchemy 连接 PostgreSQL、MySQL 和 SQLite。支持本地 stdio,以及带令牌认证的远程 HTTP;查询安全检查在访问数据库前执行。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
7收录的工具列表 · 更新于 1 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| limit | integer | 否 | 每页的表数,范围为 1 到 1000。 |
| detail | boolean | 否 | 列出每个表的列、键和索引,而不是列数。若问题涉及多个表,请使用此参数,而不是对每个表分别调用一次 table_name。页面会变大,因此请使用 name_pattern 缩小较大架构的范围。 |
| offset | integer | 否 | 要跳过的表数。传入上一页的 next_offset。 |
| table_name | any | 否 | 某个表或视图的准确名称。返回其列、键和索引;对于表,还会返回行数。此时会忽略列表参数。 |
| name_pattern | any | 否 | 按名称筛选表,不区分大小写。普通文本匹配子字符串('order');*、? 和 [] 用于 glob 匹配('order_*')。 |
| include_sample_data | boolean | 否 | 与 table_name 一起使用时,还会返回 3 条示例行。 |
详细介绍
DB Explorer MCP
一个只读的 Model Context Protocol(MCP)服务,支持 PostgreSQL、MySQL 和 SQLite,可让 AI 编码助手探索、查询和审计关系数据库,同时绝无写入数据库的可能。
将 MCP 客户端指向数据库,然后用自然语言提问。客户端的大模型负责编写 SQL;此服务会解析 SQL,拒绝任何非单条只读 SELECT 语句,在行数上限内执行查询,并返回结构化结果。此外还提供架构检查、执行计划、索引建议和迁移审查功能。
MCP client LLM -> FastMCP tools -> safety layer -> SQLAlchemy -> database
(writes SQL) (7 tools) (rejects writes) (any dialect)
此服务本身不会调用任何大模型 API,因此无需配置 API 密钥——推理由你连接的客户端完成。通过 SQLAlchemy 支持 SQLite、PostgreSQL 和 MySQL。
经过测试和评估。 每次推送和拉取请求都会运行 336 项测试,CI 中还会针对真实的 PostgreSQL 和 MySQL 额外运行 26 项测试(测试)。在一项 Agent 评估中,Claude 通过这些工具完成了 14 项中的 14 项真实数据库任务,没有任何工具调用失败,每项任务平均调用 2 次,独立评审的平均得分为 4.6 / 5(Agent 评估)。
原因
向助手提供原始数据库凭据,一次误解指令或遭到提示词注入的交互就可能删除表。使用只读副本则会失去架构上下文和分析执行计划的能力。此服务采用折中方案:提供完整的检查和查询能力,同时在解析器层面从结构上杜绝数据修改,而不是寄希望于模型遵守要求。
架构
┌──────────────────────────────────────────────────────────────┐
│ MCP client (Claude Code / Claude Desktop / Inspector) │
│ owns the LLM: reads schema, authors SQL, interprets results │
└───────────────────────────┬──────────────────────────────────┘
│ MCP · stdio (local)
│ · streamable HTTP + bearer token (remote)
┌───────────────────────────▼──────────────────────────────────┐
│ server.py — FastMCP instance + one engine per token group │
│ │
│ explore_schema execute_query explain_query │
│ validate_schema suggest_index migration_context │
│ validate_migration │
└──────┬───────────────────────┬───────────────────┬───────────┘
│ │ │
│ read path │ metadata path │ review path
│ │ │
┌──────▼────────────┐ ┌───────▼─────────┐ ┌──────▼──────────┐
│ safety.py │ │ inspector.py │ │ migration.py │
│ ── trust boundary │ │ schema_health.py│ │ parses up/down, │
│ sqlparse AST │ │ index_suggest.py│ │ never executes │
│ SELECT-only │ │ explain.py │ │ │
│ 1 stmt · no cmnts │ │ │ │ │
│ denylist · LIMIT │ │ │ │ │
└──────┬────────────┘ └───────┬─────────┘ └─────────────────┘
│ │
└───────────┬───────────┘
│ SQLAlchemy Core
┌──────────────────▼───────────────────────────────────────────┐
│ Target database · PostgreSQL / MySQL / SQLite │
└──────────────────────────────────────────────────────────────┘
传入 safety.py 的 SQL 由客户端模型编写,因此不可信。服务会解析 SQL,而不是进行模式匹配;被拒绝的查询绝不会到达数据库。
| 模式 | 传输方式 | 身份验证 |
|---|---|---|
| 本地 | stdio | 无需身份验证;由客户端启动服务 |
| 远程 | streamable HTTP | bearer 令牌,或在 Prefect Horizon 的平台边缘进行 OAuth 身份验证 |
两种模式运行相同的工具代码——只有 MCP_TRANSPORT 不同。
快速开始
需要 Python 3.11+ 和 uv。
从 PyPI 安装
无需克隆。uvx 会下载此软件包并在独立环境中运行:
claude mcp add db-explorer --env DATABASE_URL="postgresql+psycopg2://user:pass@localhost:5432/example" -- uvx db-explorer-mcp-server
省略 --env,即可先尝试内置的演示数据库。在其他客户端的配置中,使用 "command": "uvx" 和 "args": ["db-explorer-mcp-server"]。
从源码安装
git clone https://github.com/AYUSH-148/DB-Explorer-MCP
cd DB-Explorer-MCP
uv sync
uv run python seed_demo_db.py # creates sample.db, a small shop with deliberate schema flaws
uv run pytest # 336 tests on SQLite; 26 more with TEST_*_URL set
在 Windows 上,如果 uv 不在 PATH 中,请在命令前加上 py -m(py -m uv sync)。
你无需自行启动服务:MCP 客户端会通过 stdio 启动它,因此请继续阅读连接客户端。手动运行时,uv run server.py 不会输出任何内容,并会等待客户端通过 stdin 连接;按 Ctrl+C 停止。
默认数据库为 sample.db。通过 DATABASE_URL 指定你自己的数据库,可在下方的客户端配置中设置,也可以复制 .env.example 到 .env 文件后设置:
postgresql+psycopg2://user:password@localhost:5432/example
mysql+pymysql://user:password@localhost:3306/example
sqlite:///C:/data/example.db
密码中的特殊字符需要进行百分号编码(@ → %40,# → %23,/ → %2F)。
连接客户端
Claude Code — 本地
claude mcp add db-explorer --env DATABASE_URL="postgresql+psycopg2://user:pass@localhost:5432/example" -- uv --directory "C:/path/to/DB-Explorer-MCP" run server.py
然后在会话中运行 /mcp,确认列出了 7 个工具。添加 -s user,即可在所有项目中使用。
Claude Code — 远程
claude mcp add --transport http db-explorer https://your-deployment.fastmcp.app/mcp
运行 /mcp → Authenticate,完成 OAuth 流程;令牌会自动缓存和刷新。
如果使用自己托管且带 bearer 令牌的服务,请通过请求头发送令牌。从环境变量中读取令牌,这样提交到团队代码库的 .mcp.json 就不会包含机密信息:
{
"mcpServers": {
"db-explorer": {
"type": "http",
"url": "https://dbx.example.com/mcp",
"headers": { "Authorization": "Bearer ${DBX_TOKEN}" }
}
}
}
Claude Desktop
本地配置,写入 claude_desktop_config.json:
{
"mcpServers": {
"db-explorer": {
"command": "uv",
"args": ["--directory", "C:/path/to/DB-Explorer-MCP", "run", "server.py"],
"env": { "DATABASE_URL": "postgresql+psycopg2://user:pass@localhost:5432/example" }
}
}
}
如需在不使用自定义连接器的情况下访问远程部署,可通过 stdio 代理:
{
"mcpServers": {
"db-explorer": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://your-deployment.fastmcp.app/mcp"]
}
}
}
VS Code
.vscode/mcp.json 已纳入版本控制,并通过 Windows py 启动器使用 stdio 启动服务。在 macOS 或 Linux 上,将其 command 改为 uv,并将 args 改为 ["run", "server.py"]。
MCP Inspector
npx @modelcontextprotocol/inspector
使用传输方式 Streamable HTTP 和你的 /mcp URL,或使用 stdio 运行 uv run server.py。Inspector 会显示原始工具响应和未经转述的错误,因此能最快分辨问题出在服务还是客户端。
Python
import asyncio
from fastmcp import Client
async def main():
async with Client("https://your-deployment.fastmcp.app/mcp", auth="oauth") as client:
print([tool.name for tool in await client.list_tools()])
print(await client.call_tool("explore_schema", {}))
asyncio.run(main())
试用
连接到 Quickstart 中的演示数据库 sample.db 后,可以直接使用以下提示词:
- “有哪些表?哪些表缺少主键?”
- “显示
orders中总额最高的 5 行。” - “为什么这个查询很慢?
SELECT * FROM orders WHERE created_at >= '2026-01-01'” - “这个数据库中哪些外键缺少索引?给出相应的
CREATE INDEX语句。” - “起草一个迁移,为
orders添加shipped_at列,然后验证 up 和 down 脚本。”
要查看安全防护措施是否生效,可以要求它运行 DELETE FROM users。调用会因 Unsafe query blocked: Only SELECT queries are allowed. Got: DELETE 而失败,数据库不会受到影响。
Agent 技能
skill/db-explorer/SKILL.md 是一项可选的 Agent 技能,旨在教模型如何高效使用这些工具:查询前先读取一次架构,在 SQL 中计算总数,而不是根据返回的行计算;在结果为 truncated 时明确说明;遵循错误提示,而不是反复重试。它只会指导模型,服务仍必须保持连接。
对于 Claude Code,可将 skill/db-explorer 文件夹复制到每个项目的 ~/.claude/skills/db-explorer/,或复制到某个项目内的 .claude/skills/db-explorer/。
工具
| 工具 | 参数 | 返回内容 |
|---|---|---|
explore_schema | table_name?、include_sample_data=false、name_pattern?、detail=false、limit=200、offset=0 | 用于编写 SQL 的 dialect,以及表和视图列表(每项包含 kind 和列数);或单个关系的 kind、列、主键、外键、索引、行数(仅表)和最多 3 行示例数据。单个关系的详情或 detail=true 时的分页结果还会附带数据库中存储的表和列的 comment |
execute_query | sql、row_limit=100(最多 1000) | 一条经过验证的 SELECT 的 columns、rows、count、truncated 和实际生效的 row_limit;如果结果中有重复列名,还会返回 duplicate_columns 和 note(重复项会标记为 id:1,这不是有效的 SQL);如果为控制在大小限制内而截断了行,也会返回 note |
explain_query | sql | 原生执行计划和解析出的 dialect |
validate_schema | table_name?、name_pattern?、limit=200、offset=0 | 包含 severity、code、message、suggestion 的架构问题;未指定 table_name 时,返回一页表(跳过视图),分页方式与 explore_schema 相同 |
suggest_index | query? 异或 table_name? | 附有理由的 CREATE INDEX 建议 |
migration_context | name_pattern?、limit=200、offset=0 | 用于客户端起草迁移的方言及一页列、键和索引(不含行数);分页方式与 explore_schema 相同 |
validate_migration | up_sql、down_sql | 按脚本列出已解析的语句类型;会拒绝 SELECT、注释和括号不平衡的语句。这不是语法检查,且绝不会执行 |
validate_schema 会报告四种代码:missing_primary_key、unindexed_foreign_key、wide_table(50 列及以上)和 no_indexes。如果任何索引、主键或唯一约束以外键的列开头,则该外键计为已建立索引;部分索引、GIN/GiST/BRIN 索引以及 FULLTEXT/SPATIAL 索引不计入。
explore_schema 默认开销很低:不带参数时会返回表名和列数。只有指定 table_name 时才会返回行数(仅限表,最多 100,000 行,达到上限时会标记 row_count_capped: true)。可使用 name_pattern 缩小大型架构的范围(order 会匹配名称中包含该字符串的项,order_* 是 glob 模式),使用 limit/offset 分页,并设置 detail=true,一次调用即可获取整页的列、键和索引。视图和物化视图会与表一同列出,每项都包含 kind。
安全模型
每次 execute_query、explain_query 和 suggest_index 调用都会先经过 safety.py 检查,再访问数据库。查询必须满足以下所有条件,否则会被拒绝:
- 单条语句。
SELECT 1; DROP TABLE users→Exactly one SQL statement is required - 仅允许
SELECT,根据解析后的语句类型判断,而不是检查字符串前缀 →Only SELECT queries are allowed. Got: DELETE - 不允许 SQL 注释。 在字符串字面量和带引号的名称之外,拒绝
--、/*、*/,以堵住注释注入途径。仅在 MySQL 和 MariaDB 上拒绝#,因为它在那里用于开始注释。 - 整个令牌流中不允许出现被屏蔽的关键字:
ALTER、CREATE、DELETE、DROP、EXEC、EXECUTE、GRANT、INSERT、INTO、REVOKE、TRUNCATE、UPDATE。按完整令牌匹配,因此SELECT grant_date FROM permissions可以通过。其中 4 个关键字至关重要:INSERT、UPDATE和DELETE可能隐藏在 PostgreSQL 数据修改型 CTE 中,而语句类型仍是SELECT;SELECT * INTO archive FROM users则会创建表。其余关键字不能出现在类型为SELECT的语句中;如果sqlparse的类型检测出现回归,它们仍可作为第二道防线。 - 不允许锁定子句。 拒绝
FOR UPDATE、FOR NO KEY UPDATE、FOR SHARE、FOR KEY SHARE和 MySQL 的LOCK IN SHARE MODE,因为锁定读取会阻塞其他写入操作。按子句匹配,而不是按关键字匹配,因此名为share的列仍可正常使用。
每个通过检查的查询都会被包装为 SELECT * FROM (<your query>) AS limited_query LIMIT <row_limit + 1>,因此无界扫描不会让客户端上下文被大量数据淹没。你在查询中指定的 LIMIT 会缩小内部结果集,但 row_limit(上限为 1000)仍会限制返回的数据量。如果返回了额外的探测行,该行会被丢弃,结果中会带有 "truncated": true,因此达到上限的结果不会被误认为完整结果。
验证只是三层防护中的第一层,因为关键字屏蔽列表无法识别语法正确但仍有害的查询:
-
语句超时。
SELECT pg_sleep(600)可以通过上述所有检查,因此需要独立于语法来限制执行时间:PostgreSQL 使用statement_timeout(按事务设置,因此在 PgBouncer、Supabase 和 Neon 连接池后也能生效),MySQL 使用max_execution_time,SQLite 使用进度处理器截止时间。通过 db.py 中的QUERY_TIMEOUT_SECONDS配置。 -
只读事务。 PostgreSQL 通过
BEGIN READ ONLY执行读取,MySQL 使用START TRANSACTION READ ONLY,SQLite 使用PRAGMA query_only。数据库会拒绝写入操作,这是关键字屏蔽列表无法提供的保障。 -
权限。 这是最外层的防线 — 参见 .env.example。只有
SELECT权限的用户才能阻止pg_read_file()这类服务器端文件读取,而任何关键字检查都无法可靠地捕获这类操作。权限也用于限制可读取的内容。令牌可以读取其所属组的数据库用户有权
SELECT的所有内容,因此应为每个组分配独立的用户(参见命名令牌和组)。应通过授权来隐藏凭据和 PII,而不是使用服务器端的表或列屏蔽列表:解析器无法识别SELECT *、SELECT u FROM users u或row_to_json(u)背后的列,但数据库会对这些列一一强制执行授权。-- PostgreSQL: whole tables, minus one GRANT SELECT ON ALL TABLES IN SCHEMA public TO explorer; REVOKE SELECT ON users FROM explorer; -- then only the safe columns of it GRANT SELECT (id, name, created_at) ON users TO explorer;MySQL 使用相同的列列表:
GRANT SELECT (id, name) ON app.users TO 'explorer'@'%';。如果查询访问了已撤销授权的列,就会失败并返回sql_error(permission denied)。
每次工具调用都会向 stderr 上的 db_explorer.audit 日志记录器写入一行:工具、结束状态(ok 或错误代码)、调用者(HTTP 请求中的 user=<token name> group=<group>)及其参数,参数长度上限为 2000 个字符。参数会按原样记录,因此应将日志视为与数据同等敏感。
错误
工具错误只有在告知模型接下来该怎么做时才有用,因此每次失败都会附带错误代码和修正提示,而不是仅给出一条消息:
[table_not_found] Table not found: usrs. Did you mean: users.
Hint: Call explore_schema() to list the tables in this database.
[sql_error] The database rejected the query: no such column: totl.
Hint: Call explore_schema(table_name=...) to confirm the table and column names before retrying.
[query_timeout] The query exceeded the statement timeout of 15s: interrupted.
Hint: Add a WHERE clause, aggregate instead of scanning, or query a smaller table.
| 代码 | 原因 |
|---|---|
table_not_found | 不存在此表。会附带数据库中最接近的匹配表名 |
sql_error | 数据库拒绝了查询——例如缺少列、类型不匹配或语法错误 |
query_timeout | 查询运行时间超过 QUERY_TIMEOUT_SECONDS,因此被取消 |
database_unavailable | 服务无法连接数据库,或连接在查询过程中断开。详细信息仅写入服务日志 |
unsafe_query | 被 safety.py 拦截。提示会指出触发的具体规则 |
query_too_complex | SQL 超过 50,000 个字符,或对解析器而言过长或嵌套过深(例如,IN (...) 中包含约 3,000 个值) |
too_many_concurrent_calls | 调用方的令牌已有 MAX_CONCURRENT_CALLS_PER_CLIENT 个调用正在运行 |
unknown_group | 调用方的令牌指定了一个没有数据库登录信息的服务端组 |
invalid_argument | 参数超出范围(例如 row_limit 小于 1),或参数不符合工具的输入架构:缺少必填参数、类型错误或名称未知 |
missing_argument / conflicting_arguments | suggest_index 需要且只能提供 query 或 table_name 中的一个 |
comments_not_allowed / unparsable_sql / select_in_migration | validate_migration 拒绝了脚本 |
server_busy | DB_POOL_TIMEOUT_SECONDS 内所有数据库连接都在使用中;未运行任何查询。请原样重试 |
internal_error | 服务端发生错误。回溯信息会写入服务日志;调用方只能看到错误代码 |
配置
| 变量 | 默认值 | 说明 |
|---|---|---|
DATABASE_URL | config.py 旁边的 sample.db,只读(仅 stdio) | 当 MCP_TRANSPORT 不是 stdio 时为必填;否则启动会明确报错 |
MCP_TRANSPORT | stdio | stdio、streamable-http 或 sse |
MCP_HOST | 127.0.0.1 | 仅用于 HTTP 传输 |
MCP_PORT | 8000 | 仅用于 HTTP 传输 |
QUERY_TIMEOUT_SECONDS | 15 | 单条语句的最长运行时间,也是整个调用的时间预算:预算耗尽后,不会再启动任何语句。必须为正整数 |
DB_POOL_SIZE | 5 | 保持打开的数据库连接数 |
DB_MAX_OVERFLOW | 10 | 负载较高时额外打开的连接数;归还后关闭 |
DB_POOL_TIMEOUT_SECONDS | 5 | 调用等待空闲连接的最长时间;超时后以 server_busy 失败 |
MAX_CONCURRENT_CALLS_PER_CLIENT | 5 | 单个令牌可同时运行的工具调用数(按服务进程计算);stdio 不受限制 |
MCP_AUTH_TOKEN | — | 一个共享令牌。通过 HTTP 提供服务时,必须设置此项或 MCP_AUTH_TOKENS,包括由主机自行加载 server.py:mcp 的情况。至少 32 个字符 |
MCP_AUTH_TOKENS | — | 命名令牌,格式为 name:token 或 name:group:token,以逗号分隔。请用此项替代 MCP_AUTH_TOKEN,不要同时使用。参见命名令牌和服务端组 |
DATABASE_URL_<GROUP> | — | 某个令牌组的数据库 URL,例如 DATABASE_URL_ANALYST。MCP_AUTH_TOKENS 中列出的每个服务端组都必须设置此项 |
MCP_ALLOW_UNAUTHENTICATED | false | 明确选择不要求令牌,适用于可信网络,或前置了主机自身身份验证的情况(Horizon) |
同时持有连接的调用最多为 DB_POOL_SIZE + DB_MAX_OVERFLOW(默认 15)个;超出此数量的调用会等待 DB_POOL_TIMEOUT_SECONDS,然后以 server_busy 失败。所有服务实例共用的连接池总量应低于数据库的 max_connections。
SQLite 回退方案仅用于本地开发:打开 config.py 旁边的 sample.db,且为只读模式。启动时会加载 .env(先加载 config.py 旁边的文件,再加载工作目录中的文件);真实环境变量的优先级高于两者。
通过 HTTP 提供服务
$env:MCP_TRANSPORT = "streamable-http"
$env:MCP_HOST = "0.0.0.0"
$env:MCP_PORT = "8000"
$env:DATABASE_URL = "postgresql+psycopg2://user:password@host:5432/example"
$env:MCP_AUTH_TOKEN = python -c "import secrets; print(secrets.token_urlsafe(32))"
uv run server.py
macOS 或 Linux 上的命令相同:
export MCP_TRANSPORT=streamable-http MCP_HOST=0.0.0.0 MCP_PORT=8000
export DATABASE_URL="postgresql+psycopg2://user:password@host:5432/example"
export MCP_AUTH_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
uv run server.py
HTTP 端点会向任何能访问该端口的人开放已配置数据库中的 SELECT,因此如果未设置 MCP_AUTH_TOKEN 或 MCP_AUTH_TOKENS,服务会拒绝启动,而不是在未受保护的情况下启动。客户端通过 Authorization: Bearer <token> 发送令牌;系统会在 auth.py 中以恒定时间进行比较。在确实可信的网络上,可设置 MCP_ALLOW_UNAUTHENTICATED=true 以覆盖此行为——此时服务会在每次启动时向 stderr 发出警告。
直接加载 server.py:mcp 的主机(Horizon、fastmcp run)在未设置令牌或 MCP_ALLOW_UNAUTHENTICATED=true 时,会对每个 HTTP 请求返回 401。
绑定到环回地址时,服务会拒绝外部的 Host 和 Origin 标头,防止网页通过 DNS 重绑定访问本地服务。此传输是无状态的,因此负载均衡器后的任何副本都可以响应任何请求。GET /health 无需令牌即可返回 {"status": "ok"},供负载均衡器探测使用;它绝不会访问数据库。
命名令牌和组
为每个人或服务设置独立令牌,还可以选择一个组,由该组的数据库用户决定其可读取的内容:
MCP_AUTH_TOKENS=alice:analyst:<token>,carol:analyst:<token>,fran:finance:<token>,ci:<token>
DATABASE_URL=postgresql+psycopg2://dbx_readonly:...@db/app # tokens with no group (ci)
DATABASE_URL_ANALYST=postgresql+psycopg2://dbx_analyst:...@db/app
DATABASE_URL_FINANCE=postgresql+psycopg2://dbx_finance:...@db/app
每个条目的格式为 name:token 或 name:group:token。令牌名称会出现在每条审计记录中;令牌所属的组决定调用使用哪个数据库登录账号,因此由数据库自身的授权(以及 PostgreSQL 行级安全策略)决定调用者可以读取的内容。单独设置 MCP_AUTH_TOKEN 时,它会作为一个名为 default、不属于任何组的令牌。
如果条目格式错误、名称或令牌重复、令牌长度不足 32 个字符、某组没有对应的 DATABASE_URL_<GROUP>,或者同时设置了 MCP_AUTH_TOKEN 和 MCP_AUTH_TOKENS,服务都会拒绝启动。令牌会以恒定时间进行比较,并在启动时读取:更改令牌后需要重启。每个组都有自己的连接池。
在 PostgreSQL 上,explore_schema 会列出某个组无法 SELECT 的表(所有人都可以读取目录),但这些表的行仍受保护。DEPLOYMENT.md 中包含两个数据库所需的 GRANT 语句、Horizon 上的 OAuth,以及使用 HTTPS 的虚拟机配置。
**托管 Supabase 说明:**直接连接(db.<ref>.supabase.co)仅支持 IPv6,因此在仅支持 IPv4 的容器中会失败,并显示内容为空的 psycopg2.OperationalError。请使用控制面板 Connect 面板中的连接池主机,并注意用户名会变为 postgres.<project-ref>。
测试
uv run pytest
336 项测试覆盖安全层、序列化、只读限制与超时、HTTP 身份验证和令牌组、每个工具以及错误报告。每项测试都使用临时 SQLite 数据库,因此测试套件无需凭据,也无需运行中的服务。
tests/test_live_databases.py 覆盖 SQLite 无法测试的内容——只读事务、语句超时、NUMERIC/TIMESTAMP 值、目录反射——并使用真实的 PostgreSQL 和 MySQL 进行测试。设置 TEST_POSTGRES_URL 和/或 TEST_MYSQL_URL,即可针对临时数据库运行测试(测试会创建并删除 dbx_live_* 表)。CI 会运行这两种测试。
Agent 评测
测试检查每个工具;评测则检查模型能否熟练使用这些工具。evals/run.py 会通过无头模式下的 Claude Code(claude -p),使用此服务作为唯一工具来源,对 evals/scenarios.py 中的每个任务运行测试,并使用预置的 evals/eval.db。每次运行会根据工具选择、调用预算以及从数据库中读取的答案是否正确进行评分;另一次 claude -p 调用会对整个运行结果进行 1 到 5 分的评分。
uv run python evals/seed_eval_db.py # build evals/eval.db (the runner does this if it is missing)
uv run python evals/run.py # every scenario, on Sonnet
uv run python evals/run.py --only top_customers --model opus
需要在 PATH 中安装 claude CLI 并完成登录;运行会计入该登录账号的使用限额,且无需 API 密钥。不同运行的结果会有所差异,因此评测不属于 CI。报告会写入 evals/results/。
最新结果
Claude Sonnet 5.5 作为 Agent 和评审模型,2026-10-14。完整报告(包括每次工具调用及其结果):evals/reports/2026-10-14-sonnet.json。
14/14 通过 · 0 次工具错误 · 0 次重复调用 · 总计 28 次工具调用 · 评审均分 4.64 / 5
| 任务 | Agent 选择的工具 | 调用次数 / 预算 | 评审 |
|---|---|---|---|
| 列出表和视图 | explore_schema | 1/3 | 5/5 |
| 按状态统计订单 | explore_schema、execute_query | 2/4 | 5/5 |
| 按已交付订单金额列出消费最高的 3 位客户 | explore_schema、execute_query | 3/5 | 4/5 |
| 收入最高的类别 | explore_schema、execute_query | 2/5 | 5/5 |
| 平均评分最高的类别 | explore_schema、execute_query | 2/5 | 5/5 |
| 从未下过订单的用户 | explore_schema、execute_query | 2/4 | 5/5 |
| 最近的 5 笔订单及客户姓名 | explore_schema、execute_query | 2/4 | 5/5 |
“用户表”的行数(实际名称:users) | explore_schema、execute_query | 2/4 | 5/5 |
| 为什么带日期筛选条件的查询很慢 | explore_schema、explain_query | 2/4 | 5/5 |
| 审计架构中的结构性问题 | validate_schema、explore_schema | 2/4 | 4/5 |
reviews 上哪些外键需要索引 | suggest_index、explore_schema | 2/3 | 4/5 |
添加 phone 列的迁移 | migration_context、validate_migration | 2/4 | 5/5 |
| “删除所有已取消的订单”(必须拒绝:只读) | explore_schema、execute_query | 2/5 | 4/5 |
| 使用 pro 计划的用户 | explore_schema、execute_query | 2/3 | 4/5 |
所有只有一个正确答案的结果都经过数据库核验,而不只是由评审模型评判。得分为 4 的原因是多调用了一次工具,或回答中加入了问题之外的额外说明。
已知限制:explore_schema 目前不会报告 UNIQUE 或 CHECK 约束,因此在架构审计中,模型可能会误称某个已存在的约束缺失。
项目结构
server.py FastMCP instance, engine, and the 7 tool definitions
safety.py query validation and row-limited execution
db.py engine construction, statement timeouts, read-only transactions
serialization.py driver values to JSON-safe primitives
auth.py bearer-token verification for HTTP transports
inspector.py schema reflection (columns, PK, FKs, indexes, samples)
explain.py dialect-aware EXPLAIN
index_suggest.py index recommendations from plans or FK metadata
schema_health.py objective schema issue reporting
indexes.py index coverage shared by schema_health and index_suggest
migration.py migration context and non-executing script validation
errors.py coded errors with hints, and driver-error classification
config.py environment configuration with fail-fast checks
seed_demo_db.py builds the demo sample.db used in Quickstart
skill/ optional agent skill for clients that load SKILL.md files
tests/ pytest suite over temporary SQLite databases
evals/ agent evals: a Claude model works scripted tasks through the tools
设计说明与限制
- 查询模式下的
suggest_index会标记全表扫描,而不是一般意义上的索引缺失。 它会指出发生扫描的地方,而不会编写CREATE INDEX,因为应该为哪个列创建索引取决于查询。对小表进行扫描是预期行为,不是问题。 - 只检查默认架构:PostgreSQL 的
search_path中第一个架构(通常为public),或 MySQL 中DATABASE_URL指定的数据库。若 SQL 中指定了架构,execute_query仍会读取billing.invoices。如需检查其他 PostgreSQL 架构,请将其设为首选:ALTER ROLE explorer SET search_path = billing, public;。 - 某些只读查询形式会被拒绝。
sqlparse无法解析带括号的(SELECT 1)、VALUES (1)或TABLE users,因此会拒绝这些查询,并提示改写为普通的SELECT。 - SQL 长度上限为 50,000 个字符;超过
sqlparse的令牌数或嵌套层数限制时,调用会失败并返回query_too_complex。 - 行数上限用于控制上下文,而非执行成本。 即使是开销很大的聚合查询仍会完整运行;
QUERY_TIMEOUT_SECONDS用于限制其执行时间。 - 长文本和大型结果会被截断。 超过 2000 个字符的文本值会被截断返回,并注明完整长度。查询结果达到 500,000 字节时会停止返回,并设置
truncated;架构分页也会在此处停止,并通过next_offset继续。 - 未经 MariaDB 测试。 它与 MySQL 共用代码路径,但可能会丢弃行数上限子查询中的
ORDER BY;在 MariaDB 上执行带排序的查询时,请自行添加LIMIT。 - 不超过 256 字节的二进制值会以十六进制编码返回;更大的值只会报告大小。
- 较大的
NUMERIC值会以字符串形式返回,这样可以保留精确数字,不会发生舍入。
许可证
MIT。详见 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。