mcpField
P
Postgres (read-only)

Postgres (read-only)

本地服务
contextflo.com数据库44 周前更新

关于 Postgres (read-only)

Postgres (read-only) 是面向分析和 Agent 数据库探索的 Postgres MCP 服务。它通过多层机制限制写入,并使用可编辑的上下文文件记录团队特有的架构说明和业务定义。

主要功能

  1. 01只读 SQL 查询
  2. 02多层写入防护
  3. 03表与架构发现工具
  4. 04可编辑的共享上下文文件
  5. 05Agent 添加表说明
  6. 06限制查询结果和执行时长

适用场景

  1. 01探索数据库表以开展分析
  2. 02在不授予写权限时查询数据
  3. 03向 Agent 提供业务定义
  4. 04记录未文档化的表或列含义

MCP 相关

该 MCP 服务提供只读查询、架构发现及表上下文工具,并将表架构作为 MCP 资源提供。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"contextflo-postgres": {
"command": "npx",
"args": [
"-y",
"@contextflo/postgres-mcp"
],
"env": {
"DATABASE_URL": "<DATABASE_URL>"
}
}
}
}
启动命令npx -y @contextflo/postgres-mcp
需要配置的环境变量
DATABASE_URL密钥Postgres connection string. Use a read-only role.

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@contextflo/postgres-mcp0.1.4

工具

4

收录的工具列表 · 更新于 2 周前

参数类型必填说明
sqlstring是单条只读 SQL 语句。

详细介绍

@contextflo/postgres-mcp

适用于 Postgres 的分析型 MCP 服务。默认只读,并配有一个由你和 Agent 共同编写的上下文文件,让模型给出符合团队习惯的回答。

由 Contextflo 构建并维护。

可直接替代已归档的 @modelcontextprotocol/server-postgres。该服务器曾存在 SQL 注入漏洞,攻击者可以让 COMMIT; DROP SCHEMA public CASCADE 直接绕过只读事务。

观看 100 秒演示:设置、Agent 发现 events 表并非表面看起来的那样后将其记录为备注,以及服务器拒绝执行的 DROP。

npx @contextflo/postgres-mcp postgresql://localhost/mydb

为什么选择它

真正可靠的只读保护。 已归档的服务器通过 SQL 字符串 属性强制执行只读。此处的只读保护由线协议、连接、Postgres 解析器和数据库角色共同保障:四个独立层级各自都能拦截该攻击载荷。本仓库将此漏洞利用作为测试用例。

给出靠谱的答案。 如果模型不知道 fct_orders_v2 才是团队实际使用的表,或者 revenue 指的是毛收入而非净收入,它就会写出自信却错误的 SQL。.contextflo/context.md 是一个由你编辑的 Markdown 文件,此服务器会将其交给模型,背后没有其他依赖,只有这个文件。Agent 也会向其中添加内容:当它发现架构中未提及的信息时,add_table_context 会追加一条备注,你可以像审查其他更改一样通过 diff 检查。

设置

1. 创建只读角色。 以数据库所有者身份,在 psql 或提供商的 SQL 编辑器中运行。使用你的数据库名称和真实密码,并为 Agent 应查看的每个架构重复相应的架构行:

CREATE ROLE mcp_readonly LOGIN PASSWORD 'change-me';
GRANT CONNECT ON DATABASE mydb TO mcp_readonly;
GRANT USAGE ON SCHEMA public TO mcp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO mcp_readonly;

这样,只读保护就由数据库本身保障,而不仅仅依赖此服务器的代码。

2. 将该角色的连接字符串放入项目文件夹中的 .env:

DATABASE_URL='postgresql://mcp_readonly:[email protected]:5432/mydb'

请保留引号:托管服务提供商会添加 ?sslmode=require&...,其中的 & 需要用引号括起来。服务器会从其启动目录中的 .env 读取 DATABASE_URL,因此连接字符串不会出现在命令行中。

如果可以,请将其指向只读副本或分支,而不是主库。只读保护会阻止写入,但不会限制负载:Agent 探索数据时可能会执行全表扫描或开销很大的联接,这会在主库上与应用争抢资源。Neon 和 Supabase 只需几次点击就能创建数据库分支或副本,RDS、Cloud SQL 和大多数其他托管服务也提供只读副本。语句超时(默认 30 秒,可通过 --statement-timeout 配置)和行数上限会限制单次查询的运行时长和返回数据量,但不会降低查询成本。

3. 生成上下文文件:

npx @contextflo/postgres-mcp init

此命令会根据你的 COMMENT ON 值生成 .contextflo/context.md。请编辑此文件:业务定义部分最有价值。

4. 将其添加到客户端。 Claude Code 会在项目文件夹中启动服务,因此会自动找到 .env 和上下文文件:

claude mcp add postgres --scope project -- npx -y @contextflo/postgres-mcp

--scope project 会将 .mcp.json 写入当前文件夹,而不是写入全局配置。

Cursor、Claude Desktop、VS Code

