loading…
Search for a command to run...
loading…
A PostgreSQL MCP server for AI agents that streams large result sets to CSV, enforces per-query timeouts, auto-reconnects with backoff, and supports long-runnin
A PostgreSQL MCP server for AI agents that streams large result sets to CSV, enforces per-query timeouts, auto-reconnects with backoff, and supports long-running tunnel scripts with credential rotation.
A PostgreSQL MCP server for AI agents. Streams large result sets to CSV, enforces per-query timeouts, auto-reconnects with backoff, and supports long-running tunnel scripts (e.g. AWS SSM port-forwarding) with credential rotation.
Fork of crystaldba/postgres-mcp.
Jump to: Other AI agents · Alternative install methods · Develop
Python 3.10+. Published on PyPI as fluid-postgres-mcp; console entry point of the same name.
Before wiring the MCP into your agent, confirm the install actually resolved its dependencies:
uvx fluid-postgres-mcp --version # prints "fluid-postgres-mcp X.Y.Z", exit 0
uvx fluid-postgres-mcp --help # prints usage, exit 0
Exit 0 from either command means the package downloaded and every
runtime dependency imported successfully. A Python traceback or
non-zero exit means at least one import failed — typically a missing
system library (e.g. libpq on minimal Linux images) or a broken
uvx cache. Fix that before continuing; an agent registration
against a broken install fails silently at first tool call.
claude mcp add fluid-postgres-mcp -- \
uvx fluid-postgres-mcp \
postgresql://reader:pw@host:5432/db
With a long-running tunnel script (see Pre-connect scripts for the protocol the script must speak):
claude mcp add fluid-postgres-mcp -- \
uvx fluid-postgres-mcp \
--pre-connect-script /path/to/your-tunnel.sh
Brief one-shot snippets — copy-paste, or read your agent's own MCP
docs for the full story. All entries use uvx fluid-postgres-mcp
so no global install is needed.
Codex CLI — docs:
codex mcp add fluid-postgres-mcp \
--transport stdio \
--command "uvx fluid-postgres-mcp postgresql://reader:pw@host:5432/db"
Cursor CLI — docs:
agent mcp add fluid-postgres-mcp -- \
uvx fluid-postgres-mcp postgresql://reader:pw@host:5432/db
Gemini CLI — add to ~/.gemini/settings.json
(docs):
{
"mcpServers": {
"fluid-postgres-mcp": {
"command": "uvx",
"args": ["fluid-postgres-mcp", "postgresql://reader:pw@host:5432/db"]
}
}
}
opencode — add to opencode.json
(docs):
{
"mcp": {
"fluid-postgres-mcp": {
"type": "local",
"command": ["uvx", "fluid-postgres-mcp", "postgresql://reader:pw@host:5432/db"]
}
}
}
Kiro CLI — add to mcp.json
(docs):
{
"mcpServers": {
"fluid-postgres-mcp": {
"command": "uvx",
"args": ["fluid-postgres-mcp", "postgresql://reader:pw@host:5432/db"]
}
}
}
Cursor (IDE) — add to ~/.cursor/mcp.json
(docs):
{
"mcpServers": {
"fluid-postgres-mcp": {
"command": "uvx",
"args": ["fluid-postgres-mcp", "postgresql://reader:pw@host:5432/db"]
}
}
}
Windsurf — add to ~/.codeium/windsurf/mcp_config.json
(docs):
{
"mcpServers": {
"fluid-postgres-mcp": {
"command": "uvx",
"args": ["fluid-postgres-mcp", "postgresql://reader:pw@host:5432/db"]
}
}
}
Zed — add to ~/.config/zed/settings.json under
context_servers (note: not mcpServers)
(docs):
{
"context_servers": {
"fluid-postgres-mcp": {
"command": "uvx",
"args": ["fluid-postgres-mcp", "postgresql://reader:pw@host:5432/db"]
}
}
}
If you'd rather have a persistent install than resolve through
uvx on every launch:
pipx install fluid-postgres-mcp # isolated, on $PATH
pip install fluid-postgres-mcp # use a virtualenv to avoid global pollution
From source (no editable; for users who clone but don't want a working tree):
git clone https://github.com/povesma/fluid-postgres-mcp
pip install ./fluid-postgres-mcp
After any of these, the agent snippets above can drop uvx and
invoke fluid-postgres-mcp directly.
Tools exposed: execute_sql, status, list_schemas, list_objects,
get_object_details, explain_query, analyze_db_health,
analyze_query_indexes, analyze_workload_indexes, get_top_queries.
execute_sql accepts timeout_ms, output_file, and output_mode
(inline / file / file+inline) for CSV streaming.
Every flag has a matching env var (PGMCP_*). CLI wins.
| Flag | Default | What it does |
|---|---|---|
database_url (positional) / DATABASE_URI |
required | PostgreSQL URL |
--default-timeout |
0 |
statement_timeout ms (0 = none) |
--reconnect-initial-delay / --reconnect-max-delay |
1.0 / 60.0 |
Backoff bounds (s) |
--reconnect-max-attempts |
0 |
0 = unlimited |
--pre-connect-script |
none | Tunnel/setup script (see below) |
--hook-timeout |
30.0 |
Pre-connect-script timeout (s) |
--event-buffer-size |
100 |
Per-category ring buffer |
--output-dir |
. |
Default base for CSV output |
--transport |
stdio |
stdio / sse / streamable-http |
Two modes, auto-detected from script behaviour. Existing run-and-exit scripts work unchanged.
Run-and-exit: the script runs, exits 0, and the MCP connects. Suitable when something else owns the tunnel.
Long-running: the script owns the tunnel for the lifetime of the MCP. It speaks a line-prefixed stdout protocol:
[MCP] DB_URL postgresql://user:pw@host:port/db # optional, overrides --database-url
[MCP] READY_TO_CONNECT # required, signals readiness
If the script process dies (tunnel broke), the MCP detects it within ~1 second, respawns the script, and reconnects with whatever URL the new instance emits — which is how credential/URL rotation works.
Two vendored Python reference scripts cover the common AWS topologies.
Each is a single drop-in file: copy it, set the env vars, point
--pre-connect-script at it. Both speak the long-running protocol
described above; both supervise their SSM child and exit on its
death so fluid-postgres-mcp respawns them.
| Topology | Script | When |
|---|---|---|
| EC2-direct | scripts/examples/aws-ssm-ec2-tunnel.py | PostgreSQL runs on the EC2 instance itself, or the EC2 hosts a userspace proxy you control. |
| RDS-via-EC2 | scripts/examples/aws-ssm-rds-tunnel.py | PostgreSQL runs on RDS. The EC2 is a pure SSM forwarder — no PG, no proxy on it. Uses AWS-StartPortForwardingSessionToRemoteHost. |
True bastion-less SSM-to-RDS is not possible: ssm:StartSession
requires an SSM-managed target, and RDS is not one. If you cannot
keep an EC2 in the loop, look at RDS IAM authentication or the EC2
Instance Connect Endpoint (EICE) — both are outside the SSM
port-forwarding model these scripts use.
Passwords in DB_URL are obfuscated in every fluid-postgres-mcp
event message and log line.
The scripts are configured entirely via environment variables,
passed through your agent's MCP registration (e.g. claude mcp add … -e KEY=VALUE).
| Variable | EC2-direct | RDS-via-EC2 | Purpose |
|---|---|---|---|
EC2_INSTANCE_ID |
required | required | SSM target instance |
EC2_REGION |
required | required | AWS region of the instance |
RDS_ENDPOINT |
— | required | RDS endpoint hostname |
DB_NAME |
required | required | PostgreSQL database name |
DB_USERNAME |
required | required | PostgreSQL user |
DB_PASSWD |
required | required | PostgreSQL password |
DB_HOST |
optional (localhost) |
— | Host PG listens on (EC2-direct only) |
DB_PORT |
optional (5432) |
optional (5432) |
PostgreSQL port |
ASSUME_ROLE_ARN |
optional | optional | Role to assume on top of base credentials |
AWS_PROFILE |
optional | optional | Profile (overridden by --profile flag) |
Authentication precedence (highest first): --profile CLI flag →
AWS_PROFILE → SDK default credential chain (env vars,
~/.aws/credentials, instance metadata, …). If ASSUME_ROLE_ARN
is set, the resolved base credentials drive an sts:AssumeRole
call and the resulting STS credentials drive every subsequent AWS
call.
The principal that ends up driving the AWS calls (after AssumeRole, if any) needs:
sts:AssumeRole (only if ASSUME_ROLE_ARN set)
sts:GetCallerIdentity (diagnostic)
ec2:DescribeInstances
ec2:StartInstances (only to wake a stopped host)
ssm:DescribeInstanceInformation
ssm:StartSession (see below for resource scope)
ssm:TerminateSession (on the session ARN — clean teardown)
The ssm:StartSession resource scope differs by topology:
AWS-StartPortForwardingSession.AWS-StartPortForwardingSessionToRemoteHost. The EC2's security
group must allow egress to RDS:5432; the RDS security group must
allow ingress from the EC2 security group.Both scripts emit exactly two lines on stdout (everything else goes to stderr):
[MCP] DB_URL postgresql://<user>:<pw>@127.0.0.1:<local_port>/<db>?...
[MCP] READY_TO_CONNECT
After these the script stays alive supervising the SSM child. Exit on child death is the signal to fluid-postgres-mcp that the tunnel is gone and the script should be respawned — that is the recovery loop.
PostgreSQL is reachable on the EC2 itself (running there, or
proxied by the EC2 to a backend it controls). The SSM session
terminates on the EC2 and forwards traffic to whatever
DB_HOST:DB_PORT resolves to from the EC2's perspective
(default localhost:5432).
claude mcp add my-pg \
-e EC2_INSTANCE_ID=i-0123456789abcdef0 \
-e EC2_REGION=eu-central-1 \
-e DB_NAME=mydb -e DB_USERNAME=reader -e DB_PASSWD='s3cr3t' \
-- uvx fluid-postgres-mcp \
--pre-connect-script /path/to/aws-ssm-ec2-tunnel.py
PostgreSQL runs on RDS. The EC2 is a pure SSM forwarder — no PG
process, no userspace proxy. The SSM session uses
AWS-StartPortForwardingSessionToRemoteHost with host=$RDS_ENDPOINT,
so traffic flows
localhost:<local_port> → EC2 (SSM forwarder) → $RDS_ENDPOINT:5432.
claude mcp add my-pg \
-e EC2_INSTANCE_ID=i-0123456789abcdef0 \
-e EC2_REGION=eu-central-1 \
-e RDS_ENDPOINT=my-db.abcdef.eu-central-1.rds.amazonaws.com \
-e DB_NAME=mydb -e DB_USERNAME=reader -e DB_PASSWD='s3cr3t' \
-- uvx fluid-postgres-mcp \
--pre-connect-script /path/to/aws-ssm-rds-tunnel.py
After registering, restart your agent and run a real-data query —
not SELECT 1. A constant query proves only that something
answered on the socket; it doesn't prove the right DB was mapped
or that rows flow:
SELECT count(*) FROM <a-known-populated-table>;
Expect a non-trivial count you can recognise. Zero / empty / NULL is a failure, not a pass.
Working examples — copy and adapt:
sts:AssumeRole, EC2 wake, SSM agent readiness wait, port-forward,
port-open probe, PG liveness probe, handshake, signal teardown,
remote session termination. EC2-direct topology.AWS-StartPortForwardingSessionToRemoteHost so the EC2 acts as a
pure SSM forwarder to an RDS endpoint.DB_URL + READY_TO_CONNECT,
then blocks on exec sleep until SIGTERM. Useful as a starting
template.create_long_running_tunnel_script) — full SSM port-forwarding
variant: spawns aws ssm start-session as a child, fetches the
password from Parameter Store, emits the protocol lines, then
waits on the SSM child so tunnel death exits the script.create_tunnel_script) — run-and-exit SSM variant for the legacy
flow (something else owns the tunnel lifecycle).wait "$TUNNEL_PID" (foreground
child) or exec sleep <large> (when there's no child to wait on)
both work; backgrounded sleep & wait $! with a trap is
unreliable on macOS (the parent's proc.wait() does not
observe SIGCHLD through it).sleep does not accept infinity. Use a large integer
(exec sleep 2147483647).READY_TO_CONNECT
within --hook-timeout → script killed, connect fails. Malformed
[MCP] DB_URL payload → warning event recorded, prior override
retained, MCP falls back to the configured URL. Unknown [MCP]
keywords are warned once per keyword and ignored.
Long-running script alive but no [MCP] DB_URL yet → state is
WAITING_FOR_URL, the reconnect loop keeps polling, recoverable
as soon as the script emits a URL. Run-and-exit script exited
without emitting DB_URL and no DATABASE_URI / positional URL
is set → _unrecoverable=True, state ERROR, no further retries.status MCP tool — started/pid,
READY_TO_CONNECT, DB_URL (host/db only, password redacted),
exited/exit_code. See ARCHITECTURE.md
for how the event store is wired and
TESTING-METHODOLOGY.md for the faults
we inject against it.Work from a clone:
git clone https://github.com/povesma/fluid-postgres-mcp
cd fluid-postgres-mcp
pip install -e ".[dev]"
pytest
Design and fault-injection catalogue: ARCHITECTURE.md · TESTING-METHODOLOGY.md.
Versioning is SemVer. PyPI is the point of no return — a version uploaded there cannot be edited or reuploaded, only yanked. Everything reversible happens before PyPI publish; GitHub Release stays in draft until PyPI succeeds. A release is not done until git tag, GitHub Release, and PyPI all agree.
The release flow is automated by scripts/release.sh. The script runs the seven steps below as a single transaction: pre-PyPI failures roll back (delete local tag + draft Release); post-PyPI failures surface loudly with the exact recovery command. scripts/release-check.sh asserts the three-source agreement (tag / GH Release / PyPI) for any version — run it any time to catch half-released states.
Author work (the script verifies but does not author):
## [X.Y.Z] - YYYY-MM-DD section to CHANGELOG.md per
the CHANGELOG.md authoring rule below.version = "X.Y.Z" in pyproject.toml.scripts/release.sh — see modes below.Non-interactive mode (AI agent or scripted, all inputs supplied):
scripts/release.sh --version X.Y.Z \
--release-body-file /path/to/release-body.md \
--yes
Interactive mode (human-driven, missing inputs prompt
$EDITOR pre-populated with the relevant authoring rule):
scripts/release.sh # prompts for version + Release body
scripts/release.sh --version 0.1.4 # prompts for Release body only
The seven steps the script orchestrates (for reference, and for the rare case the script fails partway and a step must be re-run by hand):
# 1. Tag — version + CHANGELOG already committed by the author
git tag -a vX.Y.Z -m "Release X.Y.Z"
# 2. Clean and build
rm -rf dist/ build/ *.egg-info
uvx --from build pyproject-build
# 3. Inspect sdist + twine check (script rejects .env / .claude /
# tasks leaks via the sdist allowlist)
tar -tzf dist/*.tar.gz | sort
.venv/bin/twine check dist/*
# 4. Push commit and tag
git push
git push origin vX.Y.Z
# 5. Draft GitHub Release. Body is HAND-WRITTEN per the Release
# body rule below — never awk-extracted from CHANGELOG (the
# audiences and styles differ). Draft state lets you proof-read
# before PyPI is committed.
gh release create vX.Y.Z --draft -t "vX.Y.Z" -F /tmp/release-vX.Y.Z.md
# 6. Upload to PyPI — *point of no return*. Twine's auth contract
# is TWINE_USERNAME / TWINE_PASSWORD, not PYPI_TOKEN, so source
# .env to get PYPI_TOKEN into the environment and pass via -u/-p.
# The script redacts pypi-* patterns from any echoed output.
set -a; source .env; set +a
.venv/bin/twine upload -u __token__ -p "$PYPI_TOKEN" dist/*
# 7. Flip the GH Release out of draft and smoke the published
# artefact end-to-end.
gh release edit vX.Y.Z --draft=false
uvx fluid-postgres-mcp --version # expect "fluid-postgres-mcp X.Y.Z"
Notes:
uvx --from build pyproject-build avoids needing python -m build
installed system-wide; the project's hatchling backend is fetched
into an isolated env.[tool.hatch.build.targets.sdist].include. Any new top-level
file you add to the repo is excluded from the sdist by default —
add it to the allowlist if it should ship. Treat the step-3
tar -tzf listing as a release gate, not a curiosity.CHANGELOG.md follows
Keep a Changelog 1.1.0;
the entry is mandatory before the version bump (treat it as a
release gate alongside the tar -tzf listing).MIT — see LICENSE. Forked from crystaldba/postgres-mcp (MIT).
Run in your terminal:
claude mcp add fluid-postgres-mcp -- npx Yes, Fluid Postgres MCP is free — one-click install via Unyly at no cost.
No, Fluid Postgres runs without API keys or environment variables.
Self-hosted: the server runs locally on your machine via the install command above.
Open Fluid Postgres on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Query your database in natural language
by AnthropicA universal database MCP server supporting simultaneous connections to multiple databases. It provides tools for database operations, health analysis, SQL optim
by wenb1n-devThis server enables interaction with PostgreSQL databases through the Model Context Protocol, optimized for the AWS Bedrock AgentCore Runtime. It provides tools
by madhurprashRead-only database access with schema inspection.
by modelcontextprotocolNot sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All data MCPs