Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

Fluid Postgres

БесплатноНе проверен

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

GitHubEmbed

Описание

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.

README

License: MIT Python 3.10+

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.

Install

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.

Verify install

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.

With Claude Code (primary)

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

Other AI agents

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 CLIdocs:

codex mcp add fluid-postgres-mcp \
    --transport stdio \
    --command "uvx fluid-postgres-mcp postgresql://reader:pw@host:5432/db"

Cursor CLIdocs:

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"]
    }
  }
}

Alternative install methods

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.

How to use Fluid Postgres MCP

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.

Configure

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

Pre-connect scripts

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.

AWS SSM examples

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.

Choosing a topology

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.

Environment variables

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.

Required AWS permissions

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:

  • EC2-direct: target = the EC2 instance ARN; document = AWS-StartPortForwardingSession.
  • RDS-via-EC2: target = the EC2 instance ARN; document = 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.

Stdout protocol

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.

EC2-direct

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

RDS-via-EC2

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

Smoke

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.

Reference scripts

Working examples — copy and adapt:

  • scripts/examples/aws-ssm-ec2-tunnel.py — production-shaped Python: credential resolution, optional 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.
  • scripts/examples/aws-ssm-rds-tunnel.py — same lifecycle as above, but uses AWS-StartPortForwardingSessionToRemoteHost so the EC2 acts as a pure SSM forwarder to an RDS endpoint.
  • tests/e2e/fixtures/long_running_passthrough.sh — minimal long-running script: emits DB_URL + READY_TO_CONNECT, then blocks on exec sleep until SIGTERM. Useful as a starting template.
  • tests/e2e/ssm_fixtures.py (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.
  • tests/e2e/ssm_fixtures.py (create_tunnel_script) — run-and-exit SSM variant for the legacy flow (something else owns the tunnel lifecycle).

Authoring notes

  • Block on the resource that defines liveness. A long-running script must exit when its tunnel/session dies; otherwise the MCP has no signal to reconnect. 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).
  • macOS sleep does not accept infinity. Use a large integer (exec sleep 2147483647).
  • Failure surface. Exit-before-READY → mode is run-and-exit and exit code surfaces as success/failure. No 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.
  • Diagnose without shelling onto the box. All script lifecycle events are exposed via the 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.

Develop

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.

Release

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):

  1. Add a ## [X.Y.Z] - YYYY-MM-DD section to CHANGELOG.md per the CHANGELOG.md authoring rule below.
  2. Bump version = "X.Y.Z" in pyproject.toml.
  3. Run 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.
  • The sdist contents are controlled by an explicit allowlist in [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).
  • CHANGELOG.md authoring. Audience: someone deciding whether to upgrade, and someone reconstructing history later (downstream packagers, future-you, anyone tracing a regression to a version). Comprehensive but ruthless with wording. Include all user-facing changes, categorised by impact (Breaking, Security, Added, Changed, Fixed, Deprecated). Each item: one sentence stating the change and its user impact. Add a second sentence only when a reader must take action (migration step, version range affected, workaround) — never to explain rationale or implementation. Drop the how and the why; if rationale matters, it lives in the commit message or PR.
  • GitHub Release body authoring. Audience: someone glancing at the release page or a notification feed, deciding whether this release needs their attention right now. Executive summary. Open with the most consequential change in one sentence; if the release has a coherent theme, name it — if not, don't invent one. Follow with a "Highlights" bullet list of anything a reader of the release page needs to know without opening the CHANGELOG. Breaking, security, deprecations, platform/Python/dependency shifts, and major features will usually qualify; pure bug fixes and internal changes will not. Link to the CHANGELOG for the rest. Hand-written, not awk-extracted.

License

MIT — see LICENSE. Forked from crystaldba/postgres-mcp (MIT).

from github.com/povesma/fluid-postgres-mcp

Установить Fluid Postgres в Claude Desktop, Claude Code, Cursor

Рекомендуется · одна команда, все IDE
unyly install fluid-postgres-mcp

Ставит в Claude Desktop, Claude Code, Cursor и VS Code — сам разбирается с npx, uvx и сборкой из исходников.

Впервые? Поставь CLI: curl -fsSL https://unyly.org/install | sh

Или настроить вручную

Выполни в терминале:

claude mcp add fluid-postgres-mcp -- uvx fluid-postgres-mcp

FAQ

Fluid Postgres MCP бесплатный?

Да, Fluid Postgres MCP бесплатный — установка в пару кликов через Unyly без оплаты.

Нужен ли API-ключ для Fluid Postgres?

Нет, Fluid Postgres работает без API-ключей и переменных окружения.

Fluid Postgres — hosted или self-hosted?

Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.

Как установить Fluid Postgres в Claude Desktop, Claude Code или Cursor?

Открой Fluid Postgres на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.

Похожие MCP

Compare Fluid Postgres with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории data