local-mcp-postgres
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@local-mcp-postgresWhat's the current inventory of Widget B?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
π Operation LOCAL-MCP
A fully local Postgres-backed MCP (Model Context Protocol) server, connected two ways: first to Claude Desktop (which speaks MCP natively), then to a fully local Ollama model via a hand-built bridge β since Ollama has no native MCP client. Both paths sit behind a FastAPI + Streamlit chat console.
Built as a diagnostic exercise as much as a build exercise: the core question was "where exactly does local-model tool-calling reliability break down?", and this project isolates that question with a small, controlled two-tool schema.
What it does
Ask questions in plain English about a local inventory/orders database:
"What's in the inventory?"
"What's John's total order amount?"
"How many Widget B do we have?"
The model decides which tool to call, the tool runs a safe parameterized query against Postgres, and the result gets turned back into a natural-language answer.
Related MCP server: Cloudflare Remote PostgreSQL MCP Server
Architecture
βββββββββββββββ stdio (MCP) ββββββββββββββββββββ
βClaude DesktopββββββββββββββββββββββΊβ β
βββββββββββββββ β server.py ββββΊ Postgres
β (FastMCP) β (Docker)
βββββββββββββββ hand-built bridge β β
β Ollama ββββββββββββββββββββββΊβ β
β (qwen2.5:7b) β (bridge.py) ββββββββββββββββββββ
ββββββββ¬ββββββββ
β
βΌ
βββββββββββββββ HTTP βββββββββββββββ
β FastAPI βββββββββββΊβ Streamlit β
β (main.py) β β (app.py) β
βββββββββββββββ βββββββββββββββserver.py is the single source of truth for the tools β both Claude Desktop and
the Ollama bridge talk to the exact same FastMCP server. This was deliberate: it
turns "does Claude Desktop succeed where Ollama fails?" into a clean A/B test,
since the only variable that changes is the model, not the tool definitions.
Key finding
The starting hypothesis (from prior local-LLM tooling work) was that 7Bβ12B
models are broadly unreliable at emitting well-formed tool calls. That held for
dense, high-overhead schemas (e.g. Claude Code's tool surface), but did not
hold here: qwen2.5:7b, given this project's small two-tool schema, correctly
selected the right tool on every test β including inferring a sensible default
parameter (min_quantity: 1) that wasn't stated in the prompt β and never once
emitted malformed tool-call JSON.
Takeaway: the earlier reliability problem looks like it's driven more by
schema size / context overhead than by a hard local-model capability
ceiling. Worth revisiting the claude-code-router / hosted-API tradeoff with
that in mind β schema simplification may solve more of the problem than
switching providers does.
Setup
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
docker compose up -d # Postgres, seeded via init.sql
ollama pull qwen2.5:7b # or llama3.1:8b β confirm `tools` under
ollama show qwen2.5:7b # Capabilities before relying on itRunning it
Path 1 β Claude Desktop (MCP native):
Add an entry to claude_desktop_config.json pointing at src/server.py using
your venv's Python (see repo history / commit log for the exact config that
worked after a couple of path-resolution false starts β see Gotchas below).
Path 2 β Local Ollama console:
# terminal 1
uvicorn src.main:app --reload
# terminal 2
streamlit run src/app.pyThen open the Streamlit URL and ask questions in the chat box.
Gotchas hit along the way (kept for the next person, including future me)
python -m src.serverworks from a terminal but not from Claude Desktop's launched subprocess β itscwdhandling doesn't reliably put the project root on Python's module search path for-mresolution. Fix: pointclaude_desktop_config.jsondirectly at the script file (args: ["/abs/path/src/server.py"]) instead of using-m.tool.inputSchemavstool.input_schemaβ the wire-protocol field name and the installedmcpSDK's Python attribute name don't match; the SDK usessnake_case. Worth checking installed SDK attribute names directly rather than assuming they mirror the protocol spec.init.sqlonly runs against Postgres on a fresh Docker volume. If you've already got apgdatavolume from a prior run, editinginit.sqland re-runningdocker compose upsilently does nothing β you needdocker compose down -vfirst to force re-seeding.Model function-call arguments arrive as either a dict or a JSON string depending on the model/quantization β
bridge.py's loop handles both.
Known limitations
No multi-turn memory. Each
/chatrequest (and eachrun_tool_call_loopcall) starts a fresh conversation with no history from prior turns. The Streamlit UI displays prior turns for readability, but doesn't send them back to the backend β a deliberate scope cut for this iteration, not an oversight.Each request respawns the MCP server subprocess rather than keeping one long-lived session β simpler and correct for this scale, but adds latency you'd want to remove for anything beyond a local demo.
Read-only by design: both tools run fixed, parameterized
SELECTs. No write/update tools exist, intentionally, to keep the safety surface small.
Stack
Python 3.12 Β· FastMCP Β· MCP SDK Β· Ollama (qwen2.5:7b) Β· PostgreSQL (Docker) Β·
FastAPI Β· Streamlit Β· psycopg2
Repo layout
local-mcp-postgres/
βββ README.md
βββ MISSION_PLAN.md # phase-by-phase build log / checklist
βββ docker-compose.yml
βββ init.sql
βββ requirements.txt
βββ src/
βββ server.py # FastMCP server: fetch_inventory, fetch_order_total
βββ bridge.py # Ollama-MCP bridge + tool-call loop
βββ main.py # FastAPI backend
βββ app.py # Streamlit consoleThis server cannot be deployed
Maintenance
Related MCP Connectors
Analytical memory for AI agents: a real Postgres queried in plain English over MCP. One command.
- mcpOAuthcom.gibsonai
GibsonAI MCP server: manage your databases with natural language
Query your warehouse or a CSV with Claude/ChatGPT over MCP, governed by table-level ACL + audit.
Query your org's data in natural language β read-only MCP access to SQL, NoSQL, files & warehouses.
Related MCP Servers
AlicenseNot gradedqualityAmaintenanceEnables SQL queries against PostgreSQL databases through MCP-compatible clients and includes a natural language agent for forming SQL queries from natural language.230PostgreSQL- AlicenseNot gradedqualityDmaintenanceEnables natural language interaction with PostgreSQL databases through MCP tools, with GitHub OAuth authentication and role-based access control.MIT
- AlicenseNot gradedqualityDmaintenanceAsk your database questions in plain English. Connects PostgreSQL databases to MCP clients and answers with real, structured data instead of guesses.MIT
- FlicenseNot gradedqualityCmaintenanceEnables natural language querying of a customer and orders database through read-only MCP tools for finding customers, listing orders, and generating revenue summaries.-