Skip to main content
AllMCPs
BrowseBestCategoriesStackCompareToolsGuidesBlog Log in Submit MCP

Stay in the loop

Get new MCP servers and top picks in your inbox.

AllMCPs

The open directory for discovering and installing Model Context Protocol servers.

Explore

  • Browse servers
  • Best MCP servers
  • Categories
  • MCP clients
  • Agent prompts
  • Stack Builder
  • Compare servers
  • Tags index
  • Submit a server
  • Pricing

Learn

  • Guides hub
  • What is MCP?
  • Install guide
  • Troubleshooting
  • Security
  • Blog
  • Blog RSS

Tools

  • All tools
  • Config generator
  • Config validator
  • MCP playground
  • OpenAPI β†’ MCP
  • Badge generator

For agents

  • API docs
  • Trust & traffic
  • llms.txt β†— (opens in a new tab)
  • Catalog JSON β†— (opens in a new tab)
  • Remote MCP β†— (opens in a new tab)

Company

  • About
  • Contact
  • X (@AllMCPs) β†— (opens in a new tab)
  • GitHub β†— (opens in a new tab)
  • Terms
  • Privacy
AllMCPs VerifiedAllMCPs VerifiedFeatured on Nick LaunchesFeatured on Nick LaunchesLaunch Llama NewsletterLaunch Llama NewsletterVerified DR - allmcps.comVerified DR - allmcps.comFeatured on SaaSGrowFeatured on SaaSGrowFeatured on Twelve ToolsFeatured on Twelve ToolsFeatured on Saaspa.geFeatured on Saaspa.geFeatured on Findly.toolsFeatured on Findly.toolsFeatured on Startup FameFeatured on Startup FameFeatured on LaunchKiwiFeatured on LaunchKiwiFeatured on ScrollLaunchFeatured on ScrollLaunchFeatured on DailyPingsFeatured on DailyPingsFazier badgeFazier badgeFeatured on NewTool.siteFeatured on NewTool.siteFeatured on saasfame.comFeatured on saasfame.comDR Checker - Domain RatingDR Checker - Domain RatingListed on Turbo0Listed on Turbo0Launched on LaunchBoard - Product Launch PlatformLaunched on LaunchBoard - Product Launch PlatformList on SimilarlabsList on Similarlabshttps://codetrendy.comhttps://codetrendy.comListed on DevTool.ioFeatured on BuildlistFeatured on BuildlistAllMCPs VerifiedAllMCPs VerifiedFeatured on Nick LaunchesFeatured on Nick LaunchesLaunch Llama NewsletterLaunch Llama NewsletterVerified DR - allmcps.comVerified DR - allmcps.comFeatured on SaaSGrowFeatured on SaaSGrowFeatured on Twelve ToolsFeatured on Twelve ToolsFeatured on Saaspa.geFeatured on Saaspa.geFeatured on Findly.toolsFeatured on Findly.toolsFeatured on Startup FameFeatured on Startup FameFeatured on LaunchKiwiFeatured on LaunchKiwiFeatured on ScrollLaunchFeatured on ScrollLaunchFeatured on DailyPingsFeatured on DailyPingsFazier badgeFazier badgeFeatured on NewTool.siteFeatured on NewTool.siteFeatured on saasfame.comFeatured on saasfame.comDR Checker - Domain RatingDR Checker - Domain RatingListed on Turbo0Listed on Turbo0Launched on LaunchBoard - Product Launch PlatformLaunched on LaunchBoard - Product Launch PlatformList on SimilarlabsList on Similarlabshttps://codetrendy.comhttps://codetrendy.comListed on DevTool.ioFeatured on BuildlistFeatured on Buildlist
Β© 2026 Jackalope Digital LLC. All rights reserved.
  1. Home
  2. πŸ—„οΈ Databases
  3. PostgreSQL (hardened, read Only)
P
Health: ActiveRecent health check succeeded.Last checked 8/10/2026, 11:58:17 PM

PostgreSQL (hardened, read Only)

Enrichment pendingWe haven’t run our AI enrichment pass on this listing yet, so the overview, use cases, and FAQ below may be sparse or missing. We work through the catalog over time β€” check back soon.
View Repository

Read-only PostgreSQL over MCP. Writes refused at the parsed SQL, plus a READ ONLY transaction.

Quick Install

Automated & IDE Setup

Copy the AI prompt to install this server into Claude Code, Cursor, or another agent β€” or use 1-click editor setup below.

Add to CursorAdd to VS Code
Not yet automatically verified

We haven't yet run this listing's install command through our automated sandbox check. This isn't a red flag β€” we're steadily working through the catalog.

Manual Client & Custom JSON ConfigExpand JSON β–Ύ

Install Config Generator

Choose your client
claude_desktop_config.json
{
  "mcpServers": {
    "postgresql-hardened-read-only": {
      "command": "npx",
      "args": [
        "-y",
        "ENOENT"
      ]
    }
  }
}

πŸ’‘ Paste into ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows)

Install Tool Schemas (12) Directory Badge Claim listing AlternativesπŸ—„οΈ More in Databases

Capabilities & Tool Schemas (12) ~36 tokensApproximate context cost of this server’s tool schemas (~4 chars/token), before any tool is called. Actual usage depends on your client and model.Self-reported Self-reportedParsed from the repository README, not verified against a live server β€” may be incomplete or out of date.

Inspect callable tools, capabilities, and parameters exposed to AI agents by PostgreSQL (hardened, read Only).

explain_query

Callable MCP tool function

analyze

Callable MCP tool function

database_health

Callable MCP tool function

analyze_indexes

Callable MCP tool function

top_queries

Callable MCP tool function

pg_stat_statements

Callable MCP tool function

Documentation Overview

postgres-mcp-hardened

🚧 Version 0.1.0 β€” released, not yet used by anyone but us

The first release is out: binaries for five platforms, with checksums, Sigstore signatures and build provenance. It is not in the MCP registry yet.

What is actually proven, in the sense that something other than an opinion checks it: the read-only rules (a fuzz harness over 200k mutations, an adversarial corpus of every bypass found so far, PostgreSQL 13–18); the authorisation path end to end; protocol conformance, checked by the official MCP SDK rather than by our own tests; the release path, whose signatures have been verified by hand β€” including that a tampered file and a wrong identity are both rejected; and the published binary itself, downloaded from the release page and run against a live database.

