postgres-mcp-readonly 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +562 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +503 -0
- package/dist/server.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohaimanul islam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
# PostgreSQL MCP Server
|
|
2
|
+
|
|
3
|
+
A secure, read-only PostgreSQL Model Context Protocol (MCP) server that provides safe database introspection and querying capabilities. Built with TypeScript for enhanced type safety and reliability.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This MCP server enables AI assistants and other MCP clients to safely interact with PostgreSQL databases through a read-only interface. It provides schema inspection, parameterized queries, table previews, change tracking, and row counting while preventing any data modifications.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
### 🔒 Security First
|
|
12
|
+
|
|
13
|
+
- **Read-only enforcement** - Blocks all write operations (INSERT, UPDATE, DELETE, etc.)
|
|
14
|
+
- **SQL injection protection** - Validates identifiers and sanitizes queries
|
|
15
|
+
- **Automatic LIMIT enforcement** - Prevents unbounded result sets
|
|
16
|
+
- **Query timeouts** - Prevents long-running queries from blocking resources
|
|
17
|
+
- **Error sanitization** - Prevents leakage of sensitive connection details
|
|
18
|
+
- **Transaction isolation** - All queries run in READ ONLY transactions
|
|
19
|
+
|
|
20
|
+
### 🛠️ Tools Provided
|
|
21
|
+
|
|
22
|
+
1. **db.schema** - Inspect database structure
|
|
23
|
+
2. **db.query** - Execute parameterized SELECT queries
|
|
24
|
+
3. **db.preview** - Quick table preview
|
|
25
|
+
4. **db.watch** - Poll for incremental changes
|
|
26
|
+
5. **db.count** - Get exact row counts
|
|
27
|
+
|
|
28
|
+
### 📊 Resources
|
|
29
|
+
|
|
30
|
+
- **schema-summary** (`pg://schema/summary`) - Table list with approximate row counts
|
|
31
|
+
- **schema-full** (`pg://schema/full`) - Complete schema with columns, keys, and relationships
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### Prerequisites
|
|
36
|
+
|
|
37
|
+
- Node.js 18+
|
|
38
|
+
- PostgreSQL database (accessible via network)
|
|
39
|
+
|
|
40
|
+
### Setup
|
|
41
|
+
|
|
42
|
+
1. **Clone or download this project**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cd pg-mcp-server
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. **Install dependencies**
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
3. **Build the TypeScript code**
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm run build
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
4. **Configure environment variables**
|
|
61
|
+
|
|
62
|
+
Create a `.env` file:
|
|
63
|
+
|
|
64
|
+
```env
|
|
65
|
+
DATABASE_URL=postgres://username:password@localhost:5432/database_name
|
|
66
|
+
STATEMENT_TIMEOUT_MS=5000
|
|
67
|
+
MAX_ROWS=500
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
5. **Test the connection**
|
|
71
|
+
```bash
|
|
72
|
+
npm start
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
### Environment Variables
|
|
78
|
+
|
|
79
|
+
| Variable | Required | Default | Description |
|
|
80
|
+
| ---------------------- | -------- | ------- | ----------------------------- |
|
|
81
|
+
| `DATABASE_URL` | ✓ | - | PostgreSQL connection string |
|
|
82
|
+
| `STATEMENT_TIMEOUT_MS` | ✗ | 5000 | Query timeout in milliseconds |
|
|
83
|
+
| `MAX_ROWS` | ✗ | 500 | Default maximum rows returned |
|
|
84
|
+
|
|
85
|
+
### Connection String Format
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
postgres://username:password@host:5432/database_name
|
|
89
|
+
postgresql://username:password@host:5432/database_name
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Tools Documentation
|
|
93
|
+
|
|
94
|
+
### 1. db.schema
|
|
95
|
+
|
|
96
|
+
Inspect database schema information.
|
|
97
|
+
|
|
98
|
+
**Parameters:**
|
|
99
|
+
|
|
100
|
+
- `mode` (optional): `"summary"` or `"full"` (default: `"summary"`)
|
|
101
|
+
- `filter` (optional): Filter tables by name or schema (case-insensitive)
|
|
102
|
+
|
|
103
|
+
**Examples:**
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
// Get table list with row counts
|
|
107
|
+
{
|
|
108
|
+
"mode": "summary"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Get full schema with columns and keys
|
|
112
|
+
{
|
|
113
|
+
"mode": "full"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Filter specific tables
|
|
117
|
+
{
|
|
118
|
+
"mode": "full",
|
|
119
|
+
"filter": "users"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Response (summary):**
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"mode": "summary",
|
|
128
|
+
"tables": [
|
|
129
|
+
{
|
|
130
|
+
"schema": "public",
|
|
131
|
+
"table": "users",
|
|
132
|
+
"approxRows": 1250
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Response (full):**
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"mode": "full",
|
|
143
|
+
"schemas": {
|
|
144
|
+
"public": {
|
|
145
|
+
"users": {
|
|
146
|
+
"columns": [
|
|
147
|
+
{
|
|
148
|
+
"name": "id",
|
|
149
|
+
"dataType": "integer",
|
|
150
|
+
"udtName": "int4",
|
|
151
|
+
"nullable": false,
|
|
152
|
+
"default": "nextval('users_id_seq'::regclass)",
|
|
153
|
+
"position": 1
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"primaryKey": ["id"],
|
|
157
|
+
"foreignKeys": []
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 2. db.query
|
|
165
|
+
|
|
166
|
+
Execute a read-only SELECT query with optional parameters.
|
|
167
|
+
|
|
168
|
+
**Parameters:**
|
|
169
|
+
|
|
170
|
+
- `sql` (required): SELECT query (with or without LIMIT)
|
|
171
|
+
- `params` (optional): Array of parameter values for $1, $2, etc.
|
|
172
|
+
- `maxRows` (optional): Maximum rows to return (1-5000, default: 500)
|
|
173
|
+
|
|
174
|
+
**Examples:**
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
// Simple query
|
|
178
|
+
{
|
|
179
|
+
"sql": "SELECT * FROM users WHERE active = true"
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Parameterized query
|
|
183
|
+
{
|
|
184
|
+
"sql": "SELECT id, name, email FROM users WHERE country = $1 AND age > $2",
|
|
185
|
+
"params": ["USA", 25],
|
|
186
|
+
"maxRows": 100
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Query with existing LIMIT (will be honored if <= maxRows)
|
|
190
|
+
{
|
|
191
|
+
"sql": "SELECT * FROM orders ORDER BY created_at DESC LIMIT 10"
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Response:**
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"rowCount": 10,
|
|
200
|
+
"fields": ["id", "name", "email"],
|
|
201
|
+
"rows": [{ "id": 1, "name": "John Doe", "email": "john@example.com" }]
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Security Notes:**
|
|
206
|
+
|
|
207
|
+
- Only SELECT and WITH (CTE) queries allowed
|
|
208
|
+
- Single statement only (no semicolons)
|
|
209
|
+
- Automatic LIMIT enforcement if not specified
|
|
210
|
+
- Query timeout: 5 seconds (default)
|
|
211
|
+
|
|
212
|
+
### 3. db.preview
|
|
213
|
+
|
|
214
|
+
Quick preview of table rows.
|
|
215
|
+
|
|
216
|
+
**Parameters:**
|
|
217
|
+
|
|
218
|
+
- `table` (required): Table name (use `schema.table` or just `table`)
|
|
219
|
+
- `limit` (optional): Number of rows (1-500, default: 50)
|
|
220
|
+
|
|
221
|
+
**Examples:**
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
// Preview public.users table
|
|
225
|
+
{
|
|
226
|
+
"table": "users",
|
|
227
|
+
"limit": 20
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Preview from specific schema
|
|
231
|
+
{
|
|
232
|
+
"table": "analytics.events"
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Response:**
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"table": "public.users",
|
|
241
|
+
"rowCount": 20,
|
|
242
|
+
"rows": [{ "id": 1, "name": "Alice", "created_at": "2024-01-15T10:30:00Z" }]
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 4. db.watch
|
|
247
|
+
|
|
248
|
+
Poll for incremental changes using cursor-based pagination.
|
|
249
|
+
|
|
250
|
+
**Parameters:**
|
|
251
|
+
|
|
252
|
+
- `table` (required): Table name
|
|
253
|
+
- `cursorColumn` (optional): Column to track (default: `"updated_at"`)
|
|
254
|
+
- `lastCursor` (optional): Last cursor value from previous call
|
|
255
|
+
- `batchSize` (optional): Rows per batch (1-1000, default: 200)
|
|
256
|
+
|
|
257
|
+
**Examples:**
|
|
258
|
+
|
|
259
|
+
```javascript
|
|
260
|
+
// Initial fetch (gets oldest records first)
|
|
261
|
+
{
|
|
262
|
+
"table": "orders",
|
|
263
|
+
"cursorColumn": "created_at"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Subsequent fetch (pass lastCursor from previous response)
|
|
267
|
+
{
|
|
268
|
+
"table": "orders",
|
|
269
|
+
"cursorColumn": "created_at",
|
|
270
|
+
"lastCursor": "2024-01-15T14:23:45.123Z",
|
|
271
|
+
"batchSize": 100
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Track by numeric ID
|
|
275
|
+
{
|
|
276
|
+
"table": "logs",
|
|
277
|
+
"cursorColumn": "id",
|
|
278
|
+
"lastCursor": 5042
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Response:**
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"table": "public.orders",
|
|
287
|
+
"cursorColumn": "created_at",
|
|
288
|
+
"cursorType": "timestamp with time zone",
|
|
289
|
+
"lastCursor": "2024-01-15T15:30:00Z",
|
|
290
|
+
"rows": [...]
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Use Case:**
|
|
295
|
+
|
|
296
|
+
- Real-time monitoring
|
|
297
|
+
- ETL/sync processes
|
|
298
|
+
- Audit log tracking
|
|
299
|
+
- Event streaming
|
|
300
|
+
|
|
301
|
+
### 5. db.count
|
|
302
|
+
|
|
303
|
+
Get exact row count for a table.
|
|
304
|
+
|
|
305
|
+
**Parameters:**
|
|
306
|
+
|
|
307
|
+
- `table` (required): Table name (use `schema.table` or just `table`)
|
|
308
|
+
|
|
309
|
+
**Examples:**
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
// Count rows in public.users
|
|
313
|
+
{
|
|
314
|
+
"table": "users"
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Count in specific schema
|
|
318
|
+
{
|
|
319
|
+
"table": "analytics.pageviews"
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Response:**
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"table": "public.users",
|
|
328
|
+
"count": 15247
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## Usage Examples
|
|
333
|
+
|
|
334
|
+
### With Claude Desktop (MCP Client)
|
|
335
|
+
|
|
336
|
+
Add to your `claude_desktop_config.json`:
|
|
337
|
+
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"mcpServers": {
|
|
341
|
+
"postgres": {
|
|
342
|
+
"command": "node",
|
|
343
|
+
"args": ["d:/code/node/pg-mcp-server/dist/server.js"],
|
|
344
|
+
"env": {
|
|
345
|
+
"DATABASE_URL": "postgres://user:pass@localhost:5432/mydb"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Or if installed globally via npm:
|
|
353
|
+
|
|
354
|
+
```json
|
|
355
|
+
{
|
|
356
|
+
"mcpServers": {
|
|
357
|
+
"postgres": {
|
|
358
|
+
"command": "postgres-mcp-readonly",
|
|
359
|
+
"env": {
|
|
360
|
+
"DATABASE_URL": "postgres://user:pass@localhost:5432/mydb"
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Example Conversation Flow
|
|
368
|
+
|
|
369
|
+
**User:** "Show me the database schema"
|
|
370
|
+
|
|
371
|
+
**AI uses:** `db.schema` with `mode: "summary"`
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
**User:** "How many users do we have?"
|
|
376
|
+
|
|
377
|
+
**AI uses:** `db.count` with `table: "users"`
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
**User:** "Show me the 10 most recent orders"
|
|
382
|
+
|
|
383
|
+
**AI uses:** `db.query` with SQL:
|
|
384
|
+
|
|
385
|
+
```sql
|
|
386
|
+
SELECT * FROM orders ORDER BY created_at DESC LIMIT 10
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
**User:** "Watch for new signups"
|
|
392
|
+
|
|
393
|
+
**AI uses:** `db.watch` with `table: "users"`, `cursorColumn: "created_at"`
|
|
394
|
+
|
|
395
|
+
## Security Features
|
|
396
|
+
|
|
397
|
+
### Query Validation
|
|
398
|
+
|
|
399
|
+
The server performs multiple security checks:
|
|
400
|
+
|
|
401
|
+
1. **Keyword Blocklist** - Prevents: INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, GRANT, REVOKE, VACUUM, ANALYZE, REINDEX, COPY, CALL, DO, EXECUTE
|
|
402
|
+
|
|
403
|
+
2. **Comment Stripping** - Removes SQL comments to prevent obfuscation
|
|
404
|
+
|
|
405
|
+
3. **Single Statement** - Only one query per request (no semicolons)
|
|
406
|
+
|
|
407
|
+
4. **SELECT-only** - Must start with SELECT or WITH
|
|
408
|
+
|
|
409
|
+
5. **Identifier Validation** - Table/column names must match `[a-zA-Z_][a-zA-Z0-9_]*`
|
|
410
|
+
|
|
411
|
+
6. **Parameterization** - Supports bind parameters ($1, $2, etc.) to prevent injection
|
|
412
|
+
|
|
413
|
+
### Error Sanitization
|
|
414
|
+
|
|
415
|
+
Database errors are sanitized to prevent leaking:
|
|
416
|
+
|
|
417
|
+
- Connection strings and passwords
|
|
418
|
+
- Server hostnames
|
|
419
|
+
- File system paths
|
|
420
|
+
- Overly verbose stack traces
|
|
421
|
+
|
|
422
|
+
### Connection Safety
|
|
423
|
+
|
|
424
|
+
- **Connection pooling** with max 10 connections
|
|
425
|
+
- **Statement timeout** (5s default) prevents runaway queries
|
|
426
|
+
- **Lock timeout** (1s) prevents deadlock situations
|
|
427
|
+
- **Idle transaction timeout** (5s) frees stuck connections
|
|
428
|
+
- **Graceful shutdown** on SIGINT/SIGTERM
|
|
429
|
+
|
|
430
|
+
## Best Practices
|
|
431
|
+
|
|
432
|
+
### For AI Assistants
|
|
433
|
+
|
|
434
|
+
1. **Always check schema first** - Use `db.schema` before querying unknown tables
|
|
435
|
+
2. **Use parameterization** - Never concatenate user input into SQL strings
|
|
436
|
+
3. **Start with small limits** - Use low `maxRows` for exploratory queries
|
|
437
|
+
4. **Use db.count for totals** - Don't SELECT COUNT(\*) manually
|
|
438
|
+
5. **Handle errors gracefully** - Sanitized errors are safe to show users
|
|
439
|
+
|
|
440
|
+
### For Database Admins
|
|
441
|
+
|
|
442
|
+
1. **Use read-only database user** - Grant only SELECT permissions
|
|
443
|
+
2. **Monitor connection usage** - Set appropriate pool size
|
|
444
|
+
3. **Adjust timeouts** - Based on your query complexity
|
|
445
|
+
4. **Enable query logging** - In PostgreSQL for audit trail
|
|
446
|
+
5. **Use SSL connections** - Add `?sslmode=require` to DATABASE_URL
|
|
447
|
+
|
|
448
|
+
### Performance Tips
|
|
449
|
+
|
|
450
|
+
1. **Ensure indexed columns** - Especially for `db.watch` cursor columns
|
|
451
|
+
2. **Use filters in db.schema** - Don't fetch full schema repeatedly
|
|
452
|
+
3. **Keep maxRows reasonable** - Large result sets slow serialization
|
|
453
|
+
4. **Add indexes on sort columns** - For ORDER BY performance
|
|
454
|
+
|
|
455
|
+
## Troubleshooting
|
|
456
|
+
|
|
457
|
+
### Connection Issues
|
|
458
|
+
|
|
459
|
+
**Problem:** `Missing DATABASE_URL` error
|
|
460
|
+
|
|
461
|
+
**Solution:** Create `.env` file with valid connection string
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
**Problem:** `ECONNREFUSED` or connection timeout
|
|
466
|
+
|
|
467
|
+
**Solution:**
|
|
468
|
+
|
|
469
|
+
- Verify PostgreSQL is running
|
|
470
|
+
- Check host/port in DATABASE_URL
|
|
471
|
+
- Ensure firewall allows connections
|
|
472
|
+
- Test with `psql` command line first
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
**Problem:** `password authentication failed`
|
|
477
|
+
|
|
478
|
+
**Solution:** Verify username/password in DATABASE_URL
|
|
479
|
+
|
|
480
|
+
### Query Errors
|
|
481
|
+
|
|
482
|
+
**Problem:** `Blocked keyword detected: insert`
|
|
483
|
+
|
|
484
|
+
**Solution:** This is intentional - only SELECT queries are allowed
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
**Problem:** `Only SELECT queries are allowed`
|
|
489
|
+
|
|
490
|
+
**Solution:** Ensure query starts with SELECT or WITH, not EXPLAIN, SHOW, etc.
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
**Problem:** `statement timeout`
|
|
495
|
+
|
|
496
|
+
**Solution:**
|
|
497
|
+
|
|
498
|
+
- Increase STATEMENT_TIMEOUT_MS
|
|
499
|
+
- Optimize query with indexes
|
|
500
|
+
- Reduce dataset with WHERE clause
|
|
501
|
+
|
|
502
|
+
### Schema Issues
|
|
503
|
+
|
|
504
|
+
**Problem:** `relation "table_name" does not exist`
|
|
505
|
+
|
|
506
|
+
**Solution:**
|
|
507
|
+
|
|
508
|
+
- Check table name spelling
|
|
509
|
+
- Use `schema.table` if not in `public` schema
|
|
510
|
+
- Run `db.schema` to see available tables
|
|
511
|
+
|
|
512
|
+
## Development
|
|
513
|
+
|
|
514
|
+
### Running Locally
|
|
515
|
+
|
|
516
|
+
```bash
|
|
517
|
+
# Set environment variables
|
|
518
|
+
export DATABASE_URL="postgres://localhost:5432/testdb"
|
|
519
|
+
|
|
520
|
+
# Build and run server
|
|
521
|
+
npm run build
|
|
522
|
+
npm start
|
|
523
|
+
|
|
524
|
+
# Or use dev mode (builds and runs)
|
|
525
|
+
npm run dev
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Testing with MCP Inspector
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
# Build first
|
|
532
|
+
npm run build
|
|
533
|
+
|
|
534
|
+
# Then inspect
|
|
535
|
+
npx @modelcontextprotocol/inspector node dist/server.js
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## License
|
|
539
|
+
|
|
540
|
+
MIT
|
|
541
|
+
|
|
542
|
+
## Contributing
|
|
543
|
+
|
|
544
|
+
Contributions welcome! Please ensure:
|
|
545
|
+
|
|
546
|
+
- Security best practices maintained
|
|
547
|
+
- All tools remain read-only
|
|
548
|
+
- Tests pass (if added)
|
|
549
|
+
- Documentation updated
|
|
550
|
+
|
|
551
|
+
## Support
|
|
552
|
+
|
|
553
|
+
For issues or questions:
|
|
554
|
+
|
|
555
|
+
1. Check this README first
|
|
556
|
+
2. Review PostgreSQL connection docs
|
|
557
|
+
3. Test with `psql` to isolate database issues
|
|
558
|
+
4. Open an issue with sanitized error messages
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
**Remember:** This server is read-only by design. For database modifications, use traditional database tools or separate admin interfaces.
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
8
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
9
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
10
|
+
const pg_1 = require("pg");
|
|
11
|
+
const zod_1 = require("zod");
|
|
12
|
+
dotenv_1.default.config({ quiet: true });
|
|
13
|
+
function getDatabaseUrl() {
|
|
14
|
+
const value = (process.env.DATABASE_URL || "").trim();
|
|
15
|
+
if (!value) {
|
|
16
|
+
throw new Error("Missing DATABASE_URL. Example: postgres://user:password@localhost:5432/postgres");
|
|
17
|
+
}
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = new URL(value);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new Error("Invalid DATABASE_URL format. Expected: postgres://user:password@host:5432/dbname");
|
|
24
|
+
}
|
|
25
|
+
if (!["postgres:", "postgresql:"].includes(parsed.protocol)) {
|
|
26
|
+
throw new Error("DATABASE_URL must start with postgres:// or postgresql://");
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
const DATABASE_URL = getDatabaseUrl();
|
|
31
|
+
const STATEMENT_TIMEOUT_MS = Number(process.env.STATEMENT_TIMEOUT_MS || 5000);
|
|
32
|
+
const DEFAULT_MAX_ROWS = Number(process.env.MAX_ROWS || 500);
|
|
33
|
+
const pool = new pg_1.Pool({
|
|
34
|
+
connectionString: DATABASE_URL,
|
|
35
|
+
max: 10,
|
|
36
|
+
});
|
|
37
|
+
async function withClient(fn) {
|
|
38
|
+
const client = await pool.connect();
|
|
39
|
+
try {
|
|
40
|
+
await client.query(`SET statement_timeout = '${STATEMENT_TIMEOUT_MS}ms'`);
|
|
41
|
+
await client.query("SET lock_timeout = '1000ms'");
|
|
42
|
+
await client.query("SET idle_in_transaction_session_timeout = '5000ms'");
|
|
43
|
+
return await fn(client);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
client.release();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const BLOCKLIST = [
|
|
50
|
+
"insert",
|
|
51
|
+
"update",
|
|
52
|
+
"delete",
|
|
53
|
+
"drop",
|
|
54
|
+
"alter",
|
|
55
|
+
"create",
|
|
56
|
+
"truncate",
|
|
57
|
+
"grant",
|
|
58
|
+
"revoke",
|
|
59
|
+
"vacuum",
|
|
60
|
+
"analyze",
|
|
61
|
+
"reindex",
|
|
62
|
+
"copy",
|
|
63
|
+
"call",
|
|
64
|
+
"do",
|
|
65
|
+
"execute",
|
|
66
|
+
];
|
|
67
|
+
function normalizeSql(sql) {
|
|
68
|
+
return sql
|
|
69
|
+
.replace(/--.*$/gm, "")
|
|
70
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
71
|
+
.trim();
|
|
72
|
+
}
|
|
73
|
+
function assertSelectOnly(sql) {
|
|
74
|
+
const s = normalizeSql(sql).toLowerCase();
|
|
75
|
+
if (s.includes(";")) {
|
|
76
|
+
throw new Error("Only single-statement queries are allowed.");
|
|
77
|
+
}
|
|
78
|
+
if (!s.startsWith("select") && !s.startsWith("with")) {
|
|
79
|
+
throw new Error("Only SELECT queries are allowed.");
|
|
80
|
+
}
|
|
81
|
+
for (const bad of BLOCKLIST) {
|
|
82
|
+
if (new RegExp(`\\b${bad}\\b`, "i").test(s)) {
|
|
83
|
+
throw new Error(`Blocked keyword detected: ${bad}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function enforceLimit(sql, maxRows = DEFAULT_MAX_ROWS) {
|
|
88
|
+
const s = normalizeSql(sql);
|
|
89
|
+
if (!/\blimit\b/i.test(s)) {
|
|
90
|
+
return `${s} LIMIT ${maxRows}`;
|
|
91
|
+
}
|
|
92
|
+
// If LIMIT exists, extract it and ensure it doesn't exceed maxRows
|
|
93
|
+
const limitMatch = s.match(/\blimit\s+(\d+)/i);
|
|
94
|
+
if (limitMatch) {
|
|
95
|
+
const existingLimit = parseInt(limitMatch[1], 10);
|
|
96
|
+
if (existingLimit <= maxRows) {
|
|
97
|
+
return s; // Trust existing LIMIT if within bounds
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Wrap query to enforce maxRows if existing LIMIT exceeds it
|
|
101
|
+
return `SELECT * FROM (${s}) AS _q LIMIT ${maxRows}`;
|
|
102
|
+
}
|
|
103
|
+
function sanitizeError(error) {
|
|
104
|
+
// Sanitize database errors to prevent information leakage
|
|
105
|
+
const message = error.message || String(error);
|
|
106
|
+
// Remove connection details, file paths, and sensitive info
|
|
107
|
+
let sanitized = message
|
|
108
|
+
.replace(/\b(?:password|pwd|secret|token|key)\s*=\s*[^\s;]*/gi, "[REDACTED]")
|
|
109
|
+
.replace(/\b(?:host|server)\s*=\s*[^\s;,]*/gi, "[HOST]")
|
|
110
|
+
.replace(/[A-Za-z]:\\[^\s"]*/g, "[PATH]")
|
|
111
|
+
.replace(/\/(?:home|usr|var)\/[^\s"]*/g, "[PATH]");
|
|
112
|
+
// Preserve common PostgreSQL error patterns that are safe
|
|
113
|
+
const safePatterns = [
|
|
114
|
+
/column "[^"]+" does not exist/i,
|
|
115
|
+
/relation "[^"]+" does not exist/i,
|
|
116
|
+
/syntax error/i,
|
|
117
|
+
/permission denied/i,
|
|
118
|
+
/statement timeout/i,
|
|
119
|
+
/lock timeout/i,
|
|
120
|
+
];
|
|
121
|
+
const isSafe = safePatterns.some((pattern) => pattern.test(message));
|
|
122
|
+
if (!isSafe && message.length > 200) {
|
|
123
|
+
sanitized = sanitized.substring(0, 200) + "...";
|
|
124
|
+
}
|
|
125
|
+
const sanitizedError = new Error(sanitized);
|
|
126
|
+
sanitizedError.name = error.name || "DatabaseError";
|
|
127
|
+
return sanitizedError;
|
|
128
|
+
}
|
|
129
|
+
function assertIdentifier(name) {
|
|
130
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
131
|
+
throw new Error(`Invalid identifier: ${name}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function parseTableName(input) {
|
|
135
|
+
const parts = input.split(".");
|
|
136
|
+
if (parts.length === 1) {
|
|
137
|
+
assertIdentifier(parts[0]);
|
|
138
|
+
return { schema: "public", table: parts[0] };
|
|
139
|
+
}
|
|
140
|
+
if (parts.length === 2) {
|
|
141
|
+
assertIdentifier(parts[0]);
|
|
142
|
+
assertIdentifier(parts[1]);
|
|
143
|
+
return { schema: parts[0], table: parts[1] };
|
|
144
|
+
}
|
|
145
|
+
throw new Error("Invalid table name format. Use table or schema.table");
|
|
146
|
+
}
|
|
147
|
+
async function dbSchema({ mode = "summary", filter = "", } = {}) {
|
|
148
|
+
const Mode = zod_1.z.enum(["summary", "full"]);
|
|
149
|
+
Mode.parse(mode);
|
|
150
|
+
const filterLike = `%${filter}%`;
|
|
151
|
+
return withClient(async (client) => {
|
|
152
|
+
const tablesRes = await client.query(`
|
|
153
|
+
SELECT table_schema, table_name
|
|
154
|
+
FROM information_schema.tables
|
|
155
|
+
WHERE table_type='BASE TABLE'
|
|
156
|
+
AND table_schema NOT IN ('pg_catalog','information_schema')
|
|
157
|
+
AND ($1 = '%%' OR table_name ILIKE $1 OR table_schema ILIKE $1)
|
|
158
|
+
ORDER BY table_schema, table_name
|
|
159
|
+
`, [filter ? filterLike : "%%"]);
|
|
160
|
+
if (mode === "summary") {
|
|
161
|
+
const countsRes = await client.query(`
|
|
162
|
+
SELECT schemaname AS table_schema, relname AS table_name, n_live_tup::bigint AS approx_rows
|
|
163
|
+
FROM pg_stat_user_tables
|
|
164
|
+
`);
|
|
165
|
+
const countMap = new Map(countsRes.rows.map((row) => [
|
|
166
|
+
`${row.table_schema}.${row.table_name}`,
|
|
167
|
+
row.approx_rows,
|
|
168
|
+
]));
|
|
169
|
+
return {
|
|
170
|
+
mode: "summary",
|
|
171
|
+
tables: tablesRes.rows.map((row) => ({
|
|
172
|
+
schema: row.table_schema,
|
|
173
|
+
table: row.table_name,
|
|
174
|
+
approxRows: countMap.get(`${row.table_schema}.${row.table_name}`) ?? null,
|
|
175
|
+
})),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const colsRes = await client.query(`
|
|
179
|
+
SELECT
|
|
180
|
+
c.table_schema,
|
|
181
|
+
c.table_name,
|
|
182
|
+
c.ordinal_position,
|
|
183
|
+
c.column_name,
|
|
184
|
+
c.data_type,
|
|
185
|
+
c.udt_name,
|
|
186
|
+
c.is_nullable,
|
|
187
|
+
c.column_default
|
|
188
|
+
FROM information_schema.columns c
|
|
189
|
+
WHERE c.table_schema NOT IN ('pg_catalog','information_schema')
|
|
190
|
+
AND ($1 = '%%' OR c.table_name ILIKE $1 OR c.table_schema ILIKE $1)
|
|
191
|
+
ORDER BY c.table_schema, c.table_name, c.ordinal_position
|
|
192
|
+
`, [filter ? filterLike : "%%"]);
|
|
193
|
+
const pkRes = await client.query(`
|
|
194
|
+
SELECT
|
|
195
|
+
tc.table_schema,
|
|
196
|
+
tc.table_name,
|
|
197
|
+
kcu.column_name,
|
|
198
|
+
kcu.ordinal_position
|
|
199
|
+
FROM information_schema.table_constraints tc
|
|
200
|
+
JOIN information_schema.key_column_usage kcu
|
|
201
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
202
|
+
AND tc.table_schema = kcu.table_schema
|
|
203
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
204
|
+
AND tc.table_schema NOT IN ('pg_catalog','information_schema')
|
|
205
|
+
AND ($1 = '%%' OR tc.table_name ILIKE $1 OR tc.table_schema ILIKE $1)
|
|
206
|
+
ORDER BY tc.table_schema, tc.table_name, kcu.ordinal_position
|
|
207
|
+
`, [filter ? filterLike : "%%"]);
|
|
208
|
+
const fkRes = await client.query(`
|
|
209
|
+
SELECT
|
|
210
|
+
tc.table_schema,
|
|
211
|
+
tc.table_name,
|
|
212
|
+
kcu.column_name,
|
|
213
|
+
ccu.table_schema AS foreign_table_schema,
|
|
214
|
+
ccu.table_name AS foreign_table_name,
|
|
215
|
+
ccu.column_name AS foreign_column_name
|
|
216
|
+
FROM information_schema.table_constraints tc
|
|
217
|
+
JOIN information_schema.key_column_usage kcu
|
|
218
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
219
|
+
AND tc.table_schema = kcu.table_schema
|
|
220
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
221
|
+
ON ccu.constraint_name = tc.constraint_name
|
|
222
|
+
AND ccu.table_schema = tc.table_schema
|
|
223
|
+
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
224
|
+
AND tc.table_schema NOT IN ('pg_catalog','information_schema')
|
|
225
|
+
AND ($1 = '%%' OR tc.table_name ILIKE $1 OR tc.table_schema ILIKE $1)
|
|
226
|
+
ORDER BY tc.table_schema, tc.table_name
|
|
227
|
+
`, [filter ? filterLike : "%%"]);
|
|
228
|
+
const out = { mode: "full", schemas: {} };
|
|
229
|
+
for (const row of tablesRes.rows) {
|
|
230
|
+
out.schemas[row.table_schema] ??= {};
|
|
231
|
+
out.schemas[row.table_schema][row.table_name] = {
|
|
232
|
+
columns: [],
|
|
233
|
+
primaryKey: [],
|
|
234
|
+
foreignKeys: [],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
for (const row of colsRes.rows) {
|
|
238
|
+
const table = out.schemas?.[row.table_schema]?.[row.table_name];
|
|
239
|
+
if (!table) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
table.columns.push({
|
|
243
|
+
name: row.column_name,
|
|
244
|
+
dataType: row.data_type,
|
|
245
|
+
udtName: row.udt_name,
|
|
246
|
+
nullable: row.is_nullable === "YES",
|
|
247
|
+
default: row.column_default,
|
|
248
|
+
position: row.ordinal_position,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
for (const row of pkRes.rows) {
|
|
252
|
+
const table = out.schemas?.[row.table_schema]?.[row.table_name];
|
|
253
|
+
if (!table) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
table.primaryKey.push(row.column_name);
|
|
257
|
+
}
|
|
258
|
+
for (const row of fkRes.rows) {
|
|
259
|
+
const table = out.schemas?.[row.table_schema]?.[row.table_name];
|
|
260
|
+
if (!table) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
table.foreignKeys.push({
|
|
264
|
+
column: row.column_name,
|
|
265
|
+
refSchema: row.foreign_table_schema,
|
|
266
|
+
refTable: row.foreign_table_name,
|
|
267
|
+
refColumn: row.foreign_column_name,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return out;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
async function dbQuery({ sql, params = [], maxRows = DEFAULT_MAX_ROWS, }) {
|
|
274
|
+
zod_1.z.object({
|
|
275
|
+
sql: zod_1.z.string().min(1),
|
|
276
|
+
params: zod_1.z.array(zod_1.z.any()).optional(),
|
|
277
|
+
maxRows: zod_1.z.number().int().min(1).max(5000).optional(),
|
|
278
|
+
}).parse({ sql, params, maxRows });
|
|
279
|
+
assertSelectOnly(sql);
|
|
280
|
+
const safeSql = enforceLimit(sql, maxRows);
|
|
281
|
+
return withClient(async (client) => {
|
|
282
|
+
await client.query("BEGIN READ ONLY");
|
|
283
|
+
try {
|
|
284
|
+
const result = await client.query(safeSql, params);
|
|
285
|
+
await client.query("COMMIT");
|
|
286
|
+
return {
|
|
287
|
+
rowCount: result.rowCount || 0,
|
|
288
|
+
fields: result.fields.map((field) => field.name),
|
|
289
|
+
rows: result.rows,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
await client.query("ROLLBACK");
|
|
294
|
+
throw sanitizeError(error);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
async function dbPreview({ table, limit = 50, }) {
|
|
299
|
+
zod_1.z.object({
|
|
300
|
+
table: zod_1.z.string().min(1),
|
|
301
|
+
limit: zod_1.z.number().int().min(1).max(500).optional(),
|
|
302
|
+
}).parse({ table, limit });
|
|
303
|
+
const parsed = parseTableName(table);
|
|
304
|
+
return withClient(async (client) => {
|
|
305
|
+
await client.query("BEGIN READ ONLY");
|
|
306
|
+
try {
|
|
307
|
+
const result = await client.query(`SELECT * FROM "${parsed.schema}"."${parsed.table}" LIMIT $1`, [limit]);
|
|
308
|
+
await client.query("COMMIT");
|
|
309
|
+
return {
|
|
310
|
+
table: `${parsed.schema}.${parsed.table}`,
|
|
311
|
+
rowCount: result.rowCount || 0,
|
|
312
|
+
rows: result.rows,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
await client.query("ROLLBACK");
|
|
317
|
+
throw sanitizeError(error);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async function dbWatch({ table, cursorColumn = "updated_at", lastCursor = null, batchSize = 200, }) {
|
|
322
|
+
zod_1.z.object({
|
|
323
|
+
table: zod_1.z.string().min(1),
|
|
324
|
+
cursorColumn: zod_1.z.string().min(1).optional(),
|
|
325
|
+
lastCursor: zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.null()]).optional(),
|
|
326
|
+
batchSize: zod_1.z.number().int().min(1).max(1000).optional(),
|
|
327
|
+
}).parse({ table, cursorColumn, lastCursor, batchSize });
|
|
328
|
+
const parsed = parseTableName(table);
|
|
329
|
+
assertIdentifier(cursorColumn);
|
|
330
|
+
const cursorType = await withClient(async (client) => {
|
|
331
|
+
const res = await client.query(`
|
|
332
|
+
SELECT data_type
|
|
333
|
+
FROM information_schema.columns
|
|
334
|
+
WHERE table_schema = $1 AND table_name = $2 AND column_name = $3
|
|
335
|
+
`, [parsed.schema, parsed.table, cursorColumn]);
|
|
336
|
+
if (res.rows.length === 0) {
|
|
337
|
+
throw new Error(`Cursor column not found: ${cursorColumn}`);
|
|
338
|
+
}
|
|
339
|
+
return res.rows[0].data_type;
|
|
340
|
+
});
|
|
341
|
+
const isTimestamp = /timestamp|date|time/i.test(cursorType);
|
|
342
|
+
const effectiveCursor = lastCursor !== null && lastCursor !== undefined
|
|
343
|
+
? lastCursor
|
|
344
|
+
: isTimestamp
|
|
345
|
+
? "1970-01-01T00:00:00.000Z"
|
|
346
|
+
: 0;
|
|
347
|
+
return withClient(async (client) => {
|
|
348
|
+
await client.query("BEGIN READ ONLY");
|
|
349
|
+
try {
|
|
350
|
+
const sql = `
|
|
351
|
+
SELECT *
|
|
352
|
+
FROM "${parsed.schema}"."${parsed.table}"
|
|
353
|
+
WHERE "${cursorColumn}" > $1
|
|
354
|
+
ORDER BY "${cursorColumn}" ASC
|
|
355
|
+
LIMIT $2
|
|
356
|
+
`;
|
|
357
|
+
const result = await client.query(sql, [effectiveCursor, batchSize]);
|
|
358
|
+
await client.query("COMMIT");
|
|
359
|
+
const nextCursor = result.rows.length > 0
|
|
360
|
+
? result.rows[result.rows.length - 1][cursorColumn]
|
|
361
|
+
: effectiveCursor;
|
|
362
|
+
return {
|
|
363
|
+
table: `${parsed.schema}.${parsed.table}`,
|
|
364
|
+
cursorColumn,
|
|
365
|
+
cursorType,
|
|
366
|
+
lastCursor: nextCursor,
|
|
367
|
+
rows: result.rows,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
await client.query("ROLLBACK");
|
|
372
|
+
throw sanitizeError(error);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
async function dbCount({ table, }) {
|
|
377
|
+
zod_1.z.object({
|
|
378
|
+
table: zod_1.z.string().min(1),
|
|
379
|
+
}).parse({ table });
|
|
380
|
+
const parsed = parseTableName(table);
|
|
381
|
+
return withClient(async (client) => {
|
|
382
|
+
await client.query("BEGIN READ ONLY");
|
|
383
|
+
try {
|
|
384
|
+
const result = await client.query(`SELECT COUNT(*) AS count FROM "${parsed.schema}"."${parsed.table}"`);
|
|
385
|
+
await client.query("COMMIT");
|
|
386
|
+
return {
|
|
387
|
+
table: `${parsed.schema}.${parsed.table}`,
|
|
388
|
+
count: parseInt(result.rows[0].count, 10),
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
await client.query("ROLLBACK");
|
|
393
|
+
throw sanitizeError(error);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function asTextContent(payload) {
|
|
398
|
+
return {
|
|
399
|
+
content: [
|
|
400
|
+
{
|
|
401
|
+
type: "text",
|
|
402
|
+
text: JSON.stringify(payload, null, 2),
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
structuredContent: payload,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
const server = new mcp_js_1.McpServer({
|
|
409
|
+
name: "pg_saga_db",
|
|
410
|
+
version: "1.0.0",
|
|
411
|
+
});
|
|
412
|
+
server.registerTool("db.schema", {
|
|
413
|
+
description: "Inspect database schema. Use mode='summary' for table list or mode='full' for columns and keys.",
|
|
414
|
+
inputSchema: {
|
|
415
|
+
mode: zod_1.z.enum(["summary", "full"]).optional(),
|
|
416
|
+
filter: zod_1.z.string().optional(),
|
|
417
|
+
},
|
|
418
|
+
}, async ({ mode = "summary", filter = "" }) => asTextContent(await dbSchema({ mode, filter })));
|
|
419
|
+
server.registerTool("db.query", {
|
|
420
|
+
description: "Run a single read-only SELECT query with optional parameters and row limit.",
|
|
421
|
+
inputSchema: {
|
|
422
|
+
sql: zod_1.z.string().min(1),
|
|
423
|
+
params: zod_1.z.array(zod_1.z.any()).optional(),
|
|
424
|
+
maxRows: zod_1.z.number().int().min(1).max(5000).optional(),
|
|
425
|
+
},
|
|
426
|
+
}, async ({ sql, params = [], maxRows = DEFAULT_MAX_ROWS }) => asTextContent(await dbQuery({ sql, params, maxRows })));
|
|
427
|
+
server.registerTool("db.preview", {
|
|
428
|
+
description: "Preview rows from a table using table or schema.table name.",
|
|
429
|
+
inputSchema: {
|
|
430
|
+
table: zod_1.z.string().min(1),
|
|
431
|
+
limit: zod_1.z.number().int().min(1).max(500).optional(),
|
|
432
|
+
},
|
|
433
|
+
}, async ({ table, limit = 50 }) => asTextContent(await dbPreview({ table, limit })));
|
|
434
|
+
server.registerTool("db.watch", {
|
|
435
|
+
description: "Fetch one incremental batch where cursorColumn > lastCursor. Repeat client-side for polling.",
|
|
436
|
+
inputSchema: {
|
|
437
|
+
table: zod_1.z.string().min(1),
|
|
438
|
+
cursorColumn: zod_1.z.string().min(1).optional(),
|
|
439
|
+
lastCursor: zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.null()]).optional(),
|
|
440
|
+
batchSize: zod_1.z.number().int().min(1).max(1000).optional(),
|
|
441
|
+
},
|
|
442
|
+
}, async ({ table, cursorColumn = "updated_at", lastCursor = null, batchSize = 200, }) => asTextContent(await dbWatch({ table, cursorColumn, lastCursor, batchSize })));
|
|
443
|
+
server.registerTool("db.count", {
|
|
444
|
+
description: "Get exact row count for a table. Use table or schema.table name.",
|
|
445
|
+
inputSchema: {
|
|
446
|
+
table: zod_1.z.string().min(1),
|
|
447
|
+
},
|
|
448
|
+
}, async ({ table }) => asTextContent(await dbCount({ table })));
|
|
449
|
+
const SCHEMA_SUMMARY_URI = "pg://schema/summary";
|
|
450
|
+
const SCHEMA_FULL_URI = "pg://schema/full";
|
|
451
|
+
server.registerResource("schema-summary", SCHEMA_SUMMARY_URI, {
|
|
452
|
+
mimeType: "application/json",
|
|
453
|
+
description: "Summary of tables and approximate row counts.",
|
|
454
|
+
}, async () => {
|
|
455
|
+
const payload = await dbSchema({ mode: "summary" });
|
|
456
|
+
return {
|
|
457
|
+
contents: [
|
|
458
|
+
{
|
|
459
|
+
uri: SCHEMA_SUMMARY_URI,
|
|
460
|
+
mimeType: "application/json",
|
|
461
|
+
text: JSON.stringify(payload, null, 2),
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
server.registerResource("schema-full", SCHEMA_FULL_URI, {
|
|
467
|
+
mimeType: "application/json",
|
|
468
|
+
description: "Full schema including columns, primary keys, and foreign keys.",
|
|
469
|
+
}, async () => {
|
|
470
|
+
const payload = await dbSchema({ mode: "full" });
|
|
471
|
+
return {
|
|
472
|
+
contents: [
|
|
473
|
+
{
|
|
474
|
+
uri: SCHEMA_FULL_URI,
|
|
475
|
+
mimeType: "application/json",
|
|
476
|
+
text: JSON.stringify(payload, null, 2),
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
};
|
|
480
|
+
});
|
|
481
|
+
async function closeResources() {
|
|
482
|
+
try {
|
|
483
|
+
await server.close();
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
await pool.end();
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
process.on("SIGINT", () => {
|
|
490
|
+
closeResources().finally(() => process.exit(0));
|
|
491
|
+
});
|
|
492
|
+
process.on("SIGTERM", () => {
|
|
493
|
+
closeResources().finally(() => process.exit(0));
|
|
494
|
+
});
|
|
495
|
+
async function main() {
|
|
496
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
497
|
+
await server.connect(transport);
|
|
498
|
+
}
|
|
499
|
+
main().catch((error) => {
|
|
500
|
+
console.error("pg_saga_db MCP server error:", error);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
});
|
|
503
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "postgres-mcp-readonly",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A secure, read-only PostgreSQL Model Context Protocol (MCP) server for safe database introspection and querying",
|
|
5
|
+
"main": "dist/server.js",
|
|
6
|
+
"types": "dist/server.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"postgres-mcp-readonly": "./dist/server.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"build:watch": "tsc --watch",
|
|
13
|
+
"start": "node dist/server.js",
|
|
14
|
+
"dev": "tsc && node dist/server.js",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"clean": "rimraf dist",
|
|
17
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"model-context-protocol",
|
|
22
|
+
"postgresql",
|
|
23
|
+
"postgres",
|
|
24
|
+
"database",
|
|
25
|
+
"read-only",
|
|
26
|
+
"ai",
|
|
27
|
+
"llm",
|
|
28
|
+
"claude",
|
|
29
|
+
"typescript"
|
|
30
|
+
],
|
|
31
|
+
"author": "Mohaimanul islam mahin mohaimanul6@gmail.com",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/mahin1995/postgres-mcp-readonly.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/mahin1995/postgres-mcp-readonly/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/mahin1995/postgres-mcp-readonly#readme",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE"
|
|
48
|
+
],
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
51
|
+
"dotenv": "^17.3.1",
|
|
52
|
+
"pg": "^8.19.0",
|
|
53
|
+
"zod": "^4.3.6"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.0.0",
|
|
57
|
+
"@types/pg": "^8.16.0",
|
|
58
|
+
"rimraf": "^5.0.5",
|
|
59
|
+
"typescript": "^5.3.0"
|
|
60
|
+
}
|
|
61
|
+
}
|