关于 Postgres DBA MCP
Postgres DBA MCP 是供 AI 客户端探索单个 PostgreSQL 数据库的只读 MCP 服务。它以受限的 CSV 结果和 SQL 检查机制,支持数据库检查并避免无界输出或写入操作。
主要功能
- 01九项 DBA 工具及锁分析
- 02只读 SQL 执行
- 03紧凑且有上限的 CSV 结果
- 04查询计划与假设索引
- 05健康检查与慢查询发现
适用场景
- 01检查数据库架构和查询结果
- 02审查查询计划与索引选项
- 03检查锁、健康状态和慢查询
MCP 相关
它向 MCP 客户端提供有界的 PostgreSQL DBA 工具,包括 SQL 执行、查询计划检查、索引测试和健康检查。默认启用受限模式。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
docker run -i --rm -e DATABASE_URI ghcr.io/a-lafon/postgres-dba-mcp:0.3.3DATABASE_URI密钥PostgreSQL connection URI of the database to serve本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
ghcr.io/a-lafon/postgres-dba-mcp:0.3.3stdio工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
为 AI 助手提供有界、只读的 PostgreSQL 访问能力。

让 Claude Code、Codex 或任何 MCP 客户端探索 PostgreSQL 数据库、运行 SQL、读取查询计划、测试假设索引、找出最慢的查询并检查数据库健康状况,同时避免模型上下文被大量内容淹没,也不会脱离只读模式。
这是一个 Model Context Protocol 服务,为 AI 客户端提供对单个 PostgreSQL 数据库的紧凑、有界访问。它是 crystaldba/postgres-mcp 0.3.0 的即插即用替代方案,包含名称和参数均相同的原有九个工具,以及自带的 analyze_locks,并封装在一个静态二进制文件中。只需修改一行客户端配置即可切换;模型接收的内容则会有所变化(紧凑 CSV、有界结果、每次调用执行一条语句),详见 MIGRATION.md。
为什么
crystaldba/postgres-mcp 为助手提供了合适的 DBA 工具:架构、SQL、带假设索引的计划、热门查询、健康检查和索引建议。但有三点妨碍了日常使用:
- 结果无界且冗长。 行以 Python
repr格式返回,且没有大小限制。对大表执行普通的SELECT会返回数十万个 token,甚至直接失败。 - 受限模式可被绕过。 它不会检查
FROM中的函数名,因此“只读”调用仍可运行pg_read_file或pg_advisory_lock等函数(CVE-2026-85620)。 - 体量庞大。 Python 容器约为 300 MB,内存占用约为 250 MB。
模型接收的内容会有所变化:
- 返回紧凑 CSV,并附带明确的结尾说明;每个结果都设有行数、字节数和单元格长度上限,并会提示每次截断;
- 默认启用受限模式:在只读事务中,使用 PostgreSQL 自带的解析器(libpg_query)解析每条语句,并检查其中任意位置的每次函数调用;
- 使用一个静态 Go 二进制文件,可在毫秒内启动。
测量结果
在同一个 PostgreSQL 17 数据库和同一个只读角色下,与 crystaldba/postgres-mcp 0.3.0 对比;测量基于 959bb72,日期为 2026-11-01,Agent 任务除外:
- 每个
execute_sql响应的令牌数减少 61 %:取两个服务均能回答的 4 个场景(S01、S04、S05、S06)的中位数。每个结果都限制为 32 kB;而 crystaldba 最多返回 268k 个令牌,或失败; - 受限模式下,131 条攻击语句中拒绝了 131 条;crystaldba 则会执行 10 条;
- 内存占用为 9 MB,而非 246 MB,两者均在 Linux 容器中测量;直接运行二进制文件时冷启动需 13 ms,而 crystaldba 包含
docker run需 2.3 s; - 使用 Claude Code 完成了 27 项 Agent 任务中的 27 项,而非 27 项中的 25 项——这是这里唯一有日期的测量:2026-10-22,基于 0.1.0,当时还没有波动性门控,也没有
analyze_locks。
另一方面:工具定义大 39 %(10 个工具共 2732 个令牌,crystaldba 的 9 个工具共 1962 个),而整个 Agent 任务的令牌数相近。从 --tools 中移除 locks,即可获得与 0.3.0 完全相同的工具集。
方法、所有数据(包括 postgres-dba-mcp 表现较差的部分)以及图表:见 BENCHMARK.md。生成这些结果的测试工具位于 bench,所用输入包括:bench/data 中的数据集、testdata/safesql 中的攻击和合法语料,以及 BENCHMARK.md 中列出的场景和任务。运行 cd bench && go run ./cmd/bench all,无需模型账号即可重新测量安全性、内存、冷启动和场景;精确的令牌计数需要使用 --tokens=claude 或设置 ANTHROPIC_API_KEY;Agent 任务及其评判器需要 claude CLI。前置条件以及每个级别所需内容:见 bench/README.md。
模型收到的内容
通过两个服务执行相同的 SELECT * FROM types。crystaldba 0.3.0:
[{'id': 1, 'txt': 'hello', 'empty': '', 'num': Decimal('532.10'), 'arr': [1, 2], 'iv': datetime.timedelta(days=1, seconds=7200), 'ts': datetime.datetime(2026, 3, 2, 10, 14, tzinfo=zoneinfo.ZoneInfo(key='Etc/UTC')), 'bin': b'\xde\xad\xbe\xef'}, {'id': 2, 'txt': None, 'empty': None, 'num': None, 'arr': None, 'iv': None, 'ts': None, 'bin': None}]
postgres-dba-mcp:
id,txt,empty,num,arr,iv,ts,bin
1,hello,"",532.10,"{1,2}",1 day 02:00:00,2026-03-02 10:14:00+00,\xdeadbeef
2,\N,\N,\N,\N,\N,\N,\N
-- 2 rows, 2 ms
值保留 PostgreSQL 文本格式,NULL(\N)与空字符串保持区分;页脚会说明返回了多少行,以及是否有内容被截断。
安装
使用 Homebrew(macOS 和 Linux):
brew install a-lafon/tap/postgres-dba-mcp
使用安装脚本(macOS 和 Linux,amd64 和 arm64):脚本会下载发行版归档文件,根据 checksums.txt 校验,并将二进制文件放入 ~/.local/bin(使用 POSTGRES_DBA_MCP_INSTALL_DIR 更改目录;使用 POSTGRES_DBA_MCP_VERSION=v0.1.0 指定发行版本):
curl -fsSL https://raw.githubusercontent.com/a-lafon/postgres-dba-mcp/main/install.sh | sh
使用 Docker(linux/amd64 和 linux/arm64,以非 root 用户运行,无 shell):
docker run -i --rm -e DATABASE_URI ghcr.io/a-lafon/postgres-dba-mcp:latest
此时 URI 中的 localhost 指容器自身:在 Docker Desktop 上使用 host.docker.internal,在 Linux 上使用 --network=host(见下文)。
也可从发行版归档文件安装,包含 Windows:从 releases page 下载适用于你系统的归档文件(amd64 支持 darwin、linux 和 windows;arm64 也支持 darwin 和 linux),根据 checksums.txt(SHA-256)进行校验,并将 postgres-dba-mcp 二进制文件放到 PATH 中。
使用 Go 工具链(Go 1.26 或更高版本):go install github.com/a-lafon/postgres-dba-mcp/cmd/postgres-dba-mcp@latest。
Linux 二进制文件和镜像使用原生 PostgreSQL 解析器解析 SQL(启动时约占 20 MB 内存)。macOS 和 Windows 二进制文件以及普通的 go install 使用其 WASM 版本,启动时内存峰值接近 300 MB,之后会释放内存。使用 C 编译器时,运行 CGO_ENABLED=1 go install -tags pgquery_cgo,netgo,osusergo github.com/a-lafon/postgres-dba-mcp/cmd/postgres-dba-mcp@latest 可构建原生版本。
运行 postgres-dba-mcp --version 检查安装。如果 shell 找不到该命令,说明 ~/.local/bin 不在你的 PATH 中:将其添加进去,或使用完整路径注册该服务。
使用 Claude Code 快速开始
在环境中使用数据库 URI 注册服务:
claude mcp add pg --env DATABASE_URI=postgres://pgdba_ro:secret@localhost:5432/pgdba -- postgres-dba-mcp
或者将其放入 .mcp.json:
{
"mcpServers": {
"pg": {
"command": "postgres-dba-mcp",
"env": { "DATABASE_URI": "${DATABASE_URI}" }
}
}
}
URI 也可以作为第一个参数(postgres-dba-mcp postgres://...)。如果两者都已提供,则以 DATABASE_URI 为准。服务启动时不会连接数据库:首次调用工具时才会建立连接。
URI 中的连接选项(sslmode、sslrootcert、connect_timeout、……)会生效,search_path 也一样。由于事务池化器会拒绝在连接时设置 search_path,因此它会应用于每个事务,而不是在连接时发送。出于同样的原因,像 timezone 这样的服务端设置会被报告为已忽略。拼写错误的 TLS 选项则会在启动时被拒绝:如果不处理,它会被当作服务端设置读取,连接也会退回到未经验证的证书。
通过 Docker 运行时,容器内的 localhost 指向容器本身,镜像不会对其进行改写。在 Docker Desktop 上,请在 URI 中使用 host.docker.internal;在 Linux 上,请使用 --network=host 或桥接网络中主机的地址。
{
"mcpServers": {
"pg": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "DATABASE_URI", "ghcr.io/a-lafon/postgres-dba-mcp:latest"],
"env": { "DATABASE_URI": "postgres://pgdba_ro:[email protected]:5432/pgdba" }
}
}
}
使用 Codex 快速开始
在环境中使用数据库 URI 注册服务:
codex mcp add pg --env DATABASE_URI=postgres://pgdba_ro:secret@localhost:5432/pgdba -- postgres-dba-mcp
或者将其放入 ~/.codex/config.toml:
[mcp_servers.pg]
command = "postgres-dba-mcp"
env = { DATABASE_URI = "postgres://pgdba_ro:secret@localhost:5432/pgdba" }
Codex 等待工具的时间超过 tool_timeout_sec 后就会停止等待(默认值为 60 s)。如果将 --statement-timeout 调高到超过该值,也要在同一条配置中调高 tool_timeout_sec。
多个数据库
一个进程只服务一个数据库。要使用多个数据库,请为每个数据库添加一条服务配置。每条配置都有各自的工具,因此你可以按服务设置权限规则。所有地方都使用只读角色,并且仅在需要写入的地方使用 --access-mode=unrestricted。
{
"mcpServers": {
"pg-prod": {
"command": "postgres-dba-mcp",
"env": { "DATABASE_URI": "${PROD_DATABASE_URI}" }
},
"pg-staging": {
"command": "postgres-dba-mcp",
"args": ["--access-mode=unrestricted"],
"env": { "DATABASE_URI": "${STAGING_DATABASE_URI}" }
}
}
}
在 Claude Code 中,权限规则以服务名称为对象,因此上面的两条配置可以区别对待。例如,在 .claude/settings.json 中,允许使用 pg-prod 的所有工具,并在每次对 pg-staging 执行 execute_sql 前请求确认:
{
"permissions": {
"allow": ["mcp__pg-prod"],
"ask": ["mcp__pg-staging__execute_sql"]
}
}
URI 之外的凭据
你不必将密码放在 URI 或 .mcp.json 中。驱动程序(pgx)会自行读取标准 libpq 来源;此服务不会为这些来源添加选项。
URI 中不带密码,密码从 ~/.pgpass(或 PGPASSFILE 指定的文件)或 PGPASSWORD 中读取:
DATABASE_URI=postgres://[email protected]:5432/pgdba
~/.pgpass 中的一行格式为 host:port:database:user:password,且该文件不能被其他人读取(chmod 600)。PGPASSWORD=... postgres-dba-mcp 的用法相同。
使用来自 pg_service.conf 的命名服务(或 PGSERVICEFILE 指定的文件):
DATABASE_URI="service=prod"
[prod]
host=db.example.com
port=5432
dbname=pgdba
user=pgdba_ro
sslmode=verify-full
密码绝不会出现在服务日志中:启动日志只会显示 user@host:port/db。
只读角色
在 restricted 模式下,服务会拒绝写入操作,但真正的屏障是 Postgres 角色。请为服务分配一个只能读取数据的角色:
CREATE ROLE pgdba_ro LOGIN PASSWORD 'change-me';
GRANT pg_read_all_stats TO pgdba_ro; -- pg_stat_statements text and other sessions' statistics
GRANT USAGE ON SCHEMA public TO pgdba_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO pgdba_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO pgdba_ro; -- tables created later
如果没有 pg_read_all_stats,analyze_locks 仍会报告 pid、锁和链,但其他角色会话的等待、状态、事务时长和查询文本会显示为 \N,并会在 -- session details hidden: grant pg_read_all_stats to <role> … 行中说明这一点。从 PostgreSQL 15 开始,属于 pg_read_all_stats 但设置为 NOINHERIT 的成员也无法读取任何信息,并会以同样的方式报告;PostgreSQL 14 则只要是该角色的成员就会授予此权限。无论哪种情况,只有报告确实隐藏了信息时才会出现该行。
对于 Agent 应能查看的每个其他模式,请重复执行 USAGE、SELECT 和 ALTER DEFAULT PRIVILEGES 行。首次连接后,如果角色是超级用户、绕过行级安全性、拥有表,或属于权限强大的内置角色(pg_read_server_files、pg_write_server_files、pg_execute_server_program、pg_signal_backend、rds_superuser、azure_pg_admin),服务会在 stderr 上发出警告。如果 PostgreSQL 版本高于 18,服务也会发出警告;18 是检查过受限模式拒绝名单所针对的内置函数的最新版本。
敏感列
受限模式会阻止写入,但不会阻止读取:角色可以读取的任何列都可能进入模型的上下文。如果某些列必须禁止访问,角色就是屏障。请创建专用角色,并且只授予它可以查看的内容,而不是使用 SELECT ON ALL TABLES。
使用列级授权时(此时 Postgres 会拒绝 SELECT *,并返回 SQLSTATE 42501,Agent 必须明确指定可以读取的列):
CREATE ROLE pgdba_agent LOGIN PASSWORD 'change-me';
GRANT pg_read_all_stats TO pgdba_agent;
GRANT USAGE ON SCHEMA public TO pgdba_agent;
GRANT SELECT (id, name, created_at) ON public.accounts TO pgdba_agent; -- not the email column
也可以通过不包含敏感列的视图实现:
CREATE VIEW public.accounts_safe AS SELECT id, name, created_at FROM public.accounts;
GRANT SELECT ON public.accounts_safe TO pgdba_agent;
请注意,pg_read_all_stats 会显示 pg_stat_statements 中其他用户执行的语句文本,其中可能包含字面值。
标志
标志可以放在数据库 URI 前面或后面,格式为 --name=value 或 --name value。
| 标志 | 默认值 | 含义 |
|---|---|---|
--access-mode | restricted | restricted(只读,并进行 SQL 检查)或 unrestricted(execute_sql 可以修改数据库) |
--transport | stdio | stdio、streamable-http 或 sse(已弃用) |
--tools | all | 工具组的逗号分隔列表:schema、query、explain、stats、health、index、locks 或 all |
--max-rows | 100 | 结果的默认行数上限(至少为 1,且不超过 --max-rows-limit) |
--max-rows-limit | 1000 | 客户端可请求的最高 max_rows 值 |
--max-bytes | 32768 | 单个工具结果的字节数上限(至少为 1024) |
--max-cell | 200 | 单元格缩短前允许的默认最长字符数(至少为 16,且不超过 --max-cell-limit) |
--max-cell-limit | 10000 | 客户端可请求的最高 max_cell 值 |
--statement-timeout | 30s | 每条语句的服务端超时时间(Go 时长,必须大于 0) |
--allow-function | 无 | 受限模式下允许的函数名称或模式;可重复指定,支持 schema.name 和 * 通配符 |
--deny-function | 无 | 受限模式下拒绝的函数名称或模式;可重复指定,语法相同 |
--host | 127.0.0.1 | HTTP 传输:要绑定的地址 |
--port | 8000 | HTTP 传输:要监听的端口 |
--allow-remote | false | HTTP 传输:允许绑定非环回地址 |
--allowed-hosts | 无 | HTTP 传输:额外接受的 Host 值(host 或 host:port,逗号分隔列表) |
--allowed-origins | 无 | HTTP 传输:额外接受的 Origin 值(scheme://host[:port],逗号分隔列表) |
--sse-host | 无 | --host 的别名(兼容 crystaldba) |
--sse-port | 无 | --port 的别名(兼容 crystaldba) |
--version | 打印 postgres-dba-mcp <version> 并退出 |
环境变量:DATABASE_URI(连接字符串),以及驱动读取的 libpq 变量(PGPASSWORD、PGPASSFILE、PGSERVICEFILE、PGSSLMODE 等)。
工具
工具属于一个分组,可通过 --tools 关闭。
list_schemas(schema):列出架构及其所有者、类型和表数量。首行-- 2 schemas not readable by <role>: a, b表示该角色没有对所列架构的 USAGE 权限。list_objects(schema):列出架构中的表、视图、序列或扩展。表行数是规划器的估算值,并以~开头。首行-- 1 table not readable by <role>: t表示该角色无法对所列的表或视图执行 SELECT(最多显示 5 个名称,之后为…)。get_object_details(schema):以紧凑形式显示表、视图、序列或扩展的详情(列、键、索引、约束、触发器)。execute_sql(query):执行一条 SQL 语句;在受限模式下为只读,并限制结果范围。explain_query(explain):概述查询计划,可选使用analyze(仅限 SELECT)、verbose文本或假设索引。get_top_queries(stats):根据资源用量、总耗时或平均耗时,对当前数据库pg_stat_statements中的语句进行排序。如果没有pg_read_all_stats权限,PostgreSQL 会屏蔽其他角色的查询文本,输出也会说明这一点。analyze_db_health(health):检查索引、连接、清理和死行、序列、复制、缓存命中率及约束,并且仅报告问题。analyze_query_indexes(index):针对最多 10 条给定查询推荐索引。analyze_workload_indexes(index):针对pg_stat_statements中频繁出现的慢语句推荐索引。analyze_locks(locks):报告当前数据库正在发生的锁等待;每个被阻塞的会话及其直接阻塞者占一行,并包含每条阻塞链的根节点、锁及持续时间。waiting是被阻塞语句的运行时长,也是等待时长的上限,因为 PostgreSQL 不会记录锁等待的开始时间。该工具只读取信息,绝不会取消或终止会话。如果当前没有等待中的锁,则返回-- no lock wait in progress。即使没有pg_read_all_stats权限,该工具仍会报告 PID 和锁,但其他角色会话的持续时间、状态和查询文本会显示为\N。
假设索引和索引工具需要 hypopg 扩展;get_top_queries 和 analyze_workload_indexes 需要 pg_stat_statements(也需列在 shared_preload_libraries 中)。
输出格式
工具返回纯文本,不会返回结构化 JSON。
- 表格采用带表头行的 CSV 格式。字段包含逗号或双引号、有前导或尾随空格,或为空时,会加引号。
NULL写作\N。值中的换行符、回车符和制表符分别写作\n、\r和\t,因此每条记录都只有一行。- 单元格超过
--max-cell个字符时会被截断,并以…[+<n> chars]结尾;bytea值(十六进制文本)也适用。execute_sql接受max_cell(默认值为--max-cell,上限为--max-cell-limit);单元格缩短时,会添加页脚行,例如-- 1 cell shortened (txt); pass max_cell (up to 10000) to read more。 tsvector值会概述为[tsvector: <n> lexemes](只有一个词元时为[tsvector: 1 lexeme])。选择col::text可读取其内容。- 每张表格都以
--开头的页脚结束(list_schemas和list_objects也可能在表头前出现一行以--开头的注释):-- 12 rows, 38 ms、-- 100 rows, truncated (more than 100), 38 ms或-- 57 rows, truncated (32768-byte limit), 38 ms。结果被截断时,会添加一行建议优化查询。没有返回行的语句以其命令标签结束(-- UPDATE 3, 3 ms)。 explain_query以页脚-- plan, <duration>结束,表示整个调用的实际耗时。- 结果不会超过
--max-bytes,行数也不会超过max_rows(默认值为--max-rows)。 - 错误会作为工具错误返回(
isError: true),其文本以ERROR:开头;Postgres 提供时,还会附上(SQLSTATE <code>)、DETAIL:、HINT:和POSITION:行。对于超时语句,Postgres 不会报告限制值,此时会提供HINT:,指出--statement-timeout并说明如何限制查询范围。
示例:
id,name,created_at
1,account-1,2026-03-02 10:14:00+00
2,account-2,2026-03-02 10:14:00+00
-- 2 rows, 4 ms
安全模型
从外到内的各层:
- 数据库角色。 它决定可以读取哪些内容。请使用上面的只读角色。
- 只读事务。 除非在不受限模式下调用
execute_sql,否则每个工具都在BEGIN TRANSACTION READ ONLY中运行,且事务始终会回滚。Postgres 本身会拒绝写入操作。 - 语句检查(受限模式)。
execute_sql只接受一条SELECT类型的语句(包括VALUES、TABLE和WITH ... SELECT)、对上述语句执行的EXPLAIN,或SHOW。它会拒绝SELECT ... INTO、行锁定(FOR UPDATE)、包含数据修改的WITH、事务控制、SET、COPY及其他所有内容,并说明拒绝原因。语句通过扩展查询协议发送给 Postgres;该协议每条消息只承载一条语句,因此,检查放行的调用中无法附带第二条语句。拒绝信息中如果提到通过--access-mode=unrestricted可以运行该语句(下方拒绝列表中的函数除外),则表示正是该模式会拒绝它。 - 函数拒绝列表,检查整个语句。 语句中命名的每个函数,无论出现在何处,都会被检查:无论是在
FROM中、子查询中、CTE 中,还是参数中。如果语句没有命名某个函数,而是通过运算符、类型转换或视图调用它,则不会检查该函数(参见下方的限制)。内置列表涵盖大型对象相关函数(lo_*)、set_config、咨询锁、pg_notify、后端取消和终止、服务器文件读取(pg_read_file、pg_ls_*)、复制槽和 WAL 控制、统计信息重置、索引维护(brin_summarize_*、gin_clean_pending_list)、规划器统计信息写入(pg_restore_*_stats、pg_clear_*_stats)、pg_upgrade和initdb辅助函数、dblink*、query_to_xml*、ts_rewrite、tablefunc和xml2中处理 SQL 字符串的函数(crosstab*、connectby、xpath_table),以及风险扩展中的文件或 HTTP 函数。PostgreSQL 14 到 18 中pg_catalog的每个易变函数要么列在此列表中,要么已审核并确认为安全;任何新增函数都会导致测试失败。 - 对其他所有函数进行易变性检查(受限模式)。 只读事务会拒绝 SQL 写入,但无法阻止扩展在 C 代码、其他会话或数据库外部执行操作。因此,若上述规则都无法确定某个调用是否安全,系统会在即将运行该语句的事务中查询
pg_proc,并在发现该调用涉及 PostgreSQL 未自带且未经审核放行的VOLATILE函数时拒绝执行。IMMUTABLE和STABLE函数会通过检查,因此 PostGIS、pgvector 和你自己的只读函数仍可使用。未显式声明易变性的函数(CREATE FUNCTION的默认值为VOLATILE)会被拒绝,并显示其名称:如果函数只读取数据,请将其声明为STABLE;也可以传入--allow-function。解析会遵循搜索路径;由于目录查询无法获知服务器根据参数类型会选择哪个重载,因此只要搜索路径可达的同名函数中有一个是易变且未经审核的,该名称就会被拒绝:在搜索路径中的某个模式里重新定义内置函数名后,对该名称的调用会被拒绝,而不会获准。只有会求值其函数的语句才会执行此项查询。 - 服务器端限制。 每个事务都会设置
statement_timeout(--statement-timeout)、lock_timeout(10 s)和idle_in_transaction_session_timeout(60 s)。连接返回连接池时会进行清理(DISCARD ALL、hypopg_reset());连接池最多包含 5 个连接,因此会话状态不会从一次调用泄漏到下一次调用。 - 有界输出。 行数、字节数和单元格长度均有限制(参见“输出格式”)。发送给
execute_sql的SELECT会通过服务器端游标读取,每次最多提取max_rows + 1行,因此 Postgres 不会计算更多行,而且语句绝不会被重写为附带LIMIT。其他工具会限制各自的查询。--allow-function和--deny-function会同时调整拒绝列表和门控规则:用户拒绝模式优先于用户允许模式,用户允许模式优先于内置列表,内置列表优先于门控规则。允许模式重新启用内置拒绝项时,启动时会在 stderr 上提示。
受限模式无法防护的情况:
- 读取。 角色可以读取的任何表或列都可被读取。请使用该角色(参见「敏感列」)。
- 你担保的函数。 门控规则会拒绝它不认识的
VOLATILE函数,但你声明为STABLE或通过--allow-function允许的函数,都会被视为可信,包括SECURITY DEFINER函数。只有会读取数据的函数才标记为STABLE。 - 未被直接命名的函数。 视图的主体、运算符和类型转换都会运行语句中未直接命名的函数,因此不会被任何检查发现;同样,调用易变函数的
STABLE函数也有这个问题,因为 PostgreSQL 会拒绝其中的直接写入,却不会拒绝间接副作用。每种情况都需要有人先在数据库中创建相应对象,因此防护措施是使用受限角色。 - 开销过大的查询。 耗时但合法的
SELECT会一直运行,直到触发--statement-timeout。 - 不受限模式。
--access-mode=unrestricted允许execute_sql使用该角色的权限运行任意单条语句,并提交更改。截断限制的是读取内容,而不是实际执行内容:语句中直接命名的函数会运行到结束,因此数据库会执行全部内容,而不只是你看到的行。函数调用隐藏在视图、运算符或类型转换后的语句,则会在获取到的行处停止。语句在事务中运行,因此拒绝在事务块中运行的命令(VACUUM、CREATE INDEX CONCURRENTLY、REINDEX CONCURRENTLY、ALTER SYSTEM、CREATE DATABASE)会报错 "cannot run inside a transaction block",与 0.3.0 中的行为一致。启动时会在 stderr 上发出警告。 - HTTP 传输没有身份验证(见下文)。
HTTP 传输
--transport=streamable-http 会在 http://<host>:<port>/mcp 提供 MCP 服务。--transport=sse 会在 /sse 提供已弃用的 SSE 端点,供 crystaldba 客户端使用。默认情况下,服务会绑定到 127.0.0.1:8000。
postgres-dba-mcp --transport=streamable-http --port=8000 postgres://pgdba_ro@localhost:5432/pgdba
没有身份验证。因此,服务会:
- 仅绑定到回环地址。除非指定
--allow-remote,否则拒绝非回环地址的--host;指定后会记录警告; - 拒绝
Host标头不是localhost、127.0.0.1、[::1](带监听端口)、绑定主机或--allowed-hosts条目的请求(403); - 拒绝
Origin标头不是http://localhost:<port>、http://127.0.0.1:<port>、http://[::1]:<port>或--allowed-origins条目的请求(403)。没有Origin的请求(原生客户端)可通过; - 将请求正文限制为 1 MiB。
在 Docker 中,服务必须监听容器的所有网络接口,因此需要指定 --host=0.0.0.0 --allow-remote。只在主机的回环地址上发布端口,因为任何能访问该端口的人都能使用此角色访问数据库:
docker run --rm -p 127.0.0.1:8000:8000 \
-e DATABASE_URI=postgres://pgdba_ro:[email protected]:5432/pgdba \
ghcr.io/a-lafon/postgres-dba-mcp:latest \
--transport=streamable-http --host=0.0.0.0 --allow-remote --port=8000
在 Linux 上,将 host.docker.internal 替换为主机在桥接网络上的地址,或使用 --network=host 运行。客户端发送的 Host 标头包含主机端口,因此发布的主机端口必须与 --port 相同(-p 8000:8000)。如果使用不同的端口映射,例如 -p 9000:8000,则添加 --allowed-hosts=localhost:9000。
迁移与安全政策
- 从 crystaldba/postgres-mcp 0.3.0 迁移:参见 MIGRATION.md,其中列出了所有可观察到的差异。
- 报告漏洞:参见 SECURITY.md。
- 参与代码开发:参见 CONTRIBUTING.md。
- 由一人尽力维护;欢迎提交问题和拉取请求。
许可证
MIT,参见 LICENSE。部分 SQL 改编自 crystaldba/postgres-mcp(MIT,Crystal Corp.),部分改编自 PgHero(MIT,Andrew Kane);两者的许可证副本均收录于 NOTICE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。