Skip to content

Connect Claude to PostgreSQL: Remote MCP Server, Tested

We built a small remote MCP server that lets Claude query a PostgreSQL database read-only, with scoped tokens and an audit log. Claude Code answered three business questions in 5 tool calls and 19.1 s for $0.10; every number matched direct SQL.

By the RanThis team, written with AI7 min read

Why trust this: every result, number and screenshot comes from a real run on our own accounts, not from the AI. How we test

Can Claude answer questions straight from your own PostgreSQL database without being able to break it? We built a small remote MCP server for that, put it in a public repo, and asked Claude Code three business questions over a fake coffee-shop database. It took 5 tool calls and 19.1 seconds, cost $0.10 at API prices, and every number in the answer matched the same query run directly in SQL. The database calls themselves took a few milliseconds each.

Built and measured on 27 Oct 2026 on one Linux laptop (server, PostgreSQL 18 and client on the same machine). The data is invented. Code, transcript and raw benchmark numbers: github.com/ranthishq/mcp-postgres-starter (MIT).

At a glance

Key results

  • One prompt, three answers, all correct. Claude Code (Sonnet) was asked who owes the most in overdue orders, what is almost out of stock and revenue by category for 90 days. It made 5 round-trips to the tools in 19.1 s, wrote 1,961 output tokens, and Claude Code reported $0.10 at API prices. The top three debtors, days overdue, the 13 products with 3 or fewer left and every category total matched direct SQL.
  • The database is not the slow part. Over HTTP, 30 calls per tool, the median was 3.6 ms (search_products) to 6.9 ms (find_orders). Nearly all of the 19 seconds was the model thinking and writing.
  • Read tools cannot write, even by mistake. Every read runs in a READ ONLY transaction with a 5-second statement timeout and a row cap. A test tries to write through that path and checks that it fails.
  • Tokens have scopes and are stored only as hashes. A read token that calls the write tool is refused, and both the refusal and the allowed call land in an audit_log table.
  • Only tested with Claude Code. The claude.ai and Claude Desktop connector path needs a public HTTPS address; we did not test it.
Terminal output comparing Claude's answer with direct SQL: every debtor total, the 13 low-stock products and all four category totals show "match", followed by the median latency of each tool
Our re-check, run again after the demo: a fresh database from the repo's fixed seed, plain SQL for the same three questions, compared line by line with Claude's saved answer (no new model call). Every row matches. Below it, the saved latency benchmark. Image drawn from the saved terminal output.
Image: RanThis · Source: github.com

Hands-on test

Setup

  • Server: Python 3.11+, the official MCP Python SDK (mcp 2.3.0, MCPServer), Streamable HTTP at http://127.0.0.1:8000/mcp, PostgreSQL 18 through psycopg 3.
  • Data: a generated store with 60 customers, 22 products and 600 orders; emails on example.com.
  • Client: Claude Code CLI with model Sonnet, connected with a read-only bearer token; the prompt said to use the store tools only. The $0.10 is the API-price figure Claude Code reports; how that relates to plan limits is in our Claude Code cost and limits test.
  • Tools: five read tools (search_products, low_stock, sales_summary, find_orders, overdue_orders) and one write tool (update_order_status) that needs a write token.
  • Checks: 8 tests against a real PostgreSQL database, 3 of them over real HTTP with the official MCP client; a benchmark script for latency.

How the server keeps Claude in its lane

The path is short: Claude sends a tool call over HTTP with a bearer token; one wrapper checks the token's scope, runs the SQL, caps the rows and writes an audit row; then the result goes back.

Claude ──HTTP + Bearer──> tool ──scope check──> SQL ──> PostgreSQL
                            └── every call, allowed or refused ──> audit_log

Three choices matter more than the rest:

  1. The SQL is written in advance. Claude does not send SQL. It picks a tool such as sales_summary and fills in dates and a grouping; the server turns that into a fixed query. Grouping by day, product or category comes from a fixed list, so user text never becomes part of the SQL.
  2. The database enforces read-only, not the prompt. MCP lets a server mark a tool readOnlyHint, and this server does. But the MCP specification itself warns that "clients MUST consider tool annotations to be untrusted unless they come from trusted servers." A hint is a label. A READ ONLY transaction is a rule PostgreSQL applies even if a query is wrong.
  3. Every call is logged, including refusals. audit_log stores who called which tool, with what arguments, how many rows came back, how long it took and the error if any. When someone asks "what did the AI look at?", the answer is a table query.

Write access is narrow on purpose. update_order_status only allows set transitions (pending to paid or cancelled, paid to shipped or refunded, shipped to refunded) and locks the row while it changes it.

Tokens: scoped, hashed, shown once

uv run mcp-postgres-starter-token my-laptop read prints a random token once. The server keeps only its SHA-256 hash, the client name and the scopes in tokens.json (file mode 600), so a copy of that file does not hand out working tokens. Use read,write for a token that may change order status. The server refuses to start without a tokens file.

This is the smallest kind of auth that fits a private deployment. The MCP specification says authorization is optional, and that implementations on an HTTP transport should follow its OAuth 2.1-based authorization spec when they support it. For many users or a company sign-in, the repo's README suggests swapping the static token class for one that checks tokens from your identity provider; the tools only read the scopes, so they do not change.

Connect Claude Code to it

Start the server (the README has a five-minute quick start with the fake data), then give Claude Code the URL and the token. The repo uses a config file:

{"mcpServers": {"store": {"type": "http", "url": "http://127.0.0.1:8000/mcp",
  "headers": {"Authorization": "Bearer <token>"}}}}
claude --mcp-config mcp.json --allowedTools "mcp__store__*"

Claude Code's documentation calls HTTP "the recommended option for connecting to remote MCP servers" and shows the same thing as a command: claude mcp add --transport http <name> <url> with --header "Authorization: Bearer your-token".

What about claude.ai and Claude Desktop?

Custom connectors work differently, and we did not test them. Claude's help center says that "Claude connects to your remote MCP server from Anthropic's cloud infrastructure, rather than from your local device", so the server "must be reachable over the public internet from Anthropic's IP ranges". A server on 127.0.0.1, a VPN or a private office network will not connect.

So for the web or desktop apps you would put this server behind a reverse proxy with HTTPS (the README names Caddy, nginx and Cloudflare Tunnel) and set MCP_PUBLIC_URL. On sign-in, the same help page lists OAuth options and a "Request headers" field for "an API key, bearer token, or other fixed credential instead of OAuth". The repo's README only claims the Claude Code setup was tested, and says web connectors that sign people in with OAuth need a verifier that issues OAuth tokens instead of the static one.

What worked

What worked

  • Fixed tools instead of free SQL. One plain-English prompt was enough: Claude answered all three questions from tool results, with no SQL from us and none from the model.
  • Claude checked its own grouping. It noticed that some customer names repeat across different accounts and grouped debtors by email instead, and said so in the answer.
  • Testing over real HTTP. The three HTTP tests start the server, connect with the official MCP client and real tokens, and check the 401 for no token, a normal read, and the refused write with both rows in audit_log.

Where it fell short

What didn't work

  • The connector path is untested. Everything above ran on one machine with Claude Code. A public HTTPS deployment, claude.ai, Claude Desktop and OAuth sign-in were not tried.
  • The latency numbers are a best case. Server, database and client shared one laptop, with a small database. Over a network and on big tables, expect more; the 5-second timeout and row cap are there for that.
  • Fake data is tidy. Real tables have odd names, nulls and many more rows. Plan to rewrite the tools and their descriptions for your own schema, keeping the audit_log.

Our verdict

If you want Claude to answer questions from a PostgreSQL database, a small remote MCP server with fixed tools is enough, and it can be safe without trusting the model: the database enforces read-only, tokens carry scopes, and every call is logged. In our test it answered three real-looking business questions correctly in 19 seconds for about ten cents. Start with Claude Code on your own machine; plan extra work (public HTTPS, sign-in) before you connect it to claude.ai for a team.

Need this built for your own data? The author of this test takes custom MCP server projects on Upwork (disclosure: that is the author's own profile).

How this was made: the server, the demo session and the benchmark were built and run with Claude Code (AI) on 27 Oct 2026; an AI (Claude) helped write this article; the RanThis editor checked every number against the files in the public repo and checked the quoted facts against the linked official documentation.

Claude

Claude Code Cost and Limits: 7 Days of Real Usage Measured

We added up every Claude Code request one machine made in a week: 2,818 requests that would have cost $149.44 at API prices, 60% of it writing to the prompt cache. Here are the official plan prices and limits next to those numbers, plus seven tests on spending less.

AI-generated illustration

Claude

Claude Skills Guide: 19 Official Skills Measured, 5 Run for Real

We measured all 19 skills in Anthropic's anthropics/skills repository with a script and put the numbers next to our five hands-on Claude Code tests. Descriptions decide whether a skill loads: the only one we tested that never says when to use it stayed unused until we named it.

AI-generated illustration