querygate
Provides a read-only interface to SQLite databases, enabling agents to list tables, describe schemas, run capped SELECT queries with column masking, and audit access.
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., "@querygaterun a SELECT on customers to count them, showing masked email addresses"
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.
querygate
A read-only SQL MCP server with guard rails. Give an agent access to a database without giving it the keys: read-only enforcement, table visibility rules, column masking on the way out, row and time caps, and an audit trail of everything it asked for — including what was refused.
Why
Connecting an agent to your warehouse is a two-line change, and that is the
problem. The usual result is a connection with the same privileges the analyst
had, no record of what was read, and an email column that is now flowing
through a model's context window on every question about signups.
querygate sits between the two and makes each of those a configuration decision instead of an accident.
Related MCP server: django-mcp-sql
Try it — no database, no credentials
git clone https://github.com/Waddles1729/querygate
cd querygate
npm install
npm run demo:http # seeds a small SQLite shop database and serves itThen, as an agent would:
> list_tables
4 table(s):
customers ~400 rows
order_items ~4,955 rows
orders ~2,500 rows
products ~8 rowsemployee_salaries exists in that database. It is denied by policy, so it is
not listed — the agent never learns it is there to ask about.
> run_query SELECT name, email, phone, city FROM customers LIMIT 3
name email phone city
--------------- ---------------------- ------------- ------
Mei Tanaka [redacted]@example.com *********1667 Sendai
Sota Sato [redacted]@example.com *********6121 Tokyo
Haruto Nakamura [redacted]@example.com *********7922 Tokyo
3 row(s) · 1ms · masked by policy: email, phoneMasking happens on egress, after the database has done its work — so analysis over those columns still gives true answers:
> run_query SELECT COUNT(DISTINCT email) AS distinct_customers FROM customers
distinct_customers
------------------
400And the refusals:
> run_query DROP TABLE customers
Query refused: only SELECT statements are allowed; this one starts with DROP
> run_query SELECT * FROM employee_salaries
Query refused: not permitted to read: employee_salariesTools
Tool | What it does |
| Visible tables and views, with approximate row counts |
| Columns, types, keys, and which columns are masked |
| One SELECT, capped and masked |
| The planner's output, without running the query |
| What this server has been asked to do recently |
Configuration
{
"database": { "kind": "postgres", "url": "postgres://readonly@db/app" },
"maxRows": 1000,
"timeoutMs": 10000,
"auditLog": "/var/log/querygate.jsonl",
"policy": {
"allowTables": ["analytics_*", "orders", "customers"],
"denyTables": ["employee_salaries", "*_pii"],
"hashSalt": "${QUERYGATE_SALT}",
"mask": [
{ "table": "customers", "column": "email", "strategy": "domain" },
{ "table": "customers", "column": "phone", "strategy": "partial" },
{ "table": "customers", "column": "ssn", "strategy": "hash" },
{ "table": "*", "column": "password_hash", "strategy": "redact" }
]
}
}Masking strategies:
Strategy |
| Use it when |
|
| The value must never appear |
|
| The domain is the analytically useful part |
|
| Last four characters identify a record for support |
|
| The agent must group or join on it but never read it |
hash is the one worth understanding: it is stable across rows and runs, so
GROUP BY user_id still works and the values are still never disclosed. It is
salted, so the same input hashes differently in each deployment.
Connecting it
Claude Desktop / Claude Code, over stdio:
{
"mcpServers": {
"warehouse": {
"command": "npx",
"args": ["-y", "querygate", "stdio"],
"env": {
"DATABASE_URL": "postgres://readonly@db/app",
"QUERYGATE_CONFIG": "/etc/querygate.json"
}
}
}
}Or one shared HTTP server for several agents:
querygate http --config /etc/querygate.json # POST /mcp, GET /healthBehind an authenticating proxy, set X-Querygate-Actor and the audit log
records who each session was. Without it, entries say anonymous rather than
inventing an identity.
What this is not
The SQL guard is defence in depth, not a security boundary. It tokenises
before it inspects — so SELECT 'drop table users' is allowed and
DR/**/OP TABLE users is not — and it refuses stacked statements, DDL, DML,
ATTACH, COPY, pg_read_file() and their neighbours. It also catches the
one people forget:
WITH gone AS (DELETE FROM users RETURNING *) SELECT * FROM gonewhich is valid Postgres, is not read-only, and passes any check that only looks at the first keyword.
But a SQL firewall is a filter, and filters have edges. The actual boundary is
the database role querygate connects as, which should have no write privilege
at all. querygate reinforces it — the Postgres adapter sets the session to
READ ONLY, and the SQLite adapter opens the file read-only — so a statement
that somehow slipped past the parser still cannot change anything.
Configure the role first. Treat everything here as the layer that gives you good error messages and an audit trail.
Databases
SQLite — bundled, used by the demo and the tests.
Postgres —
npm install pg. Optional peer dependency, so the demo does not drag a driver along.
Adding another is one interface with five methods (src/adapters/types.ts).
Development
npm install
npm test # 79 tests
npm run typecheck
npm run buildThe end-to-end tests run a real MCP client against a real server over the protocol, against a real SQLite database — not mocks. A tool that works when called in-process but fails to serialise over MCP is still broken.
License
MIT.
This server cannot be deployed
Maintenance
Related MCP Connectors
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Paid remote MCP for governed database query review, SQL simulation, approvals, and audits.
Read-only MCP for identity resolution and write guardrails.
Related MCP Servers
- AlicenseAqualityCmaintenanceSecure MCP server for safe, read-only DB access by AI agents, with SQL guardrails, table allowlists, PII masking, and audit logs626 npm7MIT
- AlicenseNot gradedqualityBmaintenanceProvides a read-only PostgreSQL SQL surface for LLM agents via MCP, with defense-in-depth security layers for safe database queries.3MIT
- AlicenseNot gradedqualityDmaintenanceRead-only SQL Server MCP server enabling safe database queries, table listing, and schema inspection with built-in security protections.MIT
- AlicenseNot gradedqualityBmaintenanceAn MCP server that gives agents safe, policy-scoped access to your database via structured queries (never raw SQL), with read and opt-in write support.2 npm4MIT