mcp/ntl-postgres-mcp-server.
Its tool surface follows the shape of the Supabase MCP server, so an agent
familiar with one is immediately productive with the other.
What an agent can do with it
0.44 but a decayed
weight of 0.21. Decay is applied lazily, so an operator comparing the stored
value against a live routing decision would be comparing the wrong number.
The tool shows both.
Tools
Read-only, always available:
Write tools appear only when an operator enables them:
The safety model
This is the part worth understanding before you deploy it — or copy it.Read-only is enforced by Postgres, not by parsing SQL
The tempting approach is to inspect the query and reject anything that looks like a write. Every implementation that does this can be talked around:BEGIN TRANSACTION READ ONLY and the database rejects the write with
SQLSTATE 25006.
The test suite fires sixteen real bypasses at it and then asserts the data is
untouched — not merely that an error was returned.
Writes are off by default, and absent rather than refusing
ALLOW_WRITES defaults to false. When it is off, write tools are not
registered at all — they do not appear in the tool list. Offering a tool that
can only fail wastes an agent’s turn and teaches it nothing.
Enabling writes is per-environment, so it is a deliberate act against a named
target rather than a variable flip on production.
Auth is required
WithMCP_AUTH_TOKEN unset, the server returns 500 to every request rather
than serving unauthenticated. A server holding database credentials with no auth
is an open SQL console on the public internet. Token comparison is timing-safe.
Output is bounded, and says when it was cut
A tool returning a million rows does not help an agent — it exhausts the context the agent needs to reason with. Output is capped, and truncation is reported explicitly. Silent truncation is the worse failure: an agent that believes it saw a whole table will draw conclusions from a fragment.Deploying
Hyperdrive is not optional in production. Without pooling, every Worker
invocation opens its own Postgres connection, and a traffic burst exhausts
max_connections long before it exhausts anything else — a failure that looks
like a database outage rather than a capacity problem. DATABASE_URL is for
local development.Creating the schema
The server can create the openNTL Postgres schema itself:ntl-store-postgres
backend, and is exposed as an MCP resource at ntl://schema/postgres so an
agent about to write a migration can read it first. See
storage backends.
Copying it as a template
Portable as-is: the safety layer, formatting and truncation, the Worker entry
point with auth, the schema and SQL tools, and the whole test harness.
Testing without a database service
Tests run against PGlite — real Postgres compiled to WebAssembly, in-process — so CI needs no database. SetTEST_DATABASE_URL and
the same suite also runs against a live server.
Running both is not belt-and-braces. It caught two real bugs:
- Multi-statement SQL. Parameterised statements use the extended protocol, which accepts exactly one command. Schema creation failed with SQLSTATE 42601 on a script that worked as separate statements.
- BIGINT type instability. postgres.js returns BIGINT as a string, PGlite
as a number, so a tool’s structured output changed type depending on the
driver. Now normalised to a string — correct regardless, since BIGINT exceeds
Number.MAX_SAFE_INTEGERand openNTL stores nanosecond timestamps in it.
Related
Storage Backends
The multi-database matrix, including the PostgreSQL backend
Storage Interface
The normative contract every backend satisfies
Learning Model
What the health tool’s ratios actually mean
Quickstart
Get a node running first