What is not: nobody outside this project has run it against their own data. That is the whole reason this is 0.1.0 and not 1.0. Every adversarial round run against this code so far has found something real, including rounds run after the previous one came back clean β€” the day of the release itself produced four, one of which handed a superuser role to anyone who could create a table. The honest reading is that the next round would find something too.

Known limits, and the places we were wrong, are written down rather than tidied away: THREAT_MODEL.md, docs/AUDIT_2026-07-26.md. If you find something, SECURITY.md says how to say so.

The official Postgres MCP server was deprecated in 2024 and still gets ~440k downloads a month. Its entire defence is one database-level read-only transaction β€” and that alone does not stop every write. This is a maintained Rust replacement with defence in depth.

A drop-in Model Context Protocol server that lets an AI agent query PostgreSQL β€” read-only, enforced at the database level, with real SQL validation, timeouts, cost limits, OAuth 2.1, and an audit trail. Speaks Streamable HTTP and stdio, and negotiates the MCP revision: 2025-11-25 (current), 2025-06-18 (what shipping clients speak today), and 2026-07-28 behind a switch while it is still a draft.

Why

@modelcontextprotocol/server-postgres is deprecated on npm (last publish December 2024) and still sees ~440,000 downloads a month. Credit where it is due: its approach is not naive β€” it wraps each query in BEGIN TRANSACTION READ ONLY and always ROLLBACKs, which is a real defence and one this server now adopts as well.

The problem is that it is the only defence, and it is not complete:

  • A read-only transaction does not block every write. PostgreSQL executes pg_import_system_collations() inside SET TRANSACTION READ ONLY without raising SQLSTATE 25006 β€” it inserted 874 rows into pg_collation in our tests. gin_clean_pending_list() rewrites index structures; pg_backup_start() puts the server into backup mode and survives DISCARD ALL. The rollback saves you from the first case, not from the side effects that live outside transaction semantics.
  • No statement timeout, no cost guard, no row limit β€” one query can run until the server gives up.
  • No authentication, no audit trail, no handling of prompt injection through returned row data.
  • 113 lines, unmaintained since 2024, no test suite.

This server keeps the rollback, adds AST validation in front of it, and adds the operational layers the original never had.

postgres-mcp-hardened vs the archived original

archived server-postgrespostgres-mcp-hardened
Read-only enforcementBEGIN TRANSACTION READ ONLY + ROLLBACK β€” one layer, and PostgreSQL lets some writes through itAST validation (sqlparser) plus the same read-only transaction and rollback, plus a denylist for functions that write despite it
Multi-statement / DROP via CTEreaches the database and is stopped only by the transactionrejected by the parser, before it reaches the database
Statement timeoutnonestatement_timeout + idle_in_transaction_session_timeout enforced
Runaway / expensive queriesrun unboundedEXPLAIN cost guard rejects them before execution
Prompt injection via row dataraw outputwrapped trusted="false" + delimiter escaping
Error messagesleak schema (relation X does not exist)structured, non-leaking, actionable
AuthnoneOAuth 2.1 (RS256 JWT, scope + audience + issuer)
Auditnonetamper-evident hash-chained log
Schema as MCP resourcesβœ…βœ… β€” plus comments, primary and foreign keys
Tests / CInoneunit + end-to-end suites against live PostgreSQL, a deterministic fuzz harness, conformance driven by the official MCP SDK, clippy + cargo audit + container build on every push
Transportstdio / deprecated SSEStreamable HTTP + stdio
Maintained❌ deprecated since 2024βœ…

Install

Three ways in, in the order most people want them.

Through npm β€” shortest, and the one your MCP client config can point at directly. There is no Node runtime involved at run time: the package is a launcher that fetches the native binary for your platform and verifies its checksum before running it.

JSON Config
{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "postgres-mcp-hardened", "--stdio"],
      "env": { "DATABASE_URL": "postgres://readonly_user:PASSWORD@localhost:5432/mydb" }
    }
  }
}

The connection string goes in env, not in args, on purpose: arguments show up in ps output and in shell history on a shared machine, and a database password does not belong there.

A binary from the releases page β€” one static file, nothing to keep up to date, and the option to take if your machine has no Node at all. Every release carries builds for Linux, macOS and Windows on x86-64 and arm64, each with a checksum and a signature; verifying them is the next section.

From source β€” cargo build --release in a clone. Not cargo install: this crate is not on crates.io, and an instruction that fails is worse than one that is missing.

Checking what you downloaded

Every released binary is signed with Sigstore keyless signing β€” there is no private key for us to lose, and the certificate names the workflow, repository and tag that produced the file. Each artefact ships with a .sig and a .pem beside it:

bash
F=postgres-mcp-hardened-x86_64-unknown-linux-gnu.tar.gz
cosign verify-blob "$F" --bundle "$F.bundle" \
  --certificate-identity-regexp '^https://github.com/Eszetael/postgres-mcp-hardened/' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Pin the identity, not just the signature. Without --certificate-identity-regexp and --certificate-oidc-issuer the check answers "somebody signed this", which is not the question. A verified certificate names the workflow, the repository and the tag that built the file β€” you can read it with base64 -d "$F.pem" | openssl x509 -noout -text (cosign writes the certificate base64-encoded, which surprises people who try openssl on it directly).

Older cosign builds predate --bundle; separate .sig and .pem files are published alongside for them, used as --signature "$F.sig" --certificate "$F.pem". Current cosign marks those flags deprecated, so prefer the bundle.

Public releases additionally carry SLSA build provenance, verifiable with gh attestation verify <file> --repo Eszetael/postgres-mcp-hardened.

Use it in Claude Desktop / Cursor (stdio)

config.json
{
  "mcpServers": {
    "postgres": {
      "command": "postgres-mcp-hardened",
      "args": ["--stdio"],
      "env": { "DATABASE_URL": "postgres://readonly_user:YOUR_PASSWORD@localhost:5432/mydb" }
    }
  }
}

Or run it as a remote server (Streamable HTTP)

bash
DATABASE_URL="postgres://readonly_user:YOUR_PASSWORD@host:5432/mydb" \
MCP_ADDR="0.0.0.0:8080" \
postgres-mcp-hardened
# POST /mcp   Β·   GET /health   Β·   GET /ready   Β·   GET /metrics

