{
  "markdown": "# PlainQuery\n\n**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.\n\n[![MCP Registry](https://img.shields.io/badge/MCP%20Registry-io.github.Jarvis--27%2Fmcp--db--agent-3b82f6)](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.Jarvis-27/mcp-db-agent)\n[![CI](https://github.com/Jarvis-27/mcp-db-agent/actions/workflows/ci.yml/badge.svg)](https://github.com/Jarvis-27/mcp-db-agent/actions/workflows/ci.yml)\n![Python](https://img.shields.io/badge/python-3.12%2B-3b82f6)\n\n<p align=\"center\">\n  <img src=\"docs/PlainQuery_GIF.gif\" alt=\"PlainQuery demo — querying a database in plain English from an MCP client\" width=\"800\">\n</p>\n\n---\n\n## What it does\n\nYou connect a database once. Then, from any MCP client, you ask questions like:\n\n> _\"How many orders did we ship in March?\"_\n> _\"Which 5 customers spent the most last year?\"_\n> _\"What's the average order value by month?\"_\n\nPlainQuery 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**.\n\n## Why PlainQuery\n\n- **Natural language, real answers** — no SQL required; results come from your actual data, not a hallucination.\n- **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.\n- **Self-correcting** — when a generated query errors, the agent feeds the error back to the LLM and retries (up to a configurable limit).\n- **Schema-aware** — automatically introspects and caches your schema, so questions map to the right tables and columns.\n- **Works with any MCP client** — Claude Desktop, Cursor, VS Code Copilot, or anything that speaks MCP.\n- **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.\n- **Hosted or self-hosted** — use the managed service, or run the whole stack yourself with Docker.\n- **Bring your own LLM** — Anthropic Claude or Groq.\n\n## How it works\n\n```\nYour question\n   → Schema introspection      (reads tables/columns, cached)\n   → SQL generation            (schema + question → LLM → SQL)\n   → Safety validation         (blocks writes, scans dangerous patterns, injects LIMIT)\n   → Execution                 (runs read-only, with a timeout)\n   → Self-correction retry     (on error: feed it back to the LLM, fix, re-run)\n   → Structured JSON result\n```\n\n### Architecture\n\n<p align=\"center\">\n  <img src=\"docs/diagram/plainquery-architecture.png\" alt=\"PlainQuery architecture diagram showing clients, Next.js frontend, Starlette/FastAPI backend, FastMCP endpoint, query pipeline, LLM provider, and customer database\" width=\"900\">\n</p>\n\n---\n\n## Quick start\n\n### Option 1 — Use the hosted service (no install)\n\n1. Sign up at **`https://plainquery.in`** and verify your email.\n2. Connect your PostgreSQL database and create an API key.\n3. Add the server to your MCP client (see [Connect your MCP client](#connect-your-mcp-client)).\n\nThat's it — start asking questions.\n\n### Option 2 — Run it yourself\n\nSee [Self-hosting & local development](#self-hosting--local-development) below.\n\n## Connect your MCP client\n\nPlainQuery is published on the [official MCP Registry](https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.Jarvis-27/mcp-db-agent) as `io.github.Jarvis-27/mcp-db-agent`, so registry-aware clients can discover it directly.\n\nTo configure a client manually, point it at the MCP endpoint and pass your API key:\n\n```json\n{\n  \"mcpServers\": {\n    \"plainquery\": {\n      \"url\": \"https://plainquery.in/mcp\",\n      \"headers\": { \"X-API-Key\": \"mdbk_your_key_here\" }\n    }\n  }\n}\n```\n\nThe 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](#setup-payloads)). MCP clients can authenticate with an OAuth 2.1 bearer token or an API key, depending on `MCP_AUTH_MODE`.\n\n---\n\n## Self-hosting & local development\n\n### Prerequisites\n\n- Python 3.12+\n- `uv`\n- Node.js 20+ and `pnpm` for the frontend\n- At least one LLM API key (`ANTHROPIC_API_KEY` or `GROQ_API_KEY`)\n\n### Backend\n\n```bash\nuv sync\ncp .env.example .env\n```\n\nEdit `.env` with at least:\n\n- `CREDENTIAL_ENCRYPTION_KEYS`\n- `REGISTRATION_OPEN=true`\n- one LLM provider key plus `LLM_PROVIDER`\n\nRun the backend:\n\n```bash\nuv run uvicorn src.app:app --reload --host 0.0.0.0 --port 8000\n```\n\nThe backend mounts:\n\n- REST API at `http://localhost:8000/api`\n- MCP endpoint at `http://localhost:8000/mcp`\n\n### Frontend\n\n```bash\ncd frontend\npnpm install\npnpm dev\n```\n\nOpen `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.\n\n### Runtime model\n\n- Backend API: FastAPI/Starlette at `src.app:app`\n- Frontend: Next.js app in `frontend/`\n- Auth: passwordless email verification and login links\n- MCP auth: OAuth 2.1 bearer tokens (`oauth_only`), API keys (`api_key_only`), or both (`hybrid`) — set via `MCP_AUTH_MODE`\n- Setup payloads: `POST /api/v1/account/setup-payloads`\n- Billing: Stripe Checkout, Customer Portal, and webhook-confirmed Free/Pro entitlements\n\nThe product model is single-account and user-scoped:\n`signup → verify email → connect database → link OAuth identity → use /mcp → upgrade with Stripe`\n\n## Security\n\n<p align=\"center\">\n  <img src=\"docs/diagram/plainquery-security.png\" alt=\"PlainQuery security diagram showing URL validation, SSRF protection, encrypted credential storage, MCP authentication, per-request isolation, SQL validation, rate limits, and controlled execution\" width=\"900\">\n</p>\n\n- User-supplied database URLs are validated against SSRF, path traversal, private IPs, and DNS rebinding before any connection attempt.\n- Database URLs and LLM keys are Fernet-encrypted at rest, with support for key rotation.\n- 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.\n- Per-request context scoping prevents cross-tenant data leaks.\n- Per-user rate limits and fallback-LLM quotas limit cost abuse.\n\n## Important environment variables\n\n| Variable | Description |\n|---|---|\n| `AUTH_DATABASE_URL` | Auth/account database used by the hosted product |\n| `CREDENTIAL_ENCRYPTION_KEYS` | Encrypts stored database URLs and other secrets |\n| `REGISTRATION_OPEN` | Enables or disables public signup |\n| `ANTHROPIC_API_KEY` / `GROQ_API_KEY` | LLM credentials for SQL generation |\n| `LLM_PROVIDER` | Active provider name |\n| `APP_BASE_URL` | Base URL used in setup payloads |\n| `FRONTEND_BASE_URL` | Base URL used in email links |\n| `STRIPE_SECRET_KEY` / `STRIPE_WEBHOOK_SECRET` | Stripe API and webhook credentials |\n| `STRIPE_PRO_PRICE_ID` | Stripe Price ID that maps to the Pro plan |\n| `STRIPE_CHECKOUT_SUCCESS_URL` / `STRIPE_CHECKOUT_CANCEL_URL` | Optional Checkout redirect overrides |\n| `STRIPE_CUSTOMER_PORTAL_RETURN_URL` | Optional Customer Portal return URL override |\n\nSee [.env.example](./.env.example) for the current full set.\n\n## API surface\n\n### Auth\n\n- `POST /api/v1/auth/signup`\n- `GET /api/v1/auth/verify-email`\n- `POST /api/v1/auth/request-login-link`\n- `GET /api/v1/auth/exchange-login-link`\n- `POST /api/v1/auth/logout`\n\n### Account (session-authenticated)\n\nAll account routes use session token auth (`x-session-token: <session-token>` or `Authorization: Bearer <session-token>`):\n\n- `GET /api/v1/account`\n- `GET /api/v1/account/status`\n- `PUT /api/v1/account/database`\n- `GET /api/v1/account/api-keys`\n- `POST /api/v1/account/api-keys`\n- `DELETE /api/v1/account/api-keys/{id}`\n- `POST /api/v1/account/api-keys/{id}/rotate`\n- `POST /api/v1/account/setup-payloads`\n- `GET /api/v1/account/dashboard`\n- `GET /api/v1/account/usage/recent`\n\n### Billing\n\n- `GET /api/v1/account/billing`\n- `POST /api/v1/account/billing/checkout-session`\n- `POST /api/v1/account/billing/portal-session`\n- `POST /api/v1/billing/webhook`\n\nStripe webhooks are the source of truth for plan transitions. Checkout or\nsubscription activation moves a user to `plan_code=pro`; canceled, unpaid, or\npast-due states restrict paid entitlements without deleting database setup.\n\n### OAuth MCP account linking (session-authenticated)\n\n- `GET /api/v1/account/mcp-oauth/status`\n- `POST /api/v1/account/mcp-oauth/start`\n- `GET /api/v1/account/mcp-oauth/callback`\n- `DELETE /api/v1/account/mcp-oauth/link`\n\n### MCP\n\n- `POST /mcp`\n- Auth: OAuth 2.1 bearer token, API key, or both — controlled by `MCP_AUTH_MODE`\n\n## Setup payloads\n\n`POST /api/v1/account/setup-payloads` returns client configuration material for VS Code, Cursor, generic HTTP MCP clients, and the current ChatGPT placeholder.\n\n```bash\ncurl -X POST http://localhost:8000/api/v1/account/setup-payloads \\\n  -H \"Authorization: Bearer <session-token>\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"raw_api_key\":\"mdbk_...\"}'\n```\n\nThe 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.\n\n## Tests\n\n```bash\nuv run pytest tests/ -m \"not integration\"\nuv run pytest tests/ -m integration\nuv run ruff check .\nuv run mypy src --ignore-missing-imports\n```\n\n## Deployment smoke test\n\nAfter each hosted deploy, verify the public MCP OAuth discovery surface:\n\n```bash\nuv run python scripts/smoke_mcp_deployment.py \\\n  --mcp-url https://mcp.example.com/mcp \\\n  --issuer-url https://YOUR_DOMAIN.auth0.com/\n```\n\nThe smoke test checks the unauthenticated `/mcp` challenge, protected resource\nmetadata, ChatGPT/Claude well-known discovery routes, and issuer metadata. To\nalso verify authenticated MCP `initialize`, `tools/list`, and `resources/list`,\npass a linked user's access token with `--access-token` or\n`MCP_SMOKE_ACCESS_TOKEN`.\n\n## Docker\n\nRun the hosted HTTP stack:\n\n```bash\ndocker compose up --build\n```\n\nThe container image serves only the hosted HTTP runtime. The MCP endpoint remains `http://localhost:8000/mcp`.\n\n## License\n\n[MIT](./LICENSE) © 2026 PlainQuery\n",
  "bytes": 10453,
  "sha": "8212f91ddd0e666cf73d2db1fc5dbc8a555a21306309ce9b92ee7c930a9db5ee",
  "repo_slug": "jarvis-27/mcp-db-agent",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_jarvis_27_mcp_db_agent_1f87df53/readme"
}