其他客户端可能会在项目文件夹之外启动服务(Claude Desktop 会在 / 中启动),因此需要明确提供连接字符串和上下文文件:

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "@contextflo/postgres-mcp", "--context-file", "/path/to/project/.contextflo/context.md"],
      "env": { "DATABASE_URL": "postgresql://mcp_readonly:[email protected]:5432/mydb" }
    }
  }
}

Cursor 使用 .cursor/mcp.json。Claude Desktop 使用 claude_desktop_config.json(macOS: ~/Library/Application Support/Claude/,Windows:%APPDATA%\Claude\)。VS Code 使用 .vscode/mcp.json,并以 servers 替代 mcpServers。

## 工具
工具功能
query执行一条只读语句:SELECT、WITH ... SELECT、EXPLAIN 或 SHOW。
list_tables列出可读取的表及其说明。pattern 会匹配名称或说明中的任意位置。
get_table_context描述表:列、类型、键、外键目标、枚举值和精选说明。
add_table_context让 Agent 将发现的注意事项(例如,amount 的单位是分,status 有一个未记录的值)写入上下文文件。

这里没有单独的搜索工具,这是有意为之。information_schema 和 pg_catalog 都是普通表,因此,更具体的操作,例如查找所有名称包含 %revenue% 的列,或列出没有主键的表,都可以由模型自行编写查询:

SELECT table_schema, table_name, column_name
FROM information_schema.columns
WHERE column_name ILIKE '%revenue%';

表架构也会作为 postgres://<host>/<table>/schema 资源公开,与已归档的服务保持一致,供固定使用这些 URI 的场景使用。大多数客户端不会自行获取资源,因此发现功能由工具提供。

如何强制只读

共四层。每一层都足以单独阻止已归档服务中的漏洞。

1. 扩展查询协议。 用户 SQL 会通过 pg-cursor 执行,它始终发送 Parse/Bind/Execute,因此 Postgres 本身会拒绝多语句输入。已归档的服务调用 client.query(sql) 并传入纯字符串;node-postgres 只有在存在绑定值时才会准备语句,因此这会走 simple 协议路径,其中 ; 用于分隔语句。这就是整个漏洞。

2. 连接级只读。 启动数据包中会设置 default_transaction_read_only=on,并且每条语句都会在显式的 BEGIN READ ONLY 中运行,结束时始终执行 ROLLBACK,绝不执行 COMMIT。回滚还会撤销事务中执行的任何 SET,因此语句无法削弱连接池中的连接,影响下一个获取该连接的人。

3. 基于真实 Postgres 解析器的语句允许列表。 libpg-query 是编译为 WASM 的真正 Postgres C 解析器,而不是对 SQL 的 JavaScript 近似实现。它会遍历整个解析树,而不只是顶层节点,因此能捕获会修改数据的 CTE:

WITH x AS (INSERT INTO users VALUES (1) RETURNING *) SELECT * FROM x

它会被解析为 SelectStmt。只检查语句类型的验证器会执行它。遇到未知节点类型时则默认拒绝。

函数会根据数据库自身的目录进行检查。Postgres 会将每个函数标记为 immutable、stable 或 volatile,只有 volatile 函数可能产生副作用,因此除非 volatile 函数位于一份简短的无害函数列表中(分析所需,如 random()、clock_timestamp()、表大小函数),否则都会被拒绝。这能涵盖 dblink、pg_logical_emit_message(即使在只读事务中也会写入 WAL)、咨询锁、统计信息重置,以及未来 Postgres 新增的功能,无需逐一列举。SECURITY DEFINER 函数无论被标记为何种属性都会被拒绝,因为这类函数会以其所有者的权限运行。此外,还会检查一份固定的已知绕过方式列表。

4. 只读数据库角色。 上述各层都是代码,而代码总会有漏洞。Postgres 会强制要求角色无法写入,无论其他情况如何,这也是为什么设置的第一步是创建这样的角色。如果你以超级用户身份连接,服务会在启动时发出警告;如果 init 连接时所用的角色可以写入,它会打印角色配置片段。

这不能防范的情况。 函数检查会查看查询直接调用的函数,并信任每个函数的属性标记。声明为 STABLE、却仍然写入数据的用户自定义函数会被允许;通过间接方式调用的函数也会被允许,例如位于视图中、隐藏在运算符后,或通过类型转换调用。只读事务仍会拒绝以这些方式更改表数据的操作;可能漏过的是较少见的副作用,例如向 WAL 写入消息。第 4 层负责阻止这类情况,因此推荐使用只读角色,而不是把它当作可选项。只读也不等于保密:连接角色能读取的任何内容,模型都能读取,因此只授予 Agent 你希望其查看的内容。

从 @modelcontextprotocol/server-postgres 迁移

替换软件包名称即可。工具仍名为 query,仍接收 sql,仍返回 JSON 行,连接字符串仍是第一个参数。

有四项刻意的差异:

  1. 拒绝多语句 SQL 和 SET/RESET,并给出明确错误。在已归档的服务中,这些操作“能用”,而这正是漏洞所在。
  2. 结果默认限制为 1000 行和 50,000 个字符,超过 2,000 个字符的单个值会缩短。输出会注明截断,不会静默处理。返回的每行各占一行,而非美化打印,因此令牌开销大约减半;结果仍是 JSON 数组。日期和时间戳与 Postgres 返回的完全一致,不会按服务的时区重新渲染。
  3. 架构发现是一个工具,而不只是资源。 大多数客户端不会自动附加资源,因此使用旧服务的模型常常不知道架构。
  4. 所有非系统架构都可见,而不只是 public,并且会提供 COMMENT ON 中的列说明。

选项

--max-rows <n>            Maximum rows returned per query (default: 1000)
--max-output-chars <n>    Character budget for one query result (default: 50000)
--statement-timeout <ms>  Server-side statement timeout (default: 30000)
--context-file <path>     Curated schema context (default: .contextflo/context.md)
--no-context-writes       Do not offer add_table_context; the context file is only read
--log-file <path>         Query audit log (default: .contextflo/log.md once that directory exists)
--no-log                  Never write a query log
--http                    Serve over streamable HTTP instead of stdio
--port <n>                HTTP port (default: 8080)
--host <addr>             HTTP bind address (default: 127.0.0.1)

如果不传入连接字符串,DATABASE_URL 会从环境变量或当前目录中的 .env 提供连接字符串。服务会在首次使用时连接:即使没有连接字符串,它也会启动并列出其工具,每次工具调用都会说明缺少什么。使用 --http 时,AUTH_TOKEN 会要求请求携带该值作为 bearer token。

连接池代理。 PgBouncer(以及 Supabase、Neon 等提供的连接池连接字符串)会拒绝此服务通常发送的启动参数。发生这种情况时,服务会在不带这些参数的情况下重新连接,并在 stderr 中说明。安全性不会降低:每条语句仍会在 BEGIN READ ONLY 中执行,并设置独立的语句超时;CI 也会通过事务模式下的 PgBouncer 运行只读测试套件。

上下文文件

# Database context

Revenue means gross, before refunds.
"Active customer" means an order in the last 90 days.

## Tables

### public.orders
One row per customer order. Source of truth for revenue, not `orders_legacy`.

- revenue_usd: Gross revenue, before refunds. Net lives in `order_refunds`.
- status: One of pending, paid, refunded.

## Tables 以上的所有内容都会交给模型:既作为服务指令,也会再次出现在 list_tables 的顶部,因为有些客户端从不向模型显示服务指令。HTML 注释会被排除。在 ## Tables 下,### 标题用于命名表,其下的文字用于描述表,- column: meaning 行用于描述列(也可以用破折号分隔)。你写的内容优先于 COMMENT ON。编辑会在下一次工具调用时生效;无需重启。

相对路径的 --context-file 会相对于客户端启动服务的目录解析。Claude Desktop 会在 / 中启动服务,因此在那里会退而使用你的主目录(~/.contextflo/context.md)。如果文件位于代码仓库中,请传入绝对路径。服务启动时会打印正在使用的路径。

Agent 会补充文件内容。 当模型发现架构未说明的信息(amount 的单位是分,旧记录中的 status 也可能是 'void',每个查询都需要 deleted_at IS NULL)时,add_table_context 会将其追加到此文件中,让之后的每个会话都能了解这些信息。备注只会追加,绝不会替换你写的内容,而且仅针对实际存在的表和列。请像对待其他变更一样处理这些备注:将文件保存在 git 中并查看差异。模型可能会出错,而且数据中的文本可能会引导它写入内容,因此在有人检查之前,备注都只是建议。--no-context-writes 可关闭此工具。

init 会根据现有注释生成初始内容,并且只处理已有注释的列。为所有 4000 列都放入空白占位符的文件,没人会去编辑。

contextflo.com 可根据你的代码和文档生成更丰富的上下文,并在团队中共享。

查询日志

.contextflo/ 存在后,每条语句都会连同执行结果、行数和耗时追加到 .contextflo/log.md。此日志供你查看,不会反馈给模型。--no-log 可关闭此功能。

Docker

docker run -i --rm ghcr.io/contextflo/postgres-mcp postgresql://host.docker.internal/mydb

如需远程端点,请参阅 docker-compose.example.yml。绑定到 0.0.0.0 前,请先了解自己在做什么:这会在一个端口上建立与实时数据库的连接。请设置 AUTH_TOKEN,将其限制在私有网络内,并在其前面终止 TLS,否则令牌会以明文形式通过网络传输。服务器在未设置令牌却暴露时会打印警告。

使用默认的环回绑定时,Host 或 Origin 非本地的请求会被拒绝。这样可以防止网页利用 DNS 重绑定,通过浏览器访问 127.0.0.1 上的服务。

开发

npm install
npm run build
npm test

npm run db:up   # throwaway Postgres for the integration suite (needs Docker)
TEST_DATABASE_URL=postgres://postgres:postgres@localhost:55432/postgres npm test
npm run db:down

集成测试套件会刻意绕过解析器,直接驱动数据库,因此它验证的是网络传输层和事务层,而不是验证器。未设置 TEST_DATABASE_URL 时会跳过,并且必须在 CI 中运行。

安全

请私下报告漏洞。请参阅 SECURITY.md。

许可证

MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。