TLS: connections to PostgreSQL are encrypted whenever the server supports it, and sslmode=require, verify-ca and verify-full are all accepted (the certificate chain and the hostname are always verified, so require behaves like verify-full) β€” so managed Postgres (RDS, Supabase, Neon, Render) works out of the box. Certificates and host names are always verified β€” verified (acceptance: "a certificate naming another host is refused, by name"); for a private CA, point MCP_SSLROOTCERT at the PEM bundle. There is no "trust anything" switch.

Tip: point DATABASE_URL at a least-privilege read-only role. The server enforces read-only itself, but a scoped DB role is defense-in-depth.

Or run it on a container platform (Apify Standby)

The server needs no code changes to run as an Apify Actor in Standby mode. It reads the port the platform assigns from ACTOR_WEB_SERVER_PORT and binds 0.0.0.0 there β€” that port wins over MCP_ADDR, loudly, on stderr, because binding anywhere else means the run is never marked ready and the failure looks like a mysterious timeout. GET / answers the platform's readiness probe (x-apify-container-server-readiness-probe) without touching the database: container readiness is not database readiness, and a probe that waits on a busy pool turns a slow database into a container that never starts.

EndpointMethodPurpose
/mcpPOSTthe MCP endpoint (Streamable HTTP). DELETE ends a session.
/GETreadiness probe; otherwise a signpost naming the real endpoint
/healthGETthe process is alive
/readyGETthe process and a database connection are available
/metricsGETcounters (needs MCP_METRICS_TOKEN)
/.well-known/mcp/server-card.jsonGETwhat a registry reads: revisions, transports, tools

Input is a JSON-RPC request in the POST body β€” initialize, tools/list, tools/call, resources/list, resources/read, server/discover. Output is a JSON-RPC response; from 2025-11-25 a refused statement comes back as a tool execution error (isError: true) with the reason in the content, so the model can rewrite the query. tools/list is the authoritative description of every argument.

Authentication there is the platform's, not ours. Apify checks the caller's token before routing to the container, so the server does not additionally demand MCP_BEARER_TOKEN β€” requiring a second secret would mean an agent that finds this server cannot call it. That exemption is narrow: it needs both APIFY_IS_AT_HOME and ACTOR_WEB_SERVER_PORT, one alone changes nothing, and the server card then reports "type": "apify-platform" rather than claiming a lock we do not hold. Everywhere else the server still refuses to start on a network address with no authentication. Set MCP_BEARER_TOKEN as well if you want a second lock on the same door.

The other start gate is unchanged and matters more here: a role that can write is refused a network listener. Point DATABASE_URL at a read-only role β€” --print-setup-sql writes the statements.

Migrating from the deprecated server

The most-discussed problems reported against @modelcontextprotocol/server-postgres were reproduced against this server; here is how each behaves:

What people reportedHere
Two instances (prod + dev) are indistinguishable, the client picks oneResource URIs carry the database name (postgres:///mydb/public/orders/schema) and MCP_SERVER_LABEL names the instance in the client UI
One database per instance, because the connection string is a command-line argumentMCP_DATABASE_URLS="prod=…;dev=…" serves several databases from one server; every tool takes an optional database, and resources span all of them
no pg_hba.conf entry … SSL offThe error says the server requires TLS and names the fix (?sslmode=require)
Read-only bypassed by injecting COMMIT / ENDRejected β€” the multi-statement gate works on tokens, before the parser, and COMMIT alone is refused as a write
spawn npx ENOENT, Node version problemsA single static binary; no Node, no npx, no node_modules
Hangs indefinitely against RDS with no output or errorBounded: an unreachable host answers in ~8 s with the reason, never silently
self-signed certificate in certificate chainPoint MCP_SSLROOTCERT at the CA bundle; the error names that variable
Connection string only as a command-line argumentDATABASE_URL or the positional argument β€” the original invocation keeps working
INVALID_URL with special characters in the passwordThe error says which characters to percent-encode, and how
Partition children flood the table and resource listsHidden by default; MCP_SHOW_PARTITIONS=1 brings them back
-32601 Method not found, Unexpected end of JSON inputping and resources implemented; multi-line JSON is buffered until complete; batches are refused with a clear error rather than silence
No row limit β€” one query floods the contextAuto-LIMIT, an 8 MB byte cap, and an explicit truncated flag

Testing

Beyond unit tests, the repository carries two harnesses that run in CI on every change:

  • --fuzz β€” a deterministic fuzzer that mutates a corpus of known writes with transformations that do not change SQL meaning (comments, case, dollar-quoting, invisible Unicode, parentheses) and asserts that none of them ever becomes an allowed statement.
  • tests/acceptance.sh β€” an end-to-end suite that starts its own PostgreSQL and checks 43 behaviours: every write-bypass reported against the deprecated server (including the COMMIT/END injection), truthful results, schema introspection, protocol conformance, configuration mistakes failing loudly, audit tamper detection, fair use under load, and multi-database deployments.

Every reported problem, answered

docs/COMMUNITY_ISSUES.md is the complete ledger: every problem reported against the deprecated server and every open issue against the maintained alternatives, each with what happens here β€” including the handful we could not fix in code, said plainly.

What we learned from the alternatives

Every server in this space has an issue tracker, and those trackers are a map of what goes wrong. The ones we deliberately built against:

  • A published image that lags the code. The most-supported open complaint against the leading alternative. Our container is built and pushed from the same tag that produces the binaries, so it cannot drift.
  • A hardcoded query timeout. Also among their most requested settings. MCP_STATEMENT_TIMEOUT is configurable and validated at startup.
  • Unrestricted access by default. Some servers default to read/write and rely on the operator to restrict it. This one has no write path at all.
  • Credentials in the client configuration. MCP_PASSWORD_FILE keeps the password out of it.
  • Tables in a non-default schema silently not found. MCP_SEARCH_PATH fixes the lookup, and the tools take an explicit schema anyway.
  • Deprecated transport. SSE was replaced by Streamable HTTP in the 2025-06-18 specification; we speak the current one.

Troubleshooting

Answers to the questions people actually asked about the deprecated server, so nobody has to open an issue to find them.

spawn npx ENOENT / "which Node version do I need?" β€” none. This is a single static binary. Download it from the releases page and point your client at the file. There is no node_modules, no npx, nothing to keep up to date.

"The server starts but nothing is listening on a port." β€” that is stdio mode, which is correct for Claude Desktop and Cursor: the client talks to the process over its standard input and output, not over a socket. If you want a network endpoint, start it without --stdio; it then prints MCP HTTP listening on http://… and speaks Streamable HTTP.

"Can my client on another machine reach the database?" β€” yes: run the server next to the database in HTTP mode, expose it, and enable OAuth (JWT_PUBKEY_PEM, JWT_AUD, JWT_ISS). The database credentials then never leave the host the server runs on.

"Could not attach to MCP server." β€” the process exited before the handshake. Run the same command in a terminal: a configuration mistake prints its reason and exits with status 2 rather than dying quietly, and a connection problem is reported on the first query with the cause.

self-signed certificate in certificate chain / unable to verify the first certificate β€” your provider uses a private CA (Supabase, GCP and RDS all do). Download their CA bundle and set MCP_SSLROOTCERT to it. The error message names the step for your provider. We do not offer a "trust anything" switch.

Managed providers

This server always verifies the database certificate, including with sslmode=require. That is a deliberate deviation from libpq, where require encrypts without verifying and a machine in the middle can therefore read and rewrite every query and result without anyone noticing. The cost of being strict is that a provider with a private CA needs one extra step; the cost of being lax is that you never find out. If you disagree with the trade-off, verify-full with the bundle below is the same amount of work and leaves no doubt either way.

ProviderWhat to expect
SupabasePrivate CA. Dashboard β†’ Project Settings β†’ Database β†’ SSL configuration β†’ download the certificate, then set MCP_SSLROOTCERT to it. The direct host (db.<ref>.supabase.co) is IPv6-only β€” on an IPv4 network use the Supavisor pooler string (port 6543), which also fits serverless and short-lived connections.
Amazon RDS / AuroraPrivate CA: https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem. IAM authentication works β€” put the generated token in the password field, and remember it expires in 15 minutes.
Google Cloud SQLPrivate CA: Connections β†’ Security β†’ server-ca.pem. Through the Cloud SQL Auth Proxy, connect to the proxy on localhost and TLS is the proxy's business.
Azure Database for PostgreSQLPublic CA β€” nothing to download. Azure rotated its root to DigiCert Global Root G2 during Q1 2026; we ship the Mozilla root store, so the rotation needs nothing from you.
NeonPublic CA (ISRG Root X1, Let's Encrypt) β€” nothing to download. Pooled and direct endpoints both work.
DigitalOceanPrivate CA: download the certificate from the cluster's Overview page.

Not sure which case you are in? Ask the server itself, before configuring anything:

bash
echo | openssl s_client -starttls postgres -connect YOUR_HOST:5432 2>/dev/null \
  | openssl x509 -noout -issuer

A well-known issuer (DigiCert, ISRG, Google Trust Services) means it will just work; anything naming your provider means you need their bundle.

With a connection pooler (Supavisor, PgBouncer) in transaction mode, note that this server sets statement_timeout and idle_in_transaction_session_timeout per session and runs every query in an explicit read-only transaction. Both are compatible with transaction pooling; session-level SET outside a transaction is not, which is why we do neither.

no pg_hba.conf entry … no encryption β€” the server accepts only TLS connections for that host and user. Add ?sslmode=require to the connection string.

INVALID_URL / invalid connection string β€” a password containing @, :, /, # or ? must be percent-encoded (@ β†’ %40, : β†’ %3A, / β†’ %2F, # β†’ %23).

"My table has hundreds of partitions and the list is unusable." β€” partition children are hidden by default; the parent is listed. Set MCP_SHOW_PARTITIONS=1 if you need them.

"I need production and staging at the same time." β€” either run one server per database (they are distinguishable: set MCP_SERVER_LABEL), or configure both in one server with MCP_DATABASE_URLS and pass database in the tool arguments.

Resources

Every table and view is exposed as an MCP resource (postgres:///<schema>/<table>/schema), so a client can browse the schema without issuing a query β€” the same capability the deprecated server offered, plus column comments, primary keys and foreign keys in the payload.

Tools

  • explain_query β€” the execution plan; with analyze it runs the statement and reports real timings and buffer usage, which is safe here because the statement is validated read-only and runs inside a transaction that is always rolled back. The plan comes with a summary: which node spent the time (self time, not inclusive), and where the planner's row estimate was furthest from reality β€” because a bad estimate is usually why the plan is bad.
  • database_health β€” cache hit ratio, connections (this database and the cluster), the longest running statement and the longest abandoned transaction as separate figures, vacuum backlog, invalid indexes, sequences near their ceiling, replication lag, tables that have never been analysed (no planner statistics β€” the usual reason a database looks healthy and runs slowly), and the window the counters cover. Anything the role cannot see is declared rather than returned as a confident zero.
  • analyze_indexes β€” unused indexes, duplicates, and tables scanned sequentially often enough that an index would pay off.
  • top_queries β€” the heaviest statements, from pg_stat_statements.
  • security_posture β€” what this deployment is actually able to do to your database, asked of PostgreSQL rather than assumed: whether the role can write, bypass row-level security or reach server files; whether the transport is authenticated; whether the audit chain is keyed; whether the connection is encrypted. Returns a grade β€” the worst finding, never an average β€” and, for anything wrong, the command that fixes it. The same summary reaches the model through initialize, because under stdio nobody sees stderr and the agent is the only messenger the operator has.
  • query β€” run a read-only SQL query (validated, auto-LIMIT, cost-guarded). The response states what it did: returnedRows, appliedLimit, truncated, plus requestedLimit when a larger request was capped at the 10000-row maximum, offset when paging, and redactedColumns when masking is configured β€” so an agent never has to guess whether it received the whole answer.
  • list_schemas, list_tables, describe_table β€” progressive schema discovery (parameterized, injection-safe). describe_table returns the schema comments (COMMENT ON TABLE/COLUMN), primary keys, foreign keys and defaults, so the agent reads what a column means and what it points at instead of guessing from its name β€” and a missing table is an error, not an empty column list.

Protocol revisions

The server answers initialize with the revision the client asked for when it implements it, and with its newest otherwise. Over HTTP the revision comes from the MCP-Protocol-Version header, per request β€” one client's negotiation cannot change the contract another client is served under.

If a request carries no header, the server does not fall straight back to the oldest contract. It reads the revision this session agreed on at initialize, which is what the transport specification asks for: the default applies only "if the server does not receive an MCP-Protocol-Version header, and has no other way to identify the version β€” for example, by relying on the protocol version negotiated during initialization". A session is that other way, so a client that negotiated 2025-11-25 and then omitted the header keeps the contract it agreed to rather than being silently demoted.

A header we cannot parse is a different matter from a header that is absent, and the specification is explicit about it: "If the server receives a request with an invalid or unsupported MCP-Protocol-Version, it MUST respond with 400 Bad Request." A version we do not implement β€” 2025-03-26 or not-a-date β€” is refused with 400 and the list of revisions we do speak, rather than served under a contract the client never agreed to. Falling back is for silence, not for disagreement.

Only a request with neither a header nor a session falls back, and it falls back to 2025-06-18 β€” the oldest revision this server implements β€” rather than the 2025-03-26 the specification names. That revision is not implemented here, and answering under a contract the server cannot honour would be worse than answering under the oldest one it can.

The difference that matters is where a refusal goes. Under 2025-06-18 "this statement is not read-only" was a JSON-RPC error: the client saw a broken call and the model often never saw the reason. From 2025-11-25 (SEP-1303) it arrives as a tool execution error β€” isError: true with the reason in the content β€” so the model rewrites the query instead of handing the user a failure. What does not change is the audit: the refusal is recorded by the code that refuses, and the acceptance suite asserts both halves together, so friendlier errors can never quietly mean a quieter log.

Mcp-Method and Mcp-Name are held to agreement, not presence. The draft requires them; earlier revisions do not, and demanding them would break every client shipping today. But a gateway that routes or authorises on Mcp-Method while the server executes the body has decided about a different request than the one that runs β€” and that is true whatever revision is in force. So a header that is present must match the body under every revision, while a client that sends none is untouched.

Protocol failures stay protocol failures. A malformed envelope, an unknown method or a missing token is not something a model can fix by rewriting SQL, and a client's error handling expects those where they have always been.

The next revision, before it lands

2026-07-28 is the largest break MCP has had: no initialize, no session header, no ping. That identifier comes from LATEST_PROTOCOL_VERSION in the draft schema, and it is not a release date β€” MCP names a revision for the last date a backwards-incompatible change was made, so it describes the draft's history rather than a schedule. Every request carries its own protocol version in _meta, and a new server/discover replaces the handshake. We implemented it early behind a switch, because a draft moves and a server advertising support for a moving target will be wrong in public. Upstream cut schema/2026-07-28 on 2026-08-03 β€” the released schema differs from the draft we had verified against in four documentation URLs and nothing else β€” so the switch is gone and this is what the server speaks by default. Clients on 2025-11-25 and 2025-06-18 are answered as before.

server/discover answers under every revision, because the specification expects clients to use it as a backwards-compatibility probe β€” which only works if older servers answer it. Ours answers with the revisions we speak and, in _meta, the full security posture. That is deliberate: a client can learn it is talking to a server connected as a superuser before it sends a query, as structured data rather than prose a model has to notice.

Two of the draft's rules are security controls here, not formalities. Mcp-Method and Mcp-Name must agree with the request body, and we refuse the mismatch (-32020) β€” the headers exist so a gateway can route and authorise without parsing the body, and if header and body may disagree, then the thing that authorised and the thing that executes saw two different requests. And a client stating a version we do not implement is told so (-32022) rather than quietly served under a contract it never agreed to.

What the safety costs

Measured, not asserted: tests/bench/, against the pg driver running the same query on the same machine (PostgreSQL 18 in Docker, 50k-row table, 300 sequential requests, rate limit off).

querydriverthis serverdifference
point lookup0.28 ms3.88 ms+3.6 ms
small scan0.64 ms5.82 ms+5.2 ms
aggregate3.96 ms7.67 ms+3.7 ms

About 4 ms per query, and the shape of that number matters more than the size: it is nearly constant. If the AST validation were the cost, it would grow with the query. It does not. The time goes on round trips β€” the session is reset, the timeouts and read-only flag are set, a read-only transaction is opened, the cost guard plans the statement, then the query runs and the transaction is rolled back. Five or six exchanges where the driver has one.

That is a deliberate trade and you can see exactly what it buys. For an agent making tens of calls it is invisible; if you are putting this in front of a latency-critical serving path, you are using the wrong tool, and it is not one.

Under concurrency the interesting number is not throughput but what happens past the limits: at 8 concurrent clients it served 373 requests/second and turned away 192 more with "too many requests in flight", which is the in-flight cap doing its job rather than a queue growing until something falls over.

Would this index help? β€” answered without creating one

The one capability the leading alternative is genuinely known for is index tuning: it can tell you an index would pay off before you build it. It gets there by defaulting to a connection that can create real indexes β€” safe only if you remembered to restrict it.

simulate_index answers the same question from a connection that cannot write anything. hypopg registers a hypothetical index in backend memory: the planner sees it, storage never does, and it is gone when the call returns. You get the plan and cost with and without, and β€” separately β€” whether the planner actually reached for it, because a cost that barely moves and an index the planner ignored are different answers.

The tool takes a table and a list of columns. Not a CREATE INDEX statement. The definition is assembled server-side from identifiers the catalogue confirmed exist, quoted by PostgreSQL itself, so there is no path from a tool argument to arbitrary DDL β€” a column name carrying SQL dies on the catalogue lookup, and there is a test that fires exactly that. The numbers are planner estimates: treat a large improvement as a reason to test the index, not as proof.

Conformance is checked by somebody else's client

Every other test here is our harness talking to our server. If we misread the specification, we misread it the same way in both halves and everything passes. So CI also drives the server with the official MCP SDK β€” the client library the ecosystem uses β€” over stdio and Streamable HTTP: handshake, tool listing and schemas, a read, a refused write arriving as a tool execution error rather than a protocol one, resource listing and reading. A protocol mistake shows up as a client that cannot talk to us. tests/conformance/.

Working on this

bash
git config core.hooksPath .githooks   # once, per clone

.githooks/pre-push runs format, clippy, the unit tests and the documentation-claim checks before anything leaves your machine. It exists because of a specific mistake: a commit went out with a failing clippy lint, and the first anyone knew of it was a failure email. Note that cargo test passes on that code β€” clippy lints are not compiler errors β€” so "it builds locally" is not the same answer as "CI will be green".

It deliberately skips the acceptance suite and the PostgreSQL matrix: those need Docker and about twenty minutes, and a hook people cannot afford to run is a hook people bypass. CI runs everything. git push --no-verify when you want to see something fail in CI on purpose.

Setting up the role

bash
DATABASE_URL=postgres://admin@host/mydb postgres-mcp-hardened \
  --print-setup-sql --role mcp_reader --schemas public --redact ssn,email > setup.sql
# read it, then:
psql -v pw="$(openssl rand -base64 24)" -f setup.sql mydb

Run with a connection string and the table and column lists come from the catalogue; without one you get the same document with placeholders. The difference matters most for redaction: the columns to grant back have to be read from the database, because writing them from memory is how a column meant to stay hidden gets handed back.

The output ends with checks that return no rows when it worked, and a reminder that the server itself will tell you what the role can do the moment you point it at the database.

Limiting what the server can reach

MCP_ALLOW_SCHEMAS and MCP_ALLOW_TABLES restrict which relations a query may touch. Either one turns the allowlist on; schema.* and schema.table both work.

bash
MCP_ALLOW_TABLES='public.customers,public.orders,analytics.*'

The check reads the query plan, not the SQL. That is the whole design: the planner has already applied search_path, resolved every alias, expanded views to base tables, and knows that a CTE named customers is not the table customers β€” so WITH customers AS (SELECT 1) SELECT * FROM customers runs and touches nothing, while WITH x AS (SELECT * FROM salaries) SELECT * FROM x is refused. Reading the statement instead is what lost three rounds of adversarial review.

Two consequences worth knowing before you turn it on:

  • A partition rides on its parent. You allow events; PostgreSQL decides which children to read.
  • A view needs its base tables allowed too, because the plan names those. Allow both, and let the database privileges keep the base table unreachable directly β€” that is the boundary in any case.

pg_catalog and information_schema are outside the surface unless MCP_ALLOW_CATALOG=1: an agent that can still read the catalog can enumerate exactly what the allowlist was meant to hide. The schema tools keep working, because they run fixed queries rather than caller SQL.

That covers three routes to the same facts, because for a while it covered only one. A catalogue view plans to scans over real relations and the plan names them β€” but pg_settings plans to a single Function Scan on pg_show_all_settings, naming no relation at all, and current_setting() is a scalar call that never appears as a scan. Both used to return the server's configuration under an active allowlist. Functions whose name begins with pg_, plus current_setting, inet_server_addr and inet_server_port, are now refused alongside the catalogue relations, and MCP_ALLOW_CATALOG=1 opens all of them together. Ordinary set-returning functions β€” generate_series, jsonb_each, unnest, regexp_split_to_table β€” carry no such prefix and are unaffected. current_user, session_user, current_database and version stay readable on purpose: an agent already knows what it connected to and as whom.

The corpus of things that got through

Every shape that defeated a control during review lives in tests/adversarial/, with the round in which it stopped working. It runs on every build, and β€” because the cases are written against placeholders rather than our fixture β€” you can point it at your own database:

bash
ADV_URL='postgres://…' ADV_TABLE=people ADV_REDACT_COL=ssn ./tests/adversarial/run.sh

A security claim you can only check by reading our source is a claim you have to take on trust, and this project's own history is the argument against that.

Security model

The full statement of what this server guarantees, what it does not, and which control enforces which promise is in THREAT_MODEL.md β€” including the controls that have been defeated in review and are therefore described as depth rather than as boundaries.

  • Encrypted transport: TLS to PostgreSQL via rustls (no OpenSSL in the image), certificate verification always on, private CAs via MCP_SSLROOTCERT.

  • Read-only, two ways: every statement is parsed with sqlparser and rejected unless it's a SELECT/WITH/EXPLAIN/SHOW; the DB session is additionally set default_transaction_read_only = on.

  • Anti-DoS: enforced statement_timeout, auto-injected LIMIT, and an EXPLAIN-based cost guard that rejects expensive plans before they run.

  • Sensitive columns β€” defence in depth, and honest about it: MCP_REDACT_COLUMNS masks values at every depth and refuses to run a query that references those columns, including the ways round it that an adversarial panel actually found β€” renaming (SELECT password AS pw), wrapping (md5(password)), serialising the whole row (row_to_json(t), t::text, json_agg(t)), and naming the column as a string rather than an identifier (to_jsonb(t) ->> 'password', #>> '{password}', $.password), whole-row wildcards (ROW(t.*)::text) and positional renaming ((SELECT * FROM staff) AS x(c1, …, c9)). It is still name-based filtering, and name-based filtering cannot be a boundary against the whole SQL language β€” three adversarial rounds each got past it through a shape nobody had listed.

    So the server stops asserting and asks the database: at startup it reports every table where the connected role can still read a redacted column, with the exact statements that fix it, and MCP_REDACT_REQUIRE_REVOKE=1 turns that report into a refusal to run. Note the fix is a table-level REVOKE followed by a GRANT of the columns that stay β€” a bare REVOKE SELECT (password) ON staff is silently a no-op while the role holds SELECT on the whole table. With column-level grants PostgreSQL then refuses SELECT * on that table, so callers name columns instead; describe_table lists them and marks the redacted one.

  • Prompt-injection aware: row data is returned inside a trusted="false" provenance block with delimiters escaped, so a malicious cell can't hijack the agent.

  • It generates the role you should be running as: --print-setup-sql writes the DDL for a role that inherits nothing, bypasses nothing, creates nothing, reads only the relations you name, and β€” where you have named sensitive columns β€” has them revoked in the order that actually works. It prints; it never executes. Applying this needs administrative rights, and a tool whose whole identity is "read-only" has no business holding an administrator's password.

  • It will not expose a role that can write: when the listen address is reachable from the network, the server asks PostgreSQL what the connected role is actually allowed to do β€” superuser, BYPASSRLS, membership of pg_write_all_data and friends, and write privileges on a bounded sample of tables β€” and refuses to start if the answer is more than "reader", naming each reason and pointing at --print-setup-sql. It refuses an unauthenticated network listener for the same reason. Loopback and stdio are left alone: there the caller is the operator. The overrides (MCP_ALLOW_EXCESSIVE_ROLE, MCP_ALLOW_ANONYMOUS_NETWORK) take the literal value i-accept-the-risk so they cannot be switched on by a typo, and they are recorded in the audit log. This server enforces read-only itself, but that enforcement is code, and code has been wrong before; a role that cannot write is the part no bug of ours can undo.

  • A browser cannot reach it: a request carrying an Origin is refused with 403 unless the operator listed that origin, and on a loopback listener a Host that is not localhost is refused too β€” the shape a DNS-rebinding attack takes when it aims at a database server on your laptop.

  • The audit knows the configuration: the chain opens with a startup record naming the version, the transport and every setting in force, with connection passwords stripped and secrets reduced to fingerprints, plus a config_fp an operator can pin across restarts. A log that says what happened but not under which settings cannot answer the first question an incident asks.

  • The audit notices being shortened: a hash chain proves entries were not altered, but a log with its tail cut off is internally consistent β€” recomputing it finds nothing wrong. Alongside MCP_AUDIT_LOG the server therefore keeps <log>.hwm, a one-line record of the last sequence number and hash it wrote, updated only after the entry is durably appended. On start the two are compared, and a disagreement is reported: entries missing from the end, a rewritten last entry, or a log that has gone away entirely. This is not proof of tampering β€” an unclean shutdown looks the same β€” but a tamper-evident trail owes you the question, not the verdict. Keep the sidecar with the log when you archive or move it; deleting it only loses the truncation check, never an entry. The offline verifier is unchanged and still needs an external anchor: --verify-audit <file> --expect-last <hash>.

  • A wrong setting is fatal, not merely wrong: an unparsable listen address, an audit file that cannot be written, sslmode=disable to a database on another machine, a metrics token that is also the database credential, a boolean spelt yes β€” each used to be accepted and quietly do something other than what was meant. Startup now stops and names the setting.

  • A misspelt setting is fatal: MCP_REDACT_COLUMN (singular) used to start the server with redaction quietly switched off. Unknown MCP_* variables now stop startup and name the intended spelling; MCP_X_* is reserved for the operator's own use.

  • No schema leaks: database errors are mapped to structured, actionable messages that never echo table/column names.

  • OAuth 2.1: optional RS256 bearer-token validation (signature, exp, aud, iss) with scope enforcement; disabled when unconfigured for local/self-host use.

  • Audit: every tool decision is logged as a tamper-evident, hash-chained JSON line (no raw SQL).

  • Supply chain: dependency licences, sources and advisories enforced in CI (cargo deny, cargo audit); a CycloneDX SBOM is attached to every release.

  • Runtime: ships as a distroless, non-root container (~34 MB, built and smoke-tested in CI).

Footprint

Measured on an ordinary VPS against a 16k-row sample database, so you can check the "written in Rust" claim rather than take it:

Resident memory, idle5.2 MB
Resident memory, after a benchmark run9.2 MB
Median request latency~8 ms β€” including the curl process the measurement spawns, so the server's own share is lower
Start to first validated statement7 ms
Binary11 MB, static, no runtime to install
Container image~34 MB distroless, non-root

A twelve-minute soak of mixed traffic (reads, refusals, errors, aborted requests, session churn) left resident memory flat and file descriptors unchanged.

Configuration

EnvPurpose
DATABASE_URLPostgreSQL connection string (use a read-only role)
MCP_ADDRHTTP listen address (default 127.0.0.1:8080)
MCP_MAX_COSTreject queries whose EXPLAIN cost exceeds this (default 1,000,000)
JWT_PUBKEY_PEM, JWT_AUD, JWT_ISSenable OAuth 2.1 token validation (omit to disable auth); the key may be the PEM text or a path to a PEM file
MCP_AUDIT_LOGpath to the append-only audit log (hash-chained); verify with --verify-audit <file> [--expect-last <hash>]. The server also writes <log>.hwm beside it β€” the last sequence number and hash, used at startup to notice a shortened log
MCP_AUDIT_HMAC_KEY / MCP_AUDIT_HMAC_KEY_FILEkey that turns the audit chain into HMAC-SHA256 β€” keep it off the host so the log cannot be rewritten (a trailing newline in the file is ignored)
MCP_AUDIT_HMAC_KEYS_OLDcomma-separated previous keys, so a log that survived a key rotation still verifies
MCP_REDACT_COLUMNScolumns to keep out of results, e.g. password, ssn, card_number β€” masked at any depth and refused if referenced. Defence in depth, not a boundary: pair it with REVOKE SELECT (col)
MCP_BEARER_TOKENshared token required on every request, for deployments without an identity provider. Ignored when OAuth is configured β€” accepting it as an alternative would give its holder full scope and leave the audit with no identity
MCP_STATEMENT_TIMEOUTquery time limit (PostgreSQL interval, default 30s)
MCP_SEARCH_PATHschemas to search when a table name is unqualified, e.g. analytics, public
MCP_PASSWORD_FILEread the database password from a file instead of putting it in the connection string
MCP_DATABASE_URLSseveral databases from one server: prod=postgres://…;dev=postgres://… (tools then take a database argument)
MCP_SERVER_LABELname shown in the client UI, e.g. production β†’ postgres-mcp-hardened (production)
MCP_SHOW_PARTITIONS1 to list partition children as well (hidden by default)
MCP_ALLOW_FUNCTIONScomma-separated catalog functions to permit that we do not know to be read-only
MCP_SSLROOTCERTpath to a PEM CA bundle for TLS to PostgreSQL (e.g. the AWS RDS bundle); system and Mozilla roots are trusted by default
MCP_MAX_INFLIGHT_PER_CLIENTmax concurrent requests from one client (default 4; 0 disables)
MCP_RATE_RPMper-client request rate limit (default 120/min; 0 disables)
MCP_RATE_RPM_STDIOthe same limit for stdio (default 600/min): an agent exploring a schema legitimately makes dozens of calls a minute, but a runaway loop against a production database is still what a DBA fears most
MCP_CLIENT_IDa name for this client in the audit log over stdio, e.g. claude-desktop@ada-laptop; without it the identity falls back to the operating system's user and process
MCP_RATE_BURSTburst allowance for that limit (default MCP_RATE_RPM / 4, min 5)
MCP_METRICS_TOKENtoken required on /metrics. Without it, /metrics follows whatever the server itself requires: open when the server has no authentication, the bearer token when one is set, and closed when OAuth is configured (a JWT is the wrong shape for a scraper β€” set this instead)
MCP_REDACT_REQUIRE_REVOKE1 to refuse to serve while the database still lets the role read a redacted column β€” turns the setting above from advisory into a guarantee
MCP_STRUCTURED_CONTENT1 to also return MCP structuredContent; off by default because a client that ignores it pays for every result twice. The provenance marker travels inside the object, but the delimiter escaping that protects the text block does not apply β€” a client that pastes structured output straight into a prompt loses that layer
MCP_RESERVED_AUTH_SLOTSdatabase slots kept for authenticated traffic so an anonymous flood cannot take the pool (default: a quarter)
MCP_PUBLIC_URLthis server's public base URL, used in the OAuth discovery metadata
MCP_AUTH_SERVERSauthorization server URLs advertised in that metadata
MCP_PROTOCOL_PREVIEWRetired. It gated 2026-07-28 while that revision was a draft; upstream released it on 2026-08-03 and the server now speaks it by default. The name stays recognised so an existing config line is not reported as a misspelling, and startup says once that it no longer does anything
MCP_ALLOW_SCHEMASschemas a query may reach, e.g. public,analytics; setting either this or the next turns the allowlist on
MCP_ALLOW_TABLESrelations a query may reach, e.g. public.orders,analytics.*
MCP_ALLOW_CATALOG1 to keep pg_catalog reachable while an allowlist is active
MCP_ALLOW_PLAINTEXT_DBset to i-accept-the-risk to allow sslmode=disable to a database that is not on this machine
MCP_ALLOW_EXCESSIVE_ROLEset to i-accept-the-risk to serve a network listener with a role that can write
MCP_ALLOW_ANONYMOUS_NETWORKset to i-accept-the-risk to serve a network listener with no authentication
MCP_ALLOWED_ORIGINSbrowser origins permitted to call this server, e.g. https://my-client.example. Empty means no browser page may reach it: a page the user is merely visiting can make their browser POST to localhost, which is DNS rebinding's whole trick
MCP_ALLOWED_HOSTSextra Host values accepted when listening on loopback (localhost and 127.0.0.1 always are)
MCP_FUZZ_VERBOSEdevelopment only: makes --fuzz print each mutation it tried
MCP_TRUST_PROXYset to 1 only behind a reverse proxy β€” then the rate limiter keys on X-Forwarded-For instead of the peer address

License

MIT β€” see LICENSE.

Related MCP Servers

View all in Databases View all alternatives
  • Postgres Mcp logoPostgres Mcp

    PostgreSQL MCP server with 14 tools for querying, schema exploration, and table analysis. Features security-first design with SQL injection prevention and read-only by default.

    πŸ—„οΈ Databases3 views
    Compare vs Postgres Mcp β†’
  • Mcp Snowflake Server logoMcp Snowflake Server

    Snowflake integration implementing read and (optional) write operations as well as insight tracking

    πŸ—„οΈ Databases2 views
    Compare vs Mcp Snowflake Server β†’
  • M
    MySQL Ops MCP

    MySQL MCP server: browse schema, query, CRUD. Delete/drop gated by user approval.

    πŸ—„οΈ Databases0 views
    Compare vs MySQL Ops MCP β†’
  • AllMCPs Server logoAllMCPs Server
    β˜… Featured

    The official MCP server for AllMCPs.com - submit and manage tools directly from your AI. The open directory for MCP servers. Connect Claude, Cursor, Windsurf, and AI agents to databases, tools, files, and APIs. Explore 3,181+ servers. AllMCPs is the premier, open directory for discovering, evaluating, and installing Model Context Protocol (MCP) servers to equip AI agents and LLMs with real-world superpowers.

    πŸ—„οΈ Databases7 views
    Compare vs AllMCPs Server β†’

Frequently Asked Questions about PostgreSQL (hardened, read Only)

Add the following block to your claude_desktop_config.json under mcpServers: "mcpServers": { "postgresql-hardened-read-only": { "command": "npx", "args": ["-y", "PostgreSQL (hardened, read-only)"] } }

AllMCPs Directory Badge

Full Badge Customizer

Showcase your server listing on GitHub or your project documentation. Embed this dynamic SVG badge to highlight official listing status and live engagement.

Badge Style:
Live Dynamic SVG PreviewPostgreSQL (hardened, read Only) AllMCPs Directory Badge
Markdown (GitHub README)
[![AllMCPs](https://allmcps.com/api/badge/postgresql-hardened-read-only?style=directory)](https://allmcps.com/mcp/postgresql-hardened-read-only)
HTML Embed
<a href="https://allmcps.com/mcp/postgresql-hardened-read-only"><img src="https://allmcps.com/api/badge/postgresql-hardened-read-only?style=directory" alt="PostgreSQL (hardened, read Only) on AllMCPs" /></a>

Technical Specs & Signals

CategoryπŸ—„οΈDatabases
More technical detailsExpand β–Ύ
TransportSTDIO
RuntimeNode.js
Views0
Unique ViewsTotal visits recorded for this listing page on AllMCPs.
Installs0
Installs & Copy ActionsTotal times users copied install commands or configuration snippets for this server.
GitHub stars0
GitHub Star CountTotal stargazers on GitHub representing community popularity (0 stars).
Last commit1d ago
Last Repository CommitThe most recent commit or push recorded for this server's GitHub repository.Last commit on Aug 9, 2026
52Quality signal: Good Β· 52/100How this signal is calculated β–Ύ
Server availabilityNot measured

Not scored for repo-hosted servers β€” we can't reach the running server, only its GitHub page. Hosted MCP endpoints are health-checked live.

Verified ownership10/20
Documentation & tools25/30
Adoption & activity4/15
Community engagement0/10

A guidance signal from public completeness & health data β€” not a user rating. New listings start lower and rise as they add docs, get verified, and grow adoption. Signals we can't observe for a listing are skipped, not counted against it.

β˜… FeaturedAllMCPs Server logo

AllMCPs Server

The official MCP server for AllMCPs.com - submit and manage tools directly from your AI. The open directory for MCP servers. Connect Claude, Cursor, Windsurf, and AI agents to databases, tools, files, and APIs. Explore 3,181+ servers. AllMCPs is the premier, open directory for discovering, evaluating, and installing Model Context Protocol (MCP) servers to equip AI agents and LLMs with real-world superpowers.

Explore Server β†’

Own this project?

This directory is pre-filled from public sources. Claim via GitHub README, site badge, or DNS TXT to get the verified badge and attach your website.

Free dofollow backlink: after claiming, verify your product site and place a dofollow AllMCPs badge β€” we recheck it stays live.

Claim & get free dofollow

Share & Embed

Add our SVG badge (dark/light directory styles) or embeddable widget to your site.

Explore more

More in πŸ—„οΈ Databases β†’Best MCP servers for Databases β†’Alternatives to PostgreSQL (hardened, read Only) β†’Install in Claude DesktopInstall in CursorInstall in VS Code