Query PostgreSQL databases in plain English — LLM-generated, safety-validated SQL.
PlainQuery connects PostgreSQL databases to Claude, Cursor, or VS Code — and answers with real, structured data instead of guesses. It enables querying PostgreSQL databases in plain English with safety-validated SQL, backed by MCP-compliant tooling.
🛠️ Key Features
Natural language querying of PostgreSQL databases
Safety-validated SQL generation
LLM-backed data retrieval with structured results
Compatibility with Claude, Cursor, and VS Code
Open-source Python implementation with CI checks
🚀 Use Cases
Ask complex database questions in plain English
Generate executable SQL from natural language prompts
Retrieve precise, structured data without guesswork
Integrate database insights into IDE or AI assistants
⚡ Developer Benefits
Clear MCP-aligned server interface for cataloging
Reusable components for building LLM-assisted data tools
CI-backed quality and test coverage
Lightweight Python-based implementation
⚠️ Limitations
Readme excerpt indicates core focus on PostgreSQL integration; behavior depends on underlying SQL safety rules
Details about authentication, deployment, and performance tuning are not provided in the source data
Ask your database questions in plain English. PlainQuery connects PostgreSQL databases to Claude, Cursor, or VS Code — and answers with real, structured data instead of guesses.
PlainQuery demo — querying a database in plain English from an MCP client
What it does
You connect a database once. Then, from any MCP client, you ask questions like:
"How many orders did we ship in March?""Which 5 customers spent the most last year?""What's the average order value by month?"
PlainQuery introspects your schema, generates SQL with an LLM, validates it for safety, runs it, and returns structured JSON. If a query fails, it reads the error and retries automatically.
Why PlainQuery
Natural language, real answers — no SQL required; results come from your actual data, not a hallucination.
Read-only and safe by design — every query is checked before it runs: writes (INSERT/UPDATE/DELETE/DDL) are blocked, dangerous functions and patterns are scanned out, and a LIMIT is injected automatically.
Self-correcting — when a generated query errors, the agent feeds the error back to the LLM and retries (up to a configurable limit).
Schema-aware — automatically introspects and caches your schema, so questions map to the right tables and columns.
Works with any MCP client — Claude Desktop, Cursor, VS Code Copilot, or anything that speaks MCP.
Secure multi-tenant hosting — database URLs and LLM keys are Fernet-encrypted at rest; user-supplied connection strings are SSRF-guarded; every request is tenant-scoped.
Hosted or self-hosted — use the managed service, or run the whole stack yourself with Docker.
Bring your own LLM — Anthropic Claude or Groq.
How it works
code
Your question
→ Schema introspection (reads tables/columns, cached)
→ SQL generation (schema + question → LLM → SQL)
→ Safety validation (blocks writes, scans dangerous patterns, injects LIMIT)
→ Execution (runs read-only, with a timeout)
→ Self-correction retry (on error: feed it back to the LLM, fix, re-run)
→ Structured JSON result
Architecture
Quick start
Option 1 — Use the hosted service (no install)
Sign up at https://plainquery.in and verify your email.
Connect your PostgreSQL database and create an API key.
The backend can also generate ready-to-paste config for VS Code, Cursor, and generic HTTP clients — call POST /api/v1/account/setup-payloads (see Setup payloads). MCP clients can authenticate with an OAuth 2.1 bearer token or an API key, depending on MCP_AUTH_MODE.
Self-hosting & local development
Prerequisites
Python 3.12+
uv
Node.js 20+ and pnpm for the frontend
At least one LLM API key (ANTHROPIC_API_KEY or GROQ_API_KEY)
Backend
bash
uv synccp .env.example .env
Edit .env with at least:
CREDENTIAL_ENCRYPTION_KEYS
REGISTRATION_OPEN=true
one LLM provider key plus LLM_PROVIDER
Run the backend:
bash
uv run uvicorn src.app:app --reload --host 0.0.0.0 --port 8000
The backend mounts:
REST API at http://localhost:8000/api
MCP endpoint at http://localhost:8000/mcp
Frontend
bash
cd frontend
pnpm install
pnpm dev
Open http://localhost:3000, sign up, complete setup, link your OAuth identity via account settings, then connect an MCP client at http://localhost:8000/mcp. The client will complete the OAuth flow automatically, or you can use an API key in api_key_only / hybrid mode.
Runtime model
Backend API: FastAPI/Starlette at src.app:app
Frontend: Next.js app in frontend/
Auth: passwordless email verification and login links
MCP auth: OAuth 2.1 bearer tokens (oauth_only), API keys (api_key_only), or both (hybrid) — set via MCP_AUTH_MODE
Setup payloads: POST /api/v1/account/setup-payloads
Billing: Stripe Checkout, Customer Portal, and webhook-confirmed Free/Pro entitlements
The product model is single-account and user-scoped:
signup → verify email → connect database → link OAuth identity → use /mcp → upgrade with Stripe
Security
User-supplied database URLs are validated against SSRF, path traversal, private IPs, and DNS rebinding before any connection attempt.
Database URLs and LLM keys are Fernet-encrypted at rest, with support for key rotation.
Generated SQL is validated before execution: single-statement guard, forbidden-function scan, dangerous-pattern scan, write/DDL block, table-existence check, and automatic LIMIT injection.
Per-request context scoping prevents cross-tenant data leaks.
Per-user rate limits and fallback-LLM quotas limit cost abuse.
All account routes use session token auth (x-session-token: <session-token> or Authorization: Bearer <session-token>):
GET /api/v1/account
GET /api/v1/account/status
PUT /api/v1/account/database
GET /api/v1/account/api-keys
POST /api/v1/account/api-keys
DELETE /api/v1/account/api-keys/{id}
POST /api/v1/account/api-keys/{id}/rotate
POST /api/v1/account/setup-payloads
GET /api/v1/account/dashboard
GET /api/v1/account/usage/recent
Billing
GET /api/v1/account/billing
POST /api/v1/account/billing/checkout-session
POST /api/v1/account/billing/portal-session
POST /api/v1/billing/webhook
Stripe webhooks are the source of truth for plan transitions. Checkout or
subscription activation moves a user to plan_code=pro; canceled, unpaid, or
past-due states restrict paid entitlements without deleting database setup.
OAuth MCP account linking (session-authenticated)
GET /api/v1/account/mcp-oauth/status
POST /api/v1/account/mcp-oauth/start
GET /api/v1/account/mcp-oauth/callback
DELETE /api/v1/account/mcp-oauth/link
MCP
POST /mcp
Auth: OAuth 2.1 bearer token, API key, or both — controlled by MCP_AUTH_MODE
Setup payloads
POST /api/v1/account/setup-payloads returns client configuration material for VS Code, Cursor, generic HTTP MCP clients, and the current ChatGPT placeholder.
The backend never stores raw API keys after creation. A raw key is only embedded in setup payloads when you explicitly send it in the request.
Tests
bash
uv run pytest tests/ -m "not integration"
uv run pytest tests/ -m integration
uv run ruff check .
uv run mypy src --ignore-missing-imports
Deployment smoke test
After each hosted deploy, verify the public MCP OAuth discovery surface:
bash
uv run python scripts/smoke_mcp_deployment.py \
--mcp-url https://mcp.example.com/mcp \
--issuer-url https://YOUR_DOMAIN.auth0.com/
The smoke test checks the unauthenticated /mcp challenge, protected resource
metadata, ChatGPT/Claude well-known discovery routes, and issuer metadata. To
also verify authenticated MCP initialize, tools/list, and resources/list,
pass a linked user's access token with --access-token or
MCP_SMOKE_ACCESS_TOKEN.
Docker
Run the hosted HTTP stack:
bash
docker compose up --build
The container image serves only the hosted HTTP runtime. The MCP endpoint remains http://localhost:8000/mcp.