内容
# PostgreSQL 只读 MCP 服务器
[中文](README.zh.md)
一个用于发现一个 PostgreSQL 17+ 实例并运行一个有意为之的狭窄 SELECT 子集的 MCP(模型上下文协议)服务器。它通过 stdio 或 HTTP 公开 MCP 并包含一个 CLI。
## 快速开始
为一个权限最小的 PostgreSQL 角色设置一个连接目标,然后构建并检查目录:
```sh
export DATABASE_URL='postgres://reader@127.0.0.1:5432/app?sslmode=disable'
make build
./bin/postgres-mcp-server tools list
./bin/postgres-mcp-server mcp
```
`tools list` 和 `tools describe` 不读取数据库连接设置。启动 MCP/HTTP 或调用工具会验证连接并在报告就绪之前验证服务器和角色。
## 使用发布版本
版本 `v0.0.1` 可作为 npm 包和多架构 Docker 镜像提供。两个发布入口点默认启动 MCP over stdio。
### npm
使用 `npx` 运行已发布的包:
```sh
export DATABASE_URL='postgres://reader@127.0.0.1:5432/app?sslmode=disable'
npx -y @futuretea/postgres-mcp-server@0.0.1
```
对于 MCP 客户端配置,请使用:
```json
{
"mcpServers": {
"postgres-readonly": {
"command": "npx",
"args": ["-y", "@futuretea/postgres-mcp-server@0.0.1"],
"env": {
"DATABASE_URL": "postgres://reader@127.0.0.1:5432/app?sslmode=disable"
}
}
}
}
```
npm 命令是 MCP 入口点,而不是服务器其他 CLI 子命令的包装器。请固定您打算运行的包版本。
### Docker
直接运行已发布的镜像:
```sh
export DATABASE_URL='postgres://reader@db.example.internal:5432/app?sslmode=require'
docker run --rm -i \
--env DATABASE_URL \
ghcr.io/futuretea/postgres-mcp-server:v0.0.1
```
对于启动 Docker 的 MCP 客户端,请通过 Docker 进程传递连接设置:
```json
{
"mcpServers": {
"postgres-readonly": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"--env",
"DATABASE_URL",
"ghcr.io/futuretea/postgres-mcp-server:v0.0.1"
],
"env": {
"DATABASE_URL": "postgres://reader@db.example.internal:5432/app?sslmode=require"
}
}
}
}
```
容器内的数据库主机名必须可以从容器内访问。容器内的 `localhost` 不是主机;请使用 Docker 网络上的数据库主机名,或在适当的情况下使用平台特定的主机网关。
Docker 示例使用 `sslmode=require` 连接到远程数据库。请设置您的 PostgreSQL TLS 模式和部署所需的证书。
### HTTP 模式
在发布命令后传递 MCP 标志以启动 HTTP 而不是 stdio。在主机上,默认监听器是环回地址:
```sh
export DATABASE_URL='postgres://reader@127.0.0.1:5432/app?sslmode=disable'
npx -y @futuretea/postgres-mcp-server@0.0.1 --port 8080
```
对于 Docker,服务器必须监听所有容器接口,而发布的端口可以仅在主机上保持环回地址:
```sh
export DATABASE_URL='postgres://reader@db.example.internal:5432/app?sslmode=require'
docker run --rm \
--env DATABASE_URL \
--publish 127.0.0.1:8080:8080 \
ghcr.io/futuretea/postgres-mcp-server:v0.0.1 \
--port 8080 --listen 0.0.0.0
```
Streamable HTTP MCP 客户端使用 `http://127.0.0.1:8080/mcp`。服务器还在 `/sse` 提供 SSE 消息,在 `/message` 提供消息,并在 `/healthz` 提供健康检查。HTTP 没有身份验证或 TLS;请将其放在受信任的网络中或放在提供两者保护的基础设施后面。
## 工具
| 工具 | 参数 | 目的 |
|---|---|---|
| `postgres_get_server_info` | 无 | 返回服务器和当前连接标识。 |
| `postgres_list_databases` | 无 | 列出可见的数据库。 |
| `postgres_list_schemas` | 无 | 列出可见的模式。 |
| `postgres_list_tables` | `schema` | 列出模式中的表和视图。 |
| `postgres_describe_table` | `schema`, `table` | 描述表列。 |
| `postgres_list_indexes` | `schema`, `table` | 列出表的索引。 |
| `postgres_query` | `sql` | 运行一个严格的只读 SELECT 查询。 |
每个成功的工具响应都是一个包含 `schema_version`、`tool` 和 `data` 的 JSON 文本信封。参数必须是 JSON 对象且不包含未知键。空参数工具需要 `{}`;缺失、`null`、数组或标量参数将被拒绝。
## 数据库连接
使用纯 `DATABASE_URL` 或标准 PostgreSQL 环境变量,如 `PGHOST`、`PGPORT`、`PGDATABASE`、`PGUSER`、`PGPASSWORD` 和 `PGSSLMODE`。当设置 `DATABASE_URL` 时,它优先于其他设置。配置必须解析为一个 `host:port`;在创建连接之前,会拒绝不同的 URL 或逗号分隔的 `PGHOST` 目标。
启动时,服务器需要 PostgreSQL 17 或更高版本,并拒绝具有超级用户权限、绕过行级安全、复制或属于服务器文件、服务器程序或后端信号角色的服务角色。请使用专用的、权限最小的数据库角色。
### 创建只读服务角色
在 `psql` 中以数据库管理员身份运行以下命令。请将 `app` 和 `public` 替换为您的数据库和模式。这会创建一个新的登录角色,没有继承的权限;请不要重用管理员或应用程序所有者角色。
```sql
CREATE ROLE mcp_reader
LOGIN
NOSUPERUSER NOCREATEDB NOCREATEROLE
NOINHERIT NOREPLICATION NOBYPASSRLS;
\password mcp_reader
GRANT CONNECT ON DATABASE app TO mcp_reader;
\connect app
GRANT USAGE ON SCHEMA public TO mcp_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_reader;
```
### 验证有效模式权限
在配置 `DATABASE_URL` 之前,请验证 `mcp_reader` 无法在目标模式中创建对象:
```sql
SELECT has_schema_privilege('mcp_reader', 'public', 'CREATE') AS can_create_objects;
```
结果必须为 `false`。结果为 `true` 表示该角色未被限制为该模式的只读访问。这可能发生在从 PostgreSQL 14 或更早版本升级的安装中,该安装保留了共享的 `PUBLIC` `CREATE` 授予 `public`。请不要仅为此服务器撤销共享的 `PUBLIC` 特权;请数据库管理员首先决定数据库范围的模式策略。
`\password` 命令提示输入密码而不将密码放入 shell 历史记录中。请仅在运行时秘密或环境管理器中存储生成的连接秘密,而不要存储在存储库中。如果角色必须读取将来创建的表,并且 `postgres` 创建了这些表,请以 `postgres` 身份运行此可选命令。请在其他环境中将 `postgres` 替换为实际的对象创建角色。
```sql
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public
GRANT SELECT ON TABLES TO mcp_reader;
```
在配置 `DATABASE_URL` 之前,请以 `mcp_reader` 身份连接并验证以下所有布尔值均为 `false`:
```sql
SELECT current_user, rolsuper, rolbypassrls, rolreplication,
pg_has_role(current_user, 'pg_read_server_files', 'member') AS read_server_files,
pg_has_role(current_user, 'pg_write_server_files', 'member') AS write_server_files,
pg_has_role(current_user, 'pg_execute_server_program', 'member') AS execute_server_program,
pg_has_role(current_user, 'pg_signal_backend', 'member') AS signal_backend
FROM pg_roles
WHERE rolname = current_user;
```
如果任何值为 `true`,请使用新的专用角色,而不是从共享管理员或应用程序角色中删除权限。
## 只读边界和部署风险
每个数据库操作都使用 PostgreSQL 只读事务和 pgx 的扩展执行协议。`postgres_query` 被解析为确切的一个 SELECT,拒绝 DML、DDL、行锁、SELECT INTO、修改 CTE 和选定的 PostgreSQL 内置函数,这些函数具有已知的副作用。
这并不能完全证明每个用户定义的函数、扩展、FDW 或未来的 PostgreSQL 内置函数都是无害的。请仅授予服务器打算拥有的数据库访问权限。
HTTP 传输没有身份验证或 TLS 层。默认监听器是环回地址。如果您将 `--listen` 设置为远程地址,请仅将服务放在受信任的网络中或放在提供所需保护的基础设施后面。该服务器故意没有查询超时、行限制、响应大小限制或并发预算。
## 运行 MCP 和 HTTP
```sh
# stdio MCP
./bin/postgres-mcp-server mcp
# HTTP MCP on loopback
./bin/postgres-mcp-server mcp --port 8080
# Explicit remote listener; see the trusted-network requirement above
./bin/postgres-mcp-server mcp --port 8080 --listen 0.0.0.0
```
HTTP 健康检查端点是 `GET /healthz`。请参见 `config.example.yaml` 获取传输和工具过滤器设置;数据库连接设置仍保留在环境中。
## CLI
```sh
./bin/postgres-mcp-server tools list --json
./bin/postgres-mcp-server tools describe postgres_query --json
./bin/postgres-mcp-server tools call postgres_list_schemas --params '{}'
./bin/postgres-mcp-server tools call postgres_query --params '{"sql":"SELECT 1"}'
./bin/postgres-mcp-server version
```
## 容器
`make docker` 首先构建 Alpine CGO 解析器检查目标,然后构建可运行的镜像:
```sh
make docker
docker run --rm -i --env DATABASE_URL postgres-mcp-server:dev
```
镜像入口点是 `postgres-mcp-server mcp`.
## 开发检查
```sh
make format
make lint
make test
make test-integration # 需要运行的 Docker 守护进程;创建 PostgreSQL 17 固定装置
make build
make docker
```