Server data from the Official MCP Registry
Safe SQL gateway for AI agents: SELECT-only validation, access policies, masking, audit trail, evals
Safe SQL gateway for AI agents: SELECT-only validation, access policies, masking, audit trail, evals
Valid MCP server (1 strong, 2 medium validity signals). No known CVEs in dependencies. Package registry verified. Imported from the Official MCP Registry.
8 files analyzed · 1 issue found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
This plugin requests these system permissions. Most are normal for its category.
Set these up before or after installing:
Environment variable: QUERYPILOT_DATABASE_URL
Environment variable: QUERYPILOT_DIALECT
Environment variable: QUERYPILOT_MAX_ROWS
Environment variable: QUERYPILOT_TIMEOUT_SECONDS
Environment variable: QUERYPILOT_ACCESS_POLICY_JSON
Environment variable: QUERYPILOT_MCP_TRANSPORT
Add this to your MCP configuration file:
{
"mcpServers": {
"io-github-nickklos10-querypilot": {
"env": {
"QUERYPILOT_DIALECT": "your-querypilot-dialect-here",
"QUERYPILOT_MAX_ROWS": "your-querypilot-max-rows-here",
"QUERYPILOT_DATABASE_URL": "your-querypilot-database-url-here",
"QUERYPILOT_MCP_TRANSPORT": "your-querypilot-mcp-transport-here",
"QUERYPILOT_TIMEOUT_SECONDS": "your-querypilot-timeout-seconds-here",
"QUERYPILOT_ACCESS_POLICY_JSON": "your-querypilot-access-policy-json-here"
},
"args": [
"querypilot"
],
"command": "uvx"
}
}
}From the project's GitHub README.
Eval-driven SQL reliability for AI agents.
QueryPilot helps agents safely generate, validate, repair, execute, and regression-test SQL against real fixture databases.
Read-only SQL access for agents is becoming a commodity. Tools that let an agent list tables, read schemas, and run validated SELECTs already exist. What is much harder — and what QueryPilot focuses on — is making that access measurably reliable: proving the SQL the agent generates is correct, safe, fast, and not regressing.
Every change to QueryPilot, your prompts, or your model can be measured against an execution-truth eval suite. Suites can be authored by hand or auto-generated by replaying your audit log as a regression set, so the same queries that worked in production yesterday have to keep working tomorrow.
python3 -m venv .venv
.venv/bin/pip install -e ".[dev,eval]"
.venv/bin/querypilot eval init # scaffold suites/ and .eval/
.venv/bin/querypilot eval run \
--suite suites/smoke.yaml \
--generator demo \
--report eval-out.json
.venv/bin/querypilot eval check \
--report eval-out.json \
--baseline .eval/baseline.json \
--threshold 0.9 \
--require-safety 1.0
Sample output (abridged — see the full report at the top of this README):
QueryPilot Eval Report
Suite: smoke
Generator: demo
Overall
✅ Pass rate 3 / 3 (100%)
✅ Safety pass rate 0 / 0 (100%)
✅ Correctness 3 / 3 (100%)
✅ P95 latency 18 ms
✅ No threshold violations.
The bundled suites/smoke.yaml runs against a tiny SQLite fixture (tests/fixtures/demo.db) so the harness works end-to-end without an LLM key. To benchmark a real generator, use --generator openai or --generator anthropic.
querypilot eval replay turns a JSONL audit log written by JSONLAuditSink into a BenchmarkSuite whose gold SQL is the SQL that previously executed. Re-running that suite gates accuracy regressions against your own production traffic — the unique-to-QueryPilot capability the eval positioning rests on.
querypilot eval replay \
--audit-jsonl audit.jsonl \
--fixture-db sqlite:///tests/fixtures/demo.db \
--output suites/replay.yaml
querypilot eval run --suite suites/replay.yaml --generator demo --report replay-out.json
Conservative defaults: only successful ask records, non-empty results, no active access policy. --include-failures, --include-masked, --include-empty relax each filter.
querypilot eval check compares a SuiteReport JSON against thresholds and a committed baseline, exiting non-zero on regression. A sample GitHub Actions workflow ships at .github/workflows/eval.yml:
- run: querypilot eval run --suite suites/smoke.yaml --generator demo --report eval-out.json
- run: querypilot eval check --report eval-out.json --baseline .eval/baseline.json --threshold 0.9 --require-safety 1.0
When a regression is detected the output explains which cases regressed and how:
Regression detected.
Pass rate:
baseline: 96%
current: 89%
Failed cases (regression vs. baseline):
- monthly_revenue_by_segment (was passing -> now result_mismatch)
- top_customers_by_arr (was passing -> now repair_failed)
Latency:
baseline p95: 2100 ms
current p95: 3800 ms (+1700 ms)
Refresh the baseline on main after a deliberate change:
querypilot eval run --suite suites/smoke.yaml --generator demo --report .eval/baseline.json
git commit -am "Refresh eval baseline"
Suites are YAML or JSON. Each case carries a question, a gold SQL, and the schema/safety expectations for the candidate.
name: saas_revenue_suite
fixture_db: sqlite:///fixtures/demo.db
fixture_dialect: sqlite
thresholds:
pass_rate: 0.95
safety_pass_rate: 1.0
correctness_rate: 0.9
max_p95_latency_ms: 5000
max_avg_cost_usd: 0.01
comparison:
ignore_row_order: true
ignore_column_order: true
float_tolerance: 0.001
normalize_datetimes: true
cases:
- id: top_customers_by_revenue
question: "Top customers by revenue"
gold_sql: |
SELECT customer_name, revenue
FROM customers
ORDER BY revenue DESC
LIMIT 100
expected_tables: [customers]
must_include: ["ORDER BY", "LIMIT"]
must_not_contain: [DELETE, UPDATE, DROP]
tags: [revenue, ranking]
- id: blocks_drop_table
sql: "DROP TABLE customers"
should_pass: false
expected_failure_kind: validation
expected_error_contains: ["Only SELECT queries are allowed"]
tags: [safety, ddl]
Result-set correctness is scored by executing both the gold and candidate SQL against the same fixture database and comparing rows. Order-insensitive by default; auto-flipped to order-sensitive when the gold SQL has a top-level ORDER BY.
from querypilot import QueryPilot
qp = QueryPilot.connect(
database_url="sqlite:///demo.db",
dialect="sqlite",
readonly=True,
max_rows=100,
)
result = qp.execute_sql("SELECT * FROM customers")
print(result.sql)
print(result.rows)
Natural-language ask() works offline for simple demo questions through a deterministic generator:
answer = qp.ask("Top customers by revenue")
print(answer.sql)
print(answer.rows)
print(answer.validation.risk_level)
Runnable, self-contained examples live in examples/. They all use
the bundled demo SQLite fixture, so most need no API key:
| Example | Shows | Key? |
|---|---|---|
01_quickstart.py | connect, execute_sql, offline ask(), validation risk level | No |
02_openai_tool_use.py | as_openai_tools() in an OpenAI tool-use loop | OPENAI_API_KEY |
03_anthropic_tool_use.py | as_anthropic_tools() in an Anthropic tool-use loop | ANTHROPIC_API_KEY |
04_access_control.py | blocked columns, row filter, and masking | No |
05_custom_eval_suite/ | a custom YAML suite run with querypilot eval run/check | No |
06_mcp/ | run querypilot mcp + a paste-ready Claude MCP config | No |
See examples/README.md for setup and the full index.
For production-style natural-language SQL generation, plug in an LLM generator. QueryPilot still treats model output as an untrusted candidate: it validates, rewrites, and can ask the generator for a repair before execution.
Install optional provider dependencies:
.venv/bin/pip install -e ".[openai]"
.venv/bin/pip install -e ".[anthropic]"
OpenAI:
from querypilot import QueryPilot
from querypilot.generation import OpenAISQLGenerator
qp = QueryPilot.connect(
"sqlite:///demo.db",
generator=OpenAISQLGenerator(model="gpt-5.1"),
max_generation_attempts=2,
)
Anthropic:
from querypilot import QueryPilot
from querypilot.generation import AnthropicSQLGenerator
qp = QueryPilot.connect(
"sqlite:///demo.db",
generator=AnthropicSQLGenerator(model="claude-sonnet-4-20250514"),
max_generation_attempts=2,
)
Any OpenAI-compatible endpoint — Ollama, vLLM, LM Studio,
or llama.cpp's server — works through OpenAICompatibleSQLGenerator. It reuses
the [openai] extra (no extra dependency) and talks the Chat Completions API, so
you can benchmark open models at $0. The API key is optional (local servers
ignore it), and cost reports show $0 while token counts still flow through when
the server returns usage.
ollama pull llama3.1
.venv/bin/pip install -e ".[openai]"
from querypilot import QueryPilot
from querypilot.generation import OpenAICompatibleSQLGenerator
qp = QueryPilot.connect(
"sqlite:///demo.db",
generator=OpenAICompatibleSQLGenerator(
model="llama3.1",
base_url="http://localhost:11434/v1", # Ollama's default; omit to use it
),
max_generation_attempts=2,
)
From the eval harness, add open models to the benchmark matrix with
--generator openai-compatible:
querypilot eval run \
--suite suites/smoke.yaml \
--generator openai-compatible \
--model llama3.1 \
--base-url http://localhost:11434/v1 \
--report eval-out.json
--base-url also reads $QUERYPILOT_BASE_URL, and defaults to Ollama's
http://localhost:11434/v1 when unset.
The safety loop is always:
question
-> schema-scoped prompt
-> model candidate SQL
-> QueryPilot validation
-> optional repair
-> safe execution
The CLI is a thin wrapper around run_suite, which is also usable directly:
from querypilot import QueryPilot
from querypilot.evals import (
BenchmarkCase,
BenchmarkSuite,
NullCostTracker,
build_qp_factory,
render_terminal,
run_suite,
)
from querypilot.generation.sql_generator import DemoSQLGenerator
suite = BenchmarkSuite(
name="adhoc",
fixture_db="sqlite:///tests/fixtures/demo.db",
cases=[
BenchmarkCase(
id="count_customers",
question="Count of customers",
gold_sql="SELECT COUNT(*) AS count FROM customers",
expected_tables=["customers"],
),
],
)
qp_factory = build_qp_factory(
database_url="sqlite:///tests/fixtures/demo.db",
generator=DemoSQLGenerator(),
)
report = run_suite(
suite,
qp_factory=qp_factory,
cost_tracker_factory=NullCostTracker,
)
print(render_terminal(report, color=False))
The returned SuiteReport is a Pydantic model with pass_rate, safety_pass_rate, correctness_rate, repair_rate, p50_latency_ms, p95_latency_ms, total_prompt_tokens, estimated_cost_usd, tag_rollups, failure_breakdown, threshold_violations, and the full per-case case_results list.
QueryPilot validates SQL before execution with:
sqlglot parsingLIMIT insertion and max-row cappingSELECT * warnings or rejectionlow, medium, high, criticalFor PostgreSQL production use, connect QueryPilot with a dedicated
least-privilege role that has only the required schema USAGE and table
SELECT grants. QueryPilot requests a read-only transaction and applies a
statement timeout, but application validation is not a replacement for
database permissions.
Example:
validation = qp.validate_sql("SELECT * FROM customers")
print(validation.valid)
print(validation.risk_level)
print(validation.query_fingerprint)
print(validation.policy_checks)
For stricter deployments:
from querypilot.core.config import SafetyPolicy
qp = QueryPilot.connect(
"sqlite:///demo.db",
safety_policy=SafetyPolicy(
allow_select_star=False,
reject_cartesian_joins=True,
),
)
QueryPilot exposes tool schemas without requiring SDK dependencies:
openai_tools = qp.as_openai_tools()
anthropic_tools = qp.as_anthropic_tools()
Available tools:
ask_databasesearch_schemavalidate_sqlexecute_sqlRun QueryPilot as a local safe SQL gateway:
.venv/bin/pip install -e ".[server]"
querypilot serve --database-url sqlite:///demo.db --dialect sqlite --max-rows 100
Or use environment variables:
export QUERYPILOT_DATABASE_URL=sqlite:///demo.db
export QUERYPILOT_DIALECT=sqlite
querypilot serve
Endpoints:
GET /healthGET /schemaPOST /search-schemaPOST /askPOST /generate-sqlPOST /validate-sqlPOST /execute-sqlPOST /evals/runGET /audit/recentExample:
curl -X POST http://127.0.0.1:8000/validate-sql \
-H "content-type: application/json" \
-d '{"sql": "SELECT * FROM customers"}'
Run QueryPilot as an MCP-compatible tool server:
.venv/bin/pip install -e ".[mcp]"
querypilot mcp --database-url sqlite:///demo.db --dialect sqlite
If your MCP client launches servers with uvx, include the [mcp] extra explicitly so the MCP SDK dependency is installed:
uvx --from 'querypilot[mcp]' querypilot mcp --database-url sqlite:///demo.db --dialect sqlite
By default, the MCP command uses stdio transport. For clients that support Streamable HTTP:
querypilot mcp \
--database-url sqlite:///demo.db \
--dialect sqlite \
--transport streamable-http
MCP tools:
ask_databasesearch_schemavalidate_sqlexecute_sqlQueryPilot records structured audit events for schema search, SQL generation, validation, execution, and full ask() flows.
Each audit record can include:
audit_idUse the default in-memory sink:
from querypilot import QueryPilot
from querypilot.audit import AuditMetadata
qp = QueryPilot.connect(
"sqlite:///demo.db",
audit_metadata=AuditMetadata(
actor="agent-1",
session_id="session-1",
app_name="analytics-agent",
),
)
result = qp.execute_sql("SELECT customer_name FROM customers")
print(result.audit_id)
print(qp.get_audit_records(limit=10))
Or persist JSONL audit events:
from querypilot import QueryPilot
from querypilot.audit import JSONLAuditSink
qp = QueryPilot.connect(
"sqlite:///demo.db",
audit_sink=JSONLAuditSink("querypilot-audit.jsonl"),
)
Read-only SQL is necessary but not enough. QueryPilot can also enforce column-level and row-level access policies before execution.
from querypilot import QueryPilot
from querypilot.access import AccessPolicy, MaskingRule
qp = QueryPilot.connect(
"sqlite:///demo.db",
access_policy=AccessPolicy(
blocked_columns={
"customers": ["email"],
},
row_filters={
"customers": "tenant_id = 42",
},
masking_rules={
"customers": {
"email": MaskingRule(mode="redact"),
},
},
),
)
What this does:
allowed_columns is configuredtenant_id = 42The server and MCP runtimes can also receive access policy JSON:
querypilot serve \
--database-url sqlite:///demo.db \
--access-policy-json '{
"row_filters": {"customers": "tenant_id = 42"},
"blocked_columns": {"customers": ["ssn"]}
}'
Shipped:
querypilot eval replay)querypilot eval check against a committed baseline) + sample GitHub Actions workflowquerypilot eval init — scaffolds suites/ and .eval/ for new projectsThe eval-driven foundation is shipped. Next pillars:
Be the first to review this server!
by Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
by Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
by mcp-marketplace · Developer Tools
Create, build, and publish Python MCP servers to PyPI — conversationally.