Skip to main content
Glama
venkatacloud01

local-mcp-postgres

πŸš€ 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 it

Running 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.py

Then 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.server works from a terminal but not from Claude Desktop's launched subprocess β€” its cwd handling doesn't reliably put the project root on Python's module search path for -m resolution. Fix: point claude_desktop_config.json directly at the script file (args: ["/abs/path/src/server.py"]) instead of using -m.

  • tool.inputSchema vs tool.input_schema β€” the wire-protocol field name and the installed mcp SDK's Python attribute name don't match; the SDK uses snake_case. Worth checking installed SDK attribute names directly rather than assuming they mirror the protocol spec.

  • init.sql only runs against Postgres on a fresh Docker volume. If you've already got a pgdata volume from a prior run, editing init.sql and re-running docker compose up silently does nothing β€” you need docker compose down -v first 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 /chat request (and each run_tool_call_loop call) 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 console

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables SQL queries against PostgreSQL databases through MCP-compatible clients and includes a natural language agent for forming SQL queries from natural language.
    230
    PostgreSQL
  • A
    license
    Not graded
    quality
    D
    maintenance
    Ask your database questions in plain English. Connects PostgreSQL databases to MCP clients and answers with real, structured data instead of guesses.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables natural language querying of a customer and orders database through read-only MCP tools for finding customers, listing orders, and generating revenue summaries.
    -