@contextflo/postgres-mcp 0.1.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 +267 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +159 -0
- package/dist/config.js.map +1 -0
- package/dist/context/context-file.d.ts +53 -0
- package/dist/context/context-file.js +248 -0
- package/dist/context/context-file.js.map +1 -0
- package/dist/context/init.d.ts +14 -0
- package/dist/context/init.js +71 -0
- package/dist/context/init.js.map +1 -0
- package/dist/context/store.d.ts +34 -0
- package/dist/context/store.js +87 -0
- package/dist/context/store.js.map +1 -0
- package/dist/db/errors.d.ts +9 -0
- package/dist/db/errors.js +58 -0
- package/dist/db/errors.js.map +1 -0
- package/dist/db/introspection.d.ts +55 -0
- package/dist/db/introspection.js +178 -0
- package/dist/db/introspection.js.map +1 -0
- package/dist/db/pool.d.ts +37 -0
- package/dist/db/pool.js +213 -0
- package/dist/db/pool.js.map +1 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.js +137 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +46 -0
- package/dist/log.js +100 -0
- package/dist/log.js.map +1 -0
- package/dist/safety/errors.d.ts +11 -0
- package/dist/safety/errors.js +65 -0
- package/dist/safety/errors.js.map +1 -0
- package/dist/safety/validate.d.ts +12 -0
- package/dist/safety/validate.js +145 -0
- package/dist/safety/validate.js.map +1 -0
- package/dist/safety/walk.d.ts +26 -0
- package/dist/safety/walk.js +61 -0
- package/dist/safety/walk.js.map +1 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +132 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/add-table-context.d.ts +28 -0
- package/dist/tools/add-table-context.js +105 -0
- package/dist/tools/add-table-context.js.map +1 -0
- package/dist/tools/context.d.ts +12 -0
- package/dist/tools/context.js +2 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/get-table-context.d.ts +20 -0
- package/dist/tools/get-table-context.js +102 -0
- package/dist/tools/get-table-context.js.map +1 -0
- package/dist/tools/list-tables.d.ts +25 -0
- package/dist/tools/list-tables.js +78 -0
- package/dist/tools/list-tables.js.map +1 -0
- package/dist/tools/query.d.ts +22 -0
- package/dist/tools/query.js +138 -0
- package/dist/tools/query.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ContextFlo
|
|
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,267 @@
|
|
|
1
|
+
# @contextflo/postgres-mcp
|
|
2
|
+
|
|
3
|
+
The analytics MCP server for Postgres. Read-only by construction, with schema context that makes answers correct.
|
|
4
|
+
|
|
5
|
+
A drop-in replacement for the archived `@modelcontextprotocol/server-postgres`, which shipped with a
|
|
6
|
+
[SQL injection vulnerability](https://securitylabs.datadoghq.com/articles/mcp-vulnerability-case-study-SQL-injection-in-the-postgresql-mcp-server/)
|
|
7
|
+
that let `COMMIT; DROP SCHEMA public CASCADE` walk straight out of its read-only transaction.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @contextflo/postgres-mcp postgresql://localhost/mydb
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Why this one
|
|
14
|
+
|
|
15
|
+
**Read-only that holds up.** The archived server enforced read-only as a property of the SQL *string*. Here it is a
|
|
16
|
+
property of the connection, the role, and the wire protocol: four independent layers, each of which stops that
|
|
17
|
+
payload on its own. The exploit is a test case in this repo.
|
|
18
|
+
|
|
19
|
+
**Answers that make sense.** A model that does not know `fct_orders_v2` is the table your team actually uses, or that
|
|
20
|
+
`revenue` is gross rather than net, writes confident, wrong SQL. `.contextflo/context.md` is a markdown file you edit
|
|
21
|
+
and this server hands to the model. No database, no index, no service.
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
Add it to your MCP client:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"postgres": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "@contextflo/postgres-mcp", "postgresql://localhost/mydb"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
<details>
|
|
39
|
+
<summary>Claude Code, Cursor, Claude Desktop, VS Code</summary>
|
|
40
|
+
|
|
41
|
+
**Claude Code**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
claude mcp add postgres -- npx -y @contextflo/postgres-mcp postgresql://localhost/mydb
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Cursor** uses `.cursor/mcp.json`, same shape as above.
|
|
48
|
+
|
|
49
|
+
**Claude Desktop** uses `claude_desktop_config.json`
|
|
50
|
+
(macOS: `~/Library/Application Support/Claude/`, Windows: `%APPDATA%\Claude\`), same shape as above.
|
|
51
|
+
|
|
52
|
+
**VS Code** uses `.vscode/mcp.json`:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"servers": {
|
|
57
|
+
"postgres": {
|
|
58
|
+
"command": "npx",
|
|
59
|
+
"args": ["-y", "@contextflo/postgres-mcp", "postgresql://localhost/mydb"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
</details>
|
|
66
|
+
|
|
67
|
+
Then generate a context file:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx @contextflo/postgres-mcp init postgresql://localhost/mydb
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
That writes `.contextflo/context.md`, seeded from your `COMMENT ON` values, and prints the `CREATE ROLE` snippet for
|
|
74
|
+
a read-only role. Editing the file is the point: the business definitions section is where the value is.
|
|
75
|
+
|
|
76
|
+
## Tools
|
|
77
|
+
|
|
78
|
+
| Tool | What it does |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `query` | Runs one read-only statement: `SELECT`, `WITH ... SELECT`, `EXPLAIN`, or `SHOW`. |
|
|
81
|
+
| `list_tables` | Lists readable tables with descriptions. `pattern` matches anywhere in the name or description. |
|
|
82
|
+
| `get_table_context` | Describes tables: columns, types, keys, foreign key targets, enum values, curated descriptions. |
|
|
83
|
+
| `add_table_context` | Lets the agent write down a gotcha it found (`amount` is in cents, `status` has an undocumented value) in the context file. |
|
|
84
|
+
|
|
85
|
+
There is no separate search tool, and that is deliberate. `information_schema` and `pg_catalog` are ordinary tables,
|
|
86
|
+
so anything more specific, like finding every column named like `%revenue%` or listing tables with no primary key,
|
|
87
|
+
is a query the model can write itself:
|
|
88
|
+
|
|
89
|
+
```sql
|
|
90
|
+
SELECT table_schema, table_name, column_name
|
|
91
|
+
FROM information_schema.columns
|
|
92
|
+
WHERE column_name ILIKE '%revenue%';
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Table schemas are also exposed as `postgres://<host>/<table>/schema` resources, matching the archived server, for
|
|
96
|
+
anything pinned to those URIs. Most clients never fetch resources on their own, which is why discovery lives in the
|
|
97
|
+
tools.
|
|
98
|
+
|
|
99
|
+
## How read-only is enforced
|
|
100
|
+
|
|
101
|
+
Four layers. Each one stops the archived server's exploit by itself.
|
|
102
|
+
|
|
103
|
+
**1. Extended query protocol.** User SQL goes through `pg-cursor`, which always issues Parse/Bind/Execute, so
|
|
104
|
+
Postgres itself rejects multi-statement input. The archived server called `client.query(sql)` with a bare string;
|
|
105
|
+
node-postgres only prepares a statement when there are bind values, so that took the *simple* protocol path, where
|
|
106
|
+
`;` separates statements. That is the whole bug.
|
|
107
|
+
|
|
108
|
+
**2. Connection-level read-only.** `default_transaction_read_only=on` is set in the startup packet, and every
|
|
109
|
+
statement runs inside an explicit `BEGIN READ ONLY` that always ends in `ROLLBACK`, never `COMMIT`. The rollback
|
|
110
|
+
also undoes any `SET` made inside the transaction, so a statement cannot leave a pooled connection weakened for
|
|
111
|
+
whoever gets it next.
|
|
112
|
+
|
|
113
|
+
**3. A statement allowlist on the real Postgres parser.** [`libpg-query`](https://github.com/launchql/libpg-query-node)
|
|
114
|
+
is the actual Postgres C parser compiled to WASM, not a JavaScript approximation of SQL. The whole parse tree is
|
|
115
|
+
walked rather than just the top-level node, which is what catches a data-modifying CTE:
|
|
116
|
+
|
|
117
|
+
```sql
|
|
118
|
+
WITH x AS (INSERT INTO users VALUES (1) RETURNING *) SELECT * FROM x
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
That parses as a `SelectStmt`. A validator checking only the statement type runs it. Unknown node types fail closed.
|
|
122
|
+
|
|
123
|
+
The same walk rejects built-in functions that act outside the transaction even inside a plain `SELECT`:
|
|
124
|
+
`dblink` (a second connection, which is not read-only), `query_to_xml` (runs a SQL string the parser never sees),
|
|
125
|
+
`pg_terminate_backend`, session-level advisory locks, `set_config`, and the server-filesystem functions.
|
|
126
|
+
|
|
127
|
+
**4. A read-only database role.** The layers above are code, and code has bugs. A role that cannot write is enforced
|
|
128
|
+
by Postgres regardless. `init` prints the snippet; this is the setup we recommend:
|
|
129
|
+
|
|
130
|
+
```sql
|
|
131
|
+
CREATE ROLE mcp_readonly LOGIN PASSWORD 'change-me';
|
|
132
|
+
GRANT CONNECT ON DATABASE mydb TO mcp_readonly;
|
|
133
|
+
GRANT USAGE ON SCHEMA public TO mcp_readonly;
|
|
134
|
+
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_readonly;
|
|
135
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO mcp_readonly;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The server warns on startup if you connect as a superuser.
|
|
139
|
+
|
|
140
|
+
**What this does not protect against.** The parser cannot see inside a user-defined function, so a volatile or
|
|
141
|
+
`SECURITY DEFINER` function called from an allowed `SELECT` can do anything its body does, including open its own
|
|
142
|
+
connection. The function denylist covers the built-in escapes, not yours. Layer 4 is what stops the rest, which is
|
|
143
|
+
why the read-only role is the recommended setup rather than an optional extra. Read-only is also not
|
|
144
|
+
confidentiality: anything the connected role can read, a model can read, so grant it only what you want an agent
|
|
145
|
+
to see.
|
|
146
|
+
|
|
147
|
+
## Migrating from `@modelcontextprotocol/server-postgres`
|
|
148
|
+
|
|
149
|
+
Swap the package name. The tool is still called `query`, still takes `sql`, still returns JSON rows, and the
|
|
150
|
+
connection string is still the first argument.
|
|
151
|
+
|
|
152
|
+
Four deliberate differences:
|
|
153
|
+
|
|
154
|
+
1. **Multi-statement SQL and `SET`/`RESET` are rejected** with a clear error. On the archived server these "worked",
|
|
155
|
+
and that was the vulnerability.
|
|
156
|
+
2. **Results are capped** at 1000 rows and 50,000 characters by default, and single values over 2,000 characters are
|
|
157
|
+
shortened. Truncation is stated in the output, never silent. Rows come back one per line rather than
|
|
158
|
+
pretty-printed, which roughly halves their token cost; it is still a JSON array. Dates and timestamps are exactly
|
|
159
|
+
what Postgres sent, not re-rendered in the server's timezone.
|
|
160
|
+
3. **Schema discovery is a tool, not just a resource.** Most clients do not auto-attach resources, which is why
|
|
161
|
+
models using the old server so often did not know the schema.
|
|
162
|
+
4. **All non-system schemas are visible**, not only `public`, and column descriptions come through from
|
|
163
|
+
`COMMENT ON`.
|
|
164
|
+
|
|
165
|
+
## Options
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
--max-rows <n> Maximum rows returned per query (default: 1000)
|
|
169
|
+
--max-output-chars <n> Character budget for one query result (default: 50000)
|
|
170
|
+
--statement-timeout <ms> Server-side statement timeout (default: 30000)
|
|
171
|
+
--context-file <path> Curated schema context (default: .contextflo/context.md)
|
|
172
|
+
--no-context-writes Do not offer add_table_context; the context file is only read
|
|
173
|
+
--log-file <path> Query audit log (default: .contextflo/log.md once that directory exists)
|
|
174
|
+
--no-log Never write a query log
|
|
175
|
+
--http Serve over streamable HTTP instead of stdio
|
|
176
|
+
--port <n> HTTP port (default: 8080)
|
|
177
|
+
--host <addr> HTTP bind address (default: 127.0.0.1)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`DATABASE_URL` supplies the connection string if you do not pass one. `AUTH_TOKEN`, with `--http`, requires that
|
|
181
|
+
value as a bearer token.
|
|
182
|
+
|
|
183
|
+
**Connection poolers.** PgBouncer (and so the pooled connection strings from Supabase, Neon, and others) refuses
|
|
184
|
+
the startup parameters this server normally sends. When that happens it reconnects without them and says so on
|
|
185
|
+
stderr. Nothing is weakened: every statement still runs in `BEGIN READ ONLY` with its own statement timeout, and CI
|
|
186
|
+
runs the read-only suite through PgBouncer in transaction mode.
|
|
187
|
+
|
|
188
|
+
## The context file
|
|
189
|
+
|
|
190
|
+
```markdown
|
|
191
|
+
# Database context
|
|
192
|
+
|
|
193
|
+
Revenue means gross, before refunds.
|
|
194
|
+
"Active customer" means an order in the last 90 days.
|
|
195
|
+
|
|
196
|
+
## Tables
|
|
197
|
+
|
|
198
|
+
### public.orders
|
|
199
|
+
One row per customer order. Source of truth for revenue, not `orders_legacy`.
|
|
200
|
+
|
|
201
|
+
- revenue_usd: Gross revenue, before refunds. Net lives in `order_refunds`.
|
|
202
|
+
- status: One of pending, paid, refunded.
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Everything above `## Tables` is handed to the model: as server instructions, and again at the top of `list_tables`,
|
|
206
|
+
because several clients never show the model server instructions. HTML comments are left out. Under `## Tables`, a
|
|
207
|
+
`###` heading names a table, the prose beneath describes it, and `- column: meaning` lines describe columns (a dash
|
|
208
|
+
separator works too). Your text wins over `COMMENT ON`. Edits take effect on the next tool call; no restart.
|
|
209
|
+
|
|
210
|
+
A relative `--context-file` resolves against the directory the client starts the server in. Claude Desktop starts
|
|
211
|
+
servers in `/`, so there it falls back to your home directory (`~/.contextflo/context.md`). For a file that lives in
|
|
212
|
+
a repo, pass an absolute path. The server prints the path it is using on startup.
|
|
213
|
+
|
|
214
|
+
**The agent adds to it.** When the model finds something the schema does not say (`amount` is in cents, `status`
|
|
215
|
+
also holds `'void'` on old rows, every query needs `deleted_at IS NULL`), `add_table_context` appends it to this file,
|
|
216
|
+
and every later session starts knowing it. Notes are only ever appended, never substituted for what you wrote, and
|
|
217
|
+
only for tables and columns that exist. Treat them like any other change: keep the file in git and read the diff. A
|
|
218
|
+
model can be wrong, and text inside your data can steer what it writes, so a note is a suggestion until someone has
|
|
219
|
+
looked at it. `--no-context-writes` turns the tool off.
|
|
220
|
+
|
|
221
|
+
`init` seeds this from existing comments, and only for columns that already have one. A file with a blank
|
|
222
|
+
placeholder for all 4000 columns is a file nobody edits.
|
|
223
|
+
|
|
224
|
+
Richer context generated from your code and docs, and shared across a team, is what
|
|
225
|
+
[contextflo.com](https://contextflo.com) does.
|
|
226
|
+
|
|
227
|
+
## Query log
|
|
228
|
+
|
|
229
|
+
Once `.contextflo/` exists, every statement is appended to `.contextflo/log.md` with its outcome, row count, and
|
|
230
|
+
duration. It is written for you, not fed back to the model. `--no-log` turns it off.
|
|
231
|
+
|
|
232
|
+
## Docker
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
docker run -i --rm ghcr.io/contextflo/postgres-mcp postgresql://host.docker.internal/mydb
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
For a remote endpoint, see [`docker-compose.example.yml`](docker-compose.example.yml). Note what you are doing
|
|
239
|
+
before you bind `0.0.0.0`: that is a live database connection on a port. Set `AUTH_TOKEN`, keep it inside a private
|
|
240
|
+
network, and terminate TLS in front of it, because the token is plaintext on the wire otherwise. The server prints a
|
|
241
|
+
warning when it is exposed without a token.
|
|
242
|
+
|
|
243
|
+
On the default loopback bind, requests whose `Host` or `Origin` is not local are refused. That is what stops a web
|
|
244
|
+
page from using DNS rebinding to reach a server on `127.0.0.1` through your browser.
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
npm install
|
|
250
|
+
npm run build
|
|
251
|
+
npm test
|
|
252
|
+
|
|
253
|
+
npm run db:up # throwaway Postgres for the integration suite (needs Docker)
|
|
254
|
+
TEST_DATABASE_URL=postgres://postgres:postgres@localhost:55432/postgres npm test
|
|
255
|
+
npm run db:down
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The integration suite deliberately bypasses the parser and drives the database directly, so it proves the wire and
|
|
259
|
+
transaction layers rather than the validator. It skips without `TEST_DATABASE_URL` and is required to run in CI.
|
|
260
|
+
|
|
261
|
+
## Security
|
|
262
|
+
|
|
263
|
+
Please report vulnerabilities privately. See [SECURITY.md](SECURITY.md).
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
MIT
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface HttpConfig {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
/** From the AUTH_TOKEN environment variable. Unset means no authentication. */
|
|
5
|
+
authToken: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export interface ServerConfig {
|
|
8
|
+
command: 'serve' | 'init';
|
|
9
|
+
connectionString: string;
|
|
10
|
+
maxRows: number;
|
|
11
|
+
maxOutputChars: number;
|
|
12
|
+
statementTimeoutMs: number;
|
|
13
|
+
/** Absolute. */
|
|
14
|
+
contextFile: string;
|
|
15
|
+
/** Whether the add_table_context tool is offered. */
|
|
16
|
+
contextWrites: boolean;
|
|
17
|
+
contextDirectory: string;
|
|
18
|
+
logFile: string | undefined;
|
|
19
|
+
logDisabled: boolean;
|
|
20
|
+
/** Undefined means stdio. */
|
|
21
|
+
http: HttpConfig | undefined;
|
|
22
|
+
}
|
|
23
|
+
export declare const DEFAULT_MAX_ROWS = 1000;
|
|
24
|
+
/** About 12k tokens: room for a real answer, not enough to crowd out the conversation. */
|
|
25
|
+
export declare const DEFAULT_MAX_OUTPUT_CHARS = 50000;
|
|
26
|
+
export declare const DEFAULT_STATEMENT_TIMEOUT_MS = 30000;
|
|
27
|
+
export declare const DEFAULT_HTTP_PORT = 8080;
|
|
28
|
+
/** Loopback by default: exposing a database to the network should take a deliberate flag. */
|
|
29
|
+
export declare const DEFAULT_HTTP_HOST = "127.0.0.1";
|
|
30
|
+
export declare class ConfigError extends Error {
|
|
31
|
+
}
|
|
32
|
+
/** Not a failure — `--help` is a successful invocation that prints and exits 0. */
|
|
33
|
+
export declare class HelpRequested extends Error {
|
|
34
|
+
constructor();
|
|
35
|
+
}
|
|
36
|
+
export declare const USAGE = "postgres-mcp \u2014 the analytics MCP server for Postgres (read-only)\n\nUsage:\n npx @contextflo/postgres-mcp <connection-string> [options]\n npx @contextflo/postgres-mcp init <connection-string>\n\nThe connection string may also be supplied via the DATABASE_URL environment variable.\n\nCommands:\n init Scan the schema and write .contextflo/context.md for you to edit\n\nOptions:\n --max-rows <n> Maximum rows returned per query (default: 1000)\n --max-output-chars <n> Character budget for one query result (default: 50000)\n --statement-timeout <ms> Server-side statement timeout in milliseconds (default: 30000)\n --context-file <path> Curated schema context served to the model (default: .contextflo/context.md)\n --no-context-writes Do not offer add_table_context; the context file is only read\n --log-file <path> Append a query audit log here (default: .contextflo/log.md once that directory exists)\n --no-log Never write a query log\n --http Serve over streamable HTTP instead of stdio\n --port <n> HTTP port (default: 8080)\n --host <addr> HTTP bind address (default: 127.0.0.1; use 0.0.0.0 to expose)\n -h, --help Show this message\n\nEnvironment:\n DATABASE_URL Connection string, if not given as an argument\n AUTH_TOKEN If set with --http, require this as a bearer token";
|
|
37
|
+
/**
|
|
38
|
+
* The connection URL is the first positional argument, matching the archived
|
|
39
|
+
* `@modelcontextprotocol/server-postgres` so migrating is a package-name swap.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseArgs(argv: string[], env?: NodeJS.ProcessEnv, cwd?: string): ServerConfig;
|
|
42
|
+
/**
|
|
43
|
+
* Relative paths resolve against the working directory — except when that is the
|
|
44
|
+
* filesystem root, which is where Claude Desktop and some other clients start servers.
|
|
45
|
+
* `/.contextflo/context.md` is never what anyone meant, so fall back to the home directory.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveContextFile(path: string, cwd: string, home?: string): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
3
|
+
import { DEFAULT_CONTEXT_DIRECTORY, DEFAULT_CONTEXT_FILE } from './context/context-file.js';
|
|
4
|
+
export const DEFAULT_MAX_ROWS = 1000;
|
|
5
|
+
/** About 12k tokens: room for a real answer, not enough to crowd out the conversation. */
|
|
6
|
+
export const DEFAULT_MAX_OUTPUT_CHARS = 50_000;
|
|
7
|
+
export const DEFAULT_STATEMENT_TIMEOUT_MS = 30_000;
|
|
8
|
+
export const DEFAULT_HTTP_PORT = 8080;
|
|
9
|
+
/** Loopback by default: exposing a database to the network should take a deliberate flag. */
|
|
10
|
+
export const DEFAULT_HTTP_HOST = '127.0.0.1';
|
|
11
|
+
export class ConfigError extends Error {
|
|
12
|
+
}
|
|
13
|
+
/** Not a failure — `--help` is a successful invocation that prints and exits 0. */
|
|
14
|
+
export class HelpRequested extends Error {
|
|
15
|
+
constructor() {
|
|
16
|
+
super(USAGE);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export const USAGE = `postgres-mcp — the analytics MCP server for Postgres (read-only)
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
npx @contextflo/postgres-mcp <connection-string> [options]
|
|
23
|
+
npx @contextflo/postgres-mcp init <connection-string>
|
|
24
|
+
|
|
25
|
+
The connection string may also be supplied via the DATABASE_URL environment variable.
|
|
26
|
+
|
|
27
|
+
Commands:
|
|
28
|
+
init Scan the schema and write ${DEFAULT_CONTEXT_FILE} for you to edit
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--max-rows <n> Maximum rows returned per query (default: ${DEFAULT_MAX_ROWS})
|
|
32
|
+
--max-output-chars <n> Character budget for one query result (default: ${DEFAULT_MAX_OUTPUT_CHARS})
|
|
33
|
+
--statement-timeout <ms> Server-side statement timeout in milliseconds (default: ${DEFAULT_STATEMENT_TIMEOUT_MS})
|
|
34
|
+
--context-file <path> Curated schema context served to the model (default: ${DEFAULT_CONTEXT_FILE})
|
|
35
|
+
--no-context-writes Do not offer add_table_context; the context file is only read
|
|
36
|
+
--log-file <path> Append a query audit log here (default: ${DEFAULT_CONTEXT_DIRECTORY}/log.md once that directory exists)
|
|
37
|
+
--no-log Never write a query log
|
|
38
|
+
--http Serve over streamable HTTP instead of stdio
|
|
39
|
+
--port <n> HTTP port (default: ${DEFAULT_HTTP_PORT})
|
|
40
|
+
--host <addr> HTTP bind address (default: ${DEFAULT_HTTP_HOST}; use 0.0.0.0 to expose)
|
|
41
|
+
-h, --help Show this message
|
|
42
|
+
|
|
43
|
+
Environment:
|
|
44
|
+
DATABASE_URL Connection string, if not given as an argument
|
|
45
|
+
AUTH_TOKEN If set with --http, require this as a bearer token`;
|
|
46
|
+
/**
|
|
47
|
+
* The connection URL is the first positional argument, matching the archived
|
|
48
|
+
* `@modelcontextprotocol/server-postgres` so migrating is a package-name swap.
|
|
49
|
+
*/
|
|
50
|
+
export function parseArgs(argv, env = process.env, cwd = process.cwd()) {
|
|
51
|
+
let command = 'serve';
|
|
52
|
+
let connectionString;
|
|
53
|
+
let maxRows = DEFAULT_MAX_ROWS;
|
|
54
|
+
let maxOutputChars = DEFAULT_MAX_OUTPUT_CHARS;
|
|
55
|
+
let contextWrites = true;
|
|
56
|
+
let statementTimeoutMs = DEFAULT_STATEMENT_TIMEOUT_MS;
|
|
57
|
+
let contextFile = DEFAULT_CONTEXT_FILE;
|
|
58
|
+
let logFile;
|
|
59
|
+
let logDisabled = false;
|
|
60
|
+
let http = false;
|
|
61
|
+
let port = DEFAULT_HTTP_PORT;
|
|
62
|
+
let host = DEFAULT_HTTP_HOST;
|
|
63
|
+
for (let index = 0; index < argv.length; index++) {
|
|
64
|
+
const arg = argv[index];
|
|
65
|
+
switch (arg) {
|
|
66
|
+
case 'init':
|
|
67
|
+
if (index !== 0)
|
|
68
|
+
throw new ConfigError(`"init" must come first.\n\n${USAGE}`);
|
|
69
|
+
command = 'init';
|
|
70
|
+
break;
|
|
71
|
+
case '--max-rows':
|
|
72
|
+
maxRows = requirePositiveInteger(arg, argv[++index]);
|
|
73
|
+
break;
|
|
74
|
+
case '--max-output-chars':
|
|
75
|
+
maxOutputChars = requirePositiveInteger(arg, argv[++index]);
|
|
76
|
+
break;
|
|
77
|
+
case '--no-context-writes':
|
|
78
|
+
contextWrites = false;
|
|
79
|
+
break;
|
|
80
|
+
case '--statement-timeout':
|
|
81
|
+
statementTimeoutMs = requirePositiveInteger(arg, argv[++index]);
|
|
82
|
+
break;
|
|
83
|
+
case '--context-file':
|
|
84
|
+
contextFile = requireValue(arg, argv[++index]);
|
|
85
|
+
break;
|
|
86
|
+
case '--log-file':
|
|
87
|
+
logFile = requireValue(arg, argv[++index]);
|
|
88
|
+
break;
|
|
89
|
+
case '--no-log':
|
|
90
|
+
logDisabled = true;
|
|
91
|
+
break;
|
|
92
|
+
case '--http':
|
|
93
|
+
http = true;
|
|
94
|
+
break;
|
|
95
|
+
case '--port':
|
|
96
|
+
port = requirePositiveInteger(arg, argv[++index]);
|
|
97
|
+
break;
|
|
98
|
+
case '--host':
|
|
99
|
+
host = requireValue(arg, argv[++index]);
|
|
100
|
+
break;
|
|
101
|
+
case '-h':
|
|
102
|
+
case '--help':
|
|
103
|
+
throw new HelpRequested();
|
|
104
|
+
default:
|
|
105
|
+
if (arg.startsWith('-')) {
|
|
106
|
+
throw new ConfigError(`Unknown option: ${arg}\n\n${USAGE}`);
|
|
107
|
+
}
|
|
108
|
+
if (connectionString !== undefined) {
|
|
109
|
+
throw new ConfigError(`Unexpected argument: ${arg}\n\n${USAGE}`);
|
|
110
|
+
}
|
|
111
|
+
connectionString = arg;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
connectionString ??= env.DATABASE_URL;
|
|
115
|
+
if (!connectionString) {
|
|
116
|
+
throw new ConfigError(`A Postgres connection string is required.\n\n${USAGE}`);
|
|
117
|
+
}
|
|
118
|
+
if (logFile && logDisabled) {
|
|
119
|
+
throw new ConfigError('--log-file and --no-log contradict each other.');
|
|
120
|
+
}
|
|
121
|
+
const resolvedContextFile = resolveContextFile(contextFile, cwd);
|
|
122
|
+
return {
|
|
123
|
+
command,
|
|
124
|
+
connectionString,
|
|
125
|
+
maxRows,
|
|
126
|
+
maxOutputChars,
|
|
127
|
+
statementTimeoutMs,
|
|
128
|
+
contextFile: resolvedContextFile,
|
|
129
|
+
contextWrites,
|
|
130
|
+
contextDirectory: dirname(resolvedContextFile),
|
|
131
|
+
logFile,
|
|
132
|
+
logDisabled,
|
|
133
|
+
http: http ? { host, port, authToken: env.AUTH_TOKEN || undefined } : undefined,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Relative paths resolve against the working directory — except when that is the
|
|
138
|
+
* filesystem root, which is where Claude Desktop and some other clients start servers.
|
|
139
|
+
* `/.contextflo/context.md` is never what anyone meant, so fall back to the home directory.
|
|
140
|
+
*/
|
|
141
|
+
export function resolveContextFile(path, cwd, home = homedir()) {
|
|
142
|
+
if (isAbsolute(path))
|
|
143
|
+
return path;
|
|
144
|
+
return resolve(cwd === '/' || /^[A-Za-z]:\\?$/.test(cwd) ? home : cwd, path);
|
|
145
|
+
}
|
|
146
|
+
function requireValue(flag, raw) {
|
|
147
|
+
if (!raw || raw.startsWith('-')) {
|
|
148
|
+
throw new ConfigError(`${flag} expects a value, received: ${raw ?? '(nothing)'}`);
|
|
149
|
+
}
|
|
150
|
+
return raw;
|
|
151
|
+
}
|
|
152
|
+
function requirePositiveInteger(flag, raw) {
|
|
153
|
+
const value = Number(raw);
|
|
154
|
+
if (!raw || !Number.isInteger(value) || value <= 0) {
|
|
155
|
+
throw new ConfigError(`${flag} expects a positive integer, received: ${raw ?? '(nothing)'}`);
|
|
156
|
+
}
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.contextflo/context.md` — the whole context layer.
|
|
3
|
+
*
|
|
4
|
+
* No database, no embeddings, no index: a markdown file a human edits and this server
|
|
5
|
+
* hands to the model. The catalog can say a column is `numeric(12,2)` named `revenue`;
|
|
6
|
+
* only a person can say it is gross rather than net, or that `fct_orders_v2` is the one
|
|
7
|
+
* the team actually uses.
|
|
8
|
+
*
|
|
9
|
+
* Parsing is deliberately forgiving. This file's job is to be edited by hand, so anything
|
|
10
|
+
* it does not recognise is left alone rather than treated as an error — a file that
|
|
11
|
+
* rejects your notes is a file you stop updating.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_CONTEXT_DIRECTORY = ".contextflo";
|
|
14
|
+
export declare const DEFAULT_CONTEXT_FILE = ".contextflo/context.md";
|
|
15
|
+
export interface TableNotes {
|
|
16
|
+
description?: string;
|
|
17
|
+
columns: Map<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export interface ContextDocument {
|
|
20
|
+
/** Free text above `## Tables`: business definitions, caveats, which tables to prefer. */
|
|
21
|
+
preamble: string;
|
|
22
|
+
/** Keyed by lower-cased `schema.table`. */
|
|
23
|
+
tables: Map<string, TableNotes>;
|
|
24
|
+
}
|
|
25
|
+
export declare function emptyContextDocument(): ContextDocument;
|
|
26
|
+
/** Returns null when the file does not exist — the server runs fine without one. */
|
|
27
|
+
export declare function loadContextFile(path: string): Promise<ContextDocument | null>;
|
|
28
|
+
export declare function parseContextFile(raw: string): ContextDocument;
|
|
29
|
+
export declare function notesForTable(document: ContextDocument, fullyQualifiedName: string): TableNotes | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* The curated file wins over the database comment. `init` seeds the file from comments,
|
|
32
|
+
* so the two agree until somebody edits — and an edit is exactly the signal to prefer it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function preferCuratedDescription(curated: string | undefined, fromCatalog: string | null): string | null;
|
|
35
|
+
export interface NewTableNotes {
|
|
36
|
+
/** Appended to the table's description. */
|
|
37
|
+
note?: string | undefined;
|
|
38
|
+
/** Column name → note, appended to that column's line or added as a new one. */
|
|
39
|
+
columns?: Record<string, string> | undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Adds notes to the file's text, touching nothing else — the file belongs to the humans
|
|
43
|
+
* editing it, so formatting, comments, and ordering all survive.
|
|
44
|
+
*
|
|
45
|
+
* Append-only by design. A note never replaces what is there: when the table or column
|
|
46
|
+
* already has a description, the note is added after it. Anything an agent writes can be
|
|
47
|
+
* reviewed in a diff and deleted, and nothing a person wrote is ever lost to it.
|
|
48
|
+
*
|
|
49
|
+
* `fullyQualifiedName` is the resolved `schema.table`. An existing section headed with
|
|
50
|
+
* the bare table name is reused rather than duplicated.
|
|
51
|
+
*/
|
|
52
|
+
export declare function addTableNotes(raw: string, fullyQualifiedName: string, notes: NewTableNotes): string;
|
|
53
|
+
export declare const CONTEXT_FILE_HEADER = "# Database context\n\n<!--\npostgres-mcp reads this file and hands it to the model every session. It is the whole\ncontext layer \u2014 no database, no index, just this file.\n\nEverything above \"## Tables\" is passed through verbatim. Put the things your team\nargues about here: whether revenue is gross or net, what counts as an active customer,\nwhich table is the source of truth and which one nobody got around to dropping.\n\nUnder \"## Tables\", a \"###\" heading names a table, the prose beneath it describes the\ntable, and each \"- column \u2014 meaning\" line describes a column. Seeded from your\ndatabase's COMMENT ON values; anything you write wins over those.\n\nAdd a line for any column you like \u2014 only already-commented ones were seeded, to keep\nthis file small enough that you will actually edit it.\n\nThe agent can add notes here too, with the add_table_context tool, when it finds\nsomething about the data the next person would get wrong. It only ever appends, so\nreview those additions in a diff like any other change.\n-->\n\n## Business definitions\n\n_Replace this with the definitions a new analyst would get wrong on their first day._\n\n## Tables\n";
|