Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 52 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The official `@modelcontextprotocol/server-postgres` supports only **one databas
- **One config file** with all connections
- **Hot reload** — add a database to config, it's immediately available (no restart)
- **`--label` filter** — restrict access to a single database per project (isolation)
- **Read-only** — all queries run inside `BEGIN TRANSACTION READ ONLY` (safety)
- **Read-only by default** — queries run inside `BEGIN TRANSACTION READ ONLY` unless a connection sets `readOnly: false`

### Architecture

Expand Down Expand Up @@ -47,8 +47,8 @@ The official `@modelcontextprotocol/server-postgres` supports only **one databas

| Tool | Parameters | Description |
|---|---|---|
| `pg_list_databases` | — | List all available databases |
| `pg_query` | `database`, `query` | Execute read-only SQL query |
| `pg_list_databases` | — | List all available databases with `RO`/`RW` access mode |
| `pg_query` | `database`, `query` | Execute SQL query (`readOnly: true` blocks writes; `readOnly: false` allows writes) |
| `pg_list_tables` | `database`, `schema?` | List tables with row counts |
| `pg_describe_table` | `database`, `table`, `schema?` | Column types, PK, FK, defaults |

Expand Down Expand Up @@ -76,7 +76,8 @@ Create `~/.mcp-postgres/config.json` (or any path you like):
"user": "readonly_user",
"password": "secret",
"database": "myapp",
"enabled": true
"enabled": true,
"readOnly": true
},
{
"label": "staging",
Expand All @@ -85,12 +86,15 @@ Create `~/.mcp-postgres/config.json` (or any path you like):
"user": "dev",
"password": "dev123",
"database": "myapp_staging",
"enabled": true
"enabled": true,
"readOnly": false
}
]
}
```

`readOnly` defaults to `true`. Set it to `false` only for databases where write queries are intentionally allowed.

Object format also supported (keyed by any identifier):

```json
Expand Down Expand Up @@ -214,8 +218,8 @@ AI calls: pg_query(database="production", query="SELECT * FROM orders ORDER BY c

### Security

- All queries execute inside `READ ONLY` transactions
- Write operations (`INSERT`, `UPDATE`, `DELETE`, `DROP`) will fail
- Connections are read-only by default and execute inside `READ ONLY` transactions
- Write operations (`INSERT`, `UPDATE`, `DELETE`, `DROP`) fail unless that connection sets `readOnly: false`
- Connection timeout: 10 seconds
- Query timeout: 30 seconds
- Config file with passwords is excluded from git via `.gitignore`
Expand All @@ -229,7 +233,7 @@ AI calls: pg_query(database="production", query="SELECT * FROM orders ORDER BY c
| Config format | Connection string in args | JSON file |
| Hot reload | No (restart required) | Yes |
| Per-project isolation | N/A | `--label` filter |
| Query safety | Full access | Read-only only |
| Query safety | Full access | Read-only by default; per-connection write mode with `readOnly: false` |
| Processes needed for 5 DBs | 5 | 1 |

---
Expand All @@ -245,7 +249,7 @@ AI calls: pg_query(database="production", query="SELECT * FROM orders ORDER BY c
- **Один конфиг** со всеми подключениями
- **Hot reload** — добавил БД в конфиг, она сразу доступна (без рестарта)
- **Фильтр `--label`** — ограничивает доступ одной БД для проекта (изоляция)
- **Только чтение** — все запросы в `BEGIN TRANSACTION READ ONLY` (безопасность)
- **Только чтение по умолчанию** — запросы выполняются в `BEGIN TRANSACTION READ ONLY`, если подключение не указано с `readOnly: false`

### Архитектура

Expand Down Expand Up @@ -275,8 +279,8 @@ AI calls: pg_query(database="production", query="SELECT * FROM orders ORDER BY c

| Инструмент | Параметры | Описание |
|---|---|---|
| `pg_list_databases` | — | Список доступных БД |
| `pg_query` | `database`, `query` | Read-only SQL запрос |
| `pg_list_databases` | — | Список доступных БД с режимом доступа `RO`/`RW` |
| `pg_query` | `database`, `query` | SQL запрос (`readOnly: true` запрещает запись; `readOnly: false` разрешает запись) |
| `pg_list_tables` | `database`, `schema?` | Таблицы с количеством строк |
| `pg_describe_table` | `database`, `table`, `schema?` | Колонки, типы, PK, FK, defaults |

Expand Down Expand Up @@ -304,12 +308,25 @@ npm install
"user": "readonly_user",
"password": "secret",
"database": "myapp",
"enabled": true
"enabled": true,
"readOnly": true
},
{
"label": "staging",
"host": "localhost",
"port": 5432,
"user": "dev",
"password": "dev123",
"database": "myapp_staging",
"enabled": true,
"readOnly": false
}
]
}
```

`readOnly` по умолчанию равен `true`. Указывайте `false` только для БД, где запись через MCP действительно нужна.

#### 3. Добавьте в Claude Code

Одной командой:
Expand Down Expand Up @@ -368,13 +385,13 @@ MCP_POSTGRES_CONFIG=/path/to/config.json node dist/index.js
| Формат конфига | Connection string в аргументах | JSON файл |
| Hot reload | Нет (нужен рестарт) | Да |
| Изоляция по проектам | Нет | Фильтр `--label` |
| Безопасность запросов | Полный доступ | Только чтение |
| Безопасность запросов | Полный доступ | Только чтение по умолчанию; режим записи через `readOnly: false` для конкретного подключения |
| Процессов для 5 БД | 5 | 1 |

### Безопасность

- Все запросы выполняются в `READ ONLY` транзакциях
- Операции записи (`INSERT`, `UPDATE`, `DELETE`, `DROP`) завершатся ошибкой
- Подключения по умолчанию работают в `READ ONLY` транзакциях
- Операции записи (`INSERT`, `UPDATE`, `DELETE`, `DROP`) завершатся ошибкой, если для подключения не указано `readOnly: false`
- Таймаут подключения: 10 сек, таймаут запроса: 30 сек
- Конфиг с паролями исключён из git через `.gitignore`
- `--label` не даёт ИИ видеть чужие базы данных
Expand All @@ -392,7 +409,7 @@ MCP_POSTGRES_CONFIG=/path/to/config.json node dist/index.js
- **一个配置文件**包含所有连接
- **热重载** — 在配置中添加数据库,立即可用(无需重启)
- **`--label` 过滤器** — 限制项目只能访问单个数据库(隔离)
- **只读模式** — 所有查询在 `BEGIN TRANSACTION READ ONLY` 中执行(安全)
- **默认只读模式** — 查询在 `BEGIN TRANSACTION READ ONLY` 中执行,除非连接配置为 `readOnly: false`

### 架构

Expand Down Expand Up @@ -422,8 +439,8 @@ MCP_POSTGRES_CONFIG=/path/to/config.json node dist/index.js

| 工具 | 参数 | 描述 |
|---|---|---|
| `pg_list_databases` | — | 列出所有可用数据库 |
| `pg_query` | `database`, `query` | 执行只读SQL查询 |
| `pg_list_databases` | — | 列出所有可用数据库及 `RO`/`RW` 访问模式 |
| `pg_query` | `database`, `query` | 执行 SQL 查询(`readOnly: true` 阻止写入;`readOnly: false` 允许写入) |
| `pg_list_tables` | `database`, `schema?` | 列出表及行数估计 |
| `pg_describe_table` | `database`, `table`, `schema?` | 列类型、主键、外键、默认值 |

Expand Down Expand Up @@ -451,12 +468,25 @@ npm install
"user": "readonly_user",
"password": "secret",
"database": "myapp",
"enabled": true
"enabled": true,
"readOnly": true
},
{
"label": "staging",
"host": "localhost",
"port": 5432,
"user": "dev",
"password": "dev123",
"database": "myapp_staging",
"enabled": true,
"readOnly": false
}
]
}
```

`readOnly` 默认为 `true`。只有在确实需要 MCP 写入该数据库时才设置为 `false`。

#### 3. 添加到 Claude Code

一条命令:
Expand Down Expand Up @@ -515,13 +545,13 @@ MCP_POSTGRES_CONFIG=/path/to/config.json node dist/index.js
| 配置格式 | 参数中的连接字符串 | JSON 文件 |
| 热重载 | 否(需重启) | 是 |
| 按项目隔离 | 无 | `--label` 过滤器 |
| 查询安全性 | 完全访问 | 仅只读 |
| 查询安全性 | 完全访问 | 默认只读;可通过单个连接的 `readOnly: false` 开启写入模式 |
| 5个数据库需要的进程数 | 5 | 1 |

### 安全性

- 所有查询在 `READ ONLY` 事务中执行
- 写操作(`INSERT`、`UPDATE`、`DELETE`、`DROP`)会失败
- 连接默认在 `READ ONLY` 事务中执行查询
- 写操作(`INSERT`、`UPDATE`、`DELETE`、`DROP`)会失败,除非该连接设置了 `readOnly: false`
- 连接超时:10秒,查询超时:30秒
- 包含密码的配置文件通过 `.gitignore` 排除在git之外
- `--label` 防止AI访问无关数据库
Expand Down
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,9 +399,8 @@ server.tool(
const addr = c.url
? "(connection string)"
: `${c.user}@${c.host}:${c.port}/${c.database}`;
const flags: string[] = [];
const flags: string[] = [c.readOnly ? "RO" : "RW"];
if (c.ssl) flags.push("SSL");
if (!c.readOnly) flags.push("RW");
const suffix = flags.length ? ` [${flags.join(", ")}]` : "";
return `- ${c.label}: ${addr}${suffix}`;
});
Expand Down