- Language
- TypeScript
- Last commit
- 25m ago
- Forks
- 1
- Open issues
- 2
- Registry update
- Oct 13, 2026
Connect
npm$claude mcp add db-gateway \ -- npx -y @guardbee/[email protected]
Environment variables
Nothing to configure — it works as is.
Security review
Review passedReviewed 43m ago.
- metadata
- scanned
- packages
- 1 checked
No findings.
Tools
This server runs on your machine, so its tool list is not fetched before install — the README below describes what it offers.
@guardbee/mcp-db-gateway
🇬🇧 English | 🇹🇷 Türkçe | 🇨🇳 中文 | 🇯🇵 日本語 | 🇰🇷 한국어 | 🇧🇷 Português | 🇪🇸 Español | 🇩🇪 Deutsch
A KVKK / GDPR-compliant MCP (Model Context Protocol) server — adds a security layer between an LLM and a database.
Instead of querying your database directly, Claude (or any other LLM) goes through this gateway. Sensitive fields are masked automatically, table access is role-controlled, and every query is written to an audit log.
This package sends usage telemetry to GuardBee by default (tool name + short parameters, e.g. a table name — real row data/filter values are never included, see
@guardbee/mcp-telemetry). This is separate from and independent of the gateway's own local audit log (audit.filePath). Disable withGUARDBEE_TELEMETRY=0.
Claude ──► MCP Gateway ──► Database
│
├─ PII masking (tcKimlik → [REDACTED])
├─ Role check (ai-agent can only reach the products table)
├─ Rate limiting (max 100 queries/minute)
└─ Audit log (every query is recorded)
Features
- PII Masking — Turkish national ID, IBAN, email, phone, password hashes, etc. are masked automatically
- Role-Based Access (RBAC) — Per-role table allow/deny lists and field rules
- Rate Limiting — Global and per-table request windows
- Audit Log — Writable to console, a file, or an HTTP webhook
- Prisma / Postgres / MySQL / SQLite / MongoDB Adapters — Plug in an existing
PrismaClient, apgPool, amysql2Pool, abetter-sqlite3Database, or a MongoDBDbdirectly - Write Support (optional) — insert/update/delete, off by default; gated by per-table + per-role permission, protected-field guarding, and a "don't touch the whole table" safety net
- 195 unit tests — masking, the pipeline, RBAC, the rate limiter, the audit log, and every adapter (read + write) are covered
Recent Changes (2026-09-15)
Three new steps in this package's AI Gateway growth work:
1. The query_audit_log tool
Previously the gateway's audit log was write-only — it landed in one of the console/file/http sinks, but Claude itself had no way to look back and ask "what just happened, which calls got denied." Now it can:
// Tool called from the Claude side:
query_audit_log({ table: "orders", deniedOnly: true, limit: 20 })
- How it works:
AuditLoggergained an always-on, in-memory ring buffer that's completely independent of the configured sink (audit.bufferSize, default 200). Even when the sink is"http"(a fire-and-forget webhook), history is still queryable through this buffer. The buffer resets when the process restarts, and is never populated whileaudit.enabled: false. - Filters:
table,tool(the MCP tool name, e.g."delete_row"),operation(read/insert/update/delete),deniedOnly(denied calls only),since(an ISO 8601 timestamp),limit(default 50, max 200). Results come back newest-first. - A real bug fixed along the way:
registerDbTools(read tools) andregisterWriteTools(write tools) previously each built their ownGatewayPipeline/AuditLoggerinstance. That meant audit events frominsert_row/update_row/delete_rowcalls would never have appeared in the buffer thatquery_audit_logreads — there were two separate, unaware-of-each-other buffers.server.tsnow builds a singleGatewayPipelineinstance and shares it across both tool groups. - Tests:
src/__tests__/audit-logger.test.ts(10 tests — buffer capping, every filter, ring-buffer behavior) plus 3 tests added topipeline.test.ts(verifying that reads and writes land in the same buffer, thatdeniedOnly+tablefilters compose correctly, and that the buffer stays empty whileaudit.enabled: false).
2. The SQLite adapter
The gateway now works with better-sqlite3 alongside Prisma/Postgres/MySQL:
import Database from "better-sqlite3";
import { createServer, createSqliteAdapter } from "@guardbee/mcp-db-gateway";
const db = new Database("./app.db");
const server = createServer({}, createSqliteAdapter(db));
- Same security pattern as pg/mysql: table/column names can't be parameterized, so they're validated before being embedded in SQL — here using SQLite's
PRAGMA table_info(table)instead ofinformation_schema. A name that fails the format check (e.g.users"; DROP TABLE users;--) or isn't in the live schema is rejected before any query reaches SQLite. better-sqlite3is only an optionalpeerDependency— it was not added to the package's owndevDependencies, since it requires a native binding; tests (like the pg/mysql tests) were written against a simple mock object matching theSqliteQueryableinterface, without importing the real package.lastInsertRowidinstead ofRETURNINGfor insert: since SQLite'sRETURNINGsupport is version-dependent, this follows the same approach as the MySQL adapter'sinsertId— if the table has anidcolumn and it's not already indata, the inserted row getslastInsertRowidattached.- Bonus fix: the
AuditEvent/AuditQueryFiltertypes were not exported from the package root (index.ts) at all — added for consumers who want to type their own calls toGatewayPipeline.queryAuditLog(). - Tests:
src/__tests__/sqlite-adapter.test.ts(21 tests) — the same scenario set aspg-adapter.test.ts: table listing, filtered/unfiltered queries, limits, unknown table/column rejection, injection-attempt rejection, insert (including id assignment), update, delete.
3. The MongoDB adapter
The gateway now also works with a Db instance from the mongodb driver:
import { MongoClient } from "mongodb";
import { createServer, createMongoAdapter } from "@guardbee/mcp-db-gateway";
const client = new MongoClient(process.env.DATABASE_URL!);
await client.connect();
const server = createServer({}, createMongoAdapter(client.db("mydb")));
- A different risk class — not SQL injection, but operator injection: for pg/mysql/sqlite the risk was a table/column name being embedded in SQL. MongoDB never embeds identifiers, but if a key in
filter/datais a Mongo operator like$where/$ne, or a value is an operator object like{ $ne: null }, the "simple key-value equality filter" a tool declares can silently turn into an arbitrary query. This adapter rejects all of the following up front:- any key starting with
$(in bothfilteranddata) - any key containing
.— a dotted path (e.g."passwordHash.reset") is both an operator-like risk and a way to bypassfindProtectedWriteFields's exact-name matching (outside of its glob support) - any non-scalar (object/array) filter value — including an operator-object bypass attempt like
{ status: { $ne: "active" } }
- any key starting with
mongodbis only an optionalpeerDependency— same as the SQLite adapter, tests were written against a mock object matching theMongoDatabaseinterface, without importing the real package.- "Table" = collection.
tables()maps tolistCollections(); insert attaches_id(the driver'sinsertedId) to the returned row; update runsupdateManywith{ $set: data }; delete runsdeleteMany. - Tests:
src/__tests__/mongo-adapter.test.ts(19 tests) — collection listing, filtered/unfiltered queries, limits, three operator-injection scenarios (a$-prefixed key, a dotted key, an operator-object value), insert/update/delete.
Quick Start
1. Connect with one click via Smithery
You can add this to Claude Desktop with one click via Smithery.
2. Connect to Claude Desktop via global install
npm install -g @guardbee/mcp-db-gateway
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"guardbee-db-gateway": {
"command": "guardbee-gateway",
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb"
}
}
}
}
Windows:
%APPDATA%\Claude\claude_desktop_config.jsonLinux:~/.config/Claude/claude_desktop_config.json
Restart Claude Desktop. The demo database loads automatically, with PII masking active.
3. Use it in your project (Prisma)
npm install @guardbee/mcp-db-gateway
import { PrismaClient } from "@prisma/client";
import { createServer, createPrismaAdapter } from "@guardbee/mcp-db-gateway";
const prisma = new PrismaClient();
const server = createServer(
{
audit: { enabled: true, sink: "file", filePath: "./audit.jsonl" },
},
createPrismaAdapter(prisma)
);
If you don't use Prisma, you can also pass a raw pg or mysql2 connection directly:
// Postgres
import { Pool } from "pg";
import { createServer, createPgAdapter } from "@guardbee/mcp-db-gateway";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const server = createServer({}, createPgAdapter(pool /*, { schema: "public" } */));
// MySQL
import mysql from "mysql2/promise";
import { createServer, createMysqlAdapter } from "@guardbee/mcp-db-gateway";
const pool = mysql.createPool(process.env.DATABASE_URL!);
const server = createServer({}, createMysqlAdapter(pool /*, { database: "shop" } */));
// SQLite
import Database from "better-sqlite3";
import { createServer, createSqliteAdapter } from "@guardbee/mcp-db-gateway";
const db = new Database("./app.db");
const server = createServer({}, createSqliteAdapter(db));
// MongoDB
import { MongoClient } from "mongodb";
import { createServer, createMongoAdapter } from "@guardbee/mcp-db-gateway";
const client = new MongoClient(process.env.DATABASE_URL!);
await client.connect();
const server = createServer({}, createMongoAdapter(client.db("mydb")));
Security note: unlike the Prisma adapter, the
pg/mysql2/better-sqlite3adapters generate raw SQL. Because table and column names can't be parameterized, they are validated against the live schema on every query (information_schema, orPRAGMA table_infofor SQLite) — a table/column name that isn't in the schema (e.g. an injection attempt) is rejected before it ever reaches SQL. The MongoDB adapter guards against a different risk ("operator injection" instead of SQL) — see Recent Changes.
MCP Tools
The gateway always exposes these 5 read tools to Claude:
| Tool | Description |
|---|---|
query_table | Query rows from a table (PII is masked automatically) |
list_tables | List accessible tables (role restrictions applied) |
describe_table | Show a table's schema and masking policy |
query_audit_log | Query the gateway's own audit history (filterable by table/tool/operation/deniedOnly/since) — in-memory, capped by audit.bufferSize, resets on process restart |
gateway_status | Show the active config, roles, and rate-limit status |
When writesEnabled: true (see Write Support), 3 more write tools are added:
| Tool | Description |
|---|---|
insert_row | Inserts a new row |
update_row | Updates rows matching a filter (an empty filter is rejected) |
delete_row | Deletes rows matching a filter (an empty filter is rejected) |
Configuration
createServer({
// PII field rules (the first match applies)
fieldRules: [
{ field: "tcKimlik", strategy: "redact" }, // [REDACTED]
{ field: "iban", strategy: "mask" }, // TR32***890
{ field: "email", strategy: "mask" }, // ah***@example.com
{ field: "passwordHash", strategy: "redact" },
{ field: "*Token*", strategy: "redact" }, // glob pattern
],
// Table access rules
tableRules: [
{ table: "audit_logs", access: "deny" },
{ table: "users", access: "allow", maxRows: 25 },
// write: if unspecified, that table has no write permission at all (off by default)
{ table: "orders", access: "allow", write: { insert: true, update: true, delete: false } },
],
// Turn on the write tools (insert_row/update_row/delete_row) — false by default.
// While false, these tools are never visible to Claude.
writesEnabled: true,
// How many rows update_row/delete_row may affect via one filter.
// If exceeded, the operation is rejected before touching the database ("narrow your filter").
maxAffectedRowsPerWrite: 10,
// Default max rows
defaultMaxRows: 50,
// Rate limiting
rateLimit: {
enabled: true,
windowMs: 60_000, // 1 minute
maxRequests: 100, // global limit
maxRequestsPerTable: 20, // per table
},
// Audit log
audit: {
enabled: true,
sink: "file", // "console" | "file" | "http"
filePath: "./audit.jsonl",
// webhookUrl: "https://..." (for sink: "http")
bufferSize: 200, // size of the in-memory history the `query_audit_log` tool reads
},
// Roles
roles: [
{
name: "ai-agent",
allowTables: ["products", "orders"], // only these tables
maxRows: 10,
// If a role doesn't define write (undefined), write is COMPLETELY off for that role,
// even if the table allows it. To permit writes, state it explicitly on the role too:
write: { insert: true, update: true, delete: false },
},
{
name: "analyst",
denyTables: ["audit_logs"], // this table is blocked
fieldRules: [
{ field: "email", strategy: "allow" }, // email unmasked
],
// write not defined → analyst can write nothing
},
],
// Active role (can also be set via the GATEWAY_ROLE env var)
activeRole: "ai-agent",
});
Masking Strategies
| Strategy | Description | Example |
|---|---|---|
redact | The field is removed entirely | [REDACTED] |
mask | The middle of the value is starred out | ah***@example.com / 530***67 |
hash | SHA-256 (first 16 characters) | a665a45920422f9d |
allow | Passed through unchanged | [email protected] |
Glob pattern support: *Password*, *Token*, *Secret*
Role-Based Access (RBAC)
The role is set when the server starts, via the GATEWAY_ROLE env var or config.activeRole.
Each Claude Desktop profile or deployment can run with a different role.
GATEWAY_ROLE=analyst node dist/cli.js
Rule precedence (highest to lowest):
- Global
tableRulesdeny - Role
denyTables - Role
allowTables(a whitelist — if set, the table must be in this list) - Role
fieldRules→ globalfieldRules
Write Support (write/insert/update/delete)
The gateway is fully read-only by default. Letting the LLM modify data requires deliberately unlocking a few gates:
writesEnabled: true— the global kill-switch. Whilefalse(default),insert_row/update_row/delete_roware never visible to Claude.- Table permission —
tableRules[].write.{insert,update,delete}— per table, off by default for all. - Role permission (when a role is active) —
roles[].write.{insert,update,delete}. If a role never defineswrite(undefined), write is completely off for that role — even if the table permits it. To permit a write, both the table and the role must explicitly saytrue(AND logic — deliberately stricter than the "role overrides" logic used for masking rules).
Beyond these three gates, there are two more protections that cannot be disabled:
- Protected-field guarding — the LLM can never write a value into a field marked
redact/mask/hashinfieldRules(e.g.tcKimlik,passwordHash); ifinsert_row/update_rowsees such a field indata, the whole request is rejected. maxAffectedRowsPerWrite— beforeupdate_row/delete_rowruns, the filter is first tried as a read; if the matched row count exceeds this limit (default 10), the operation is rejected before touching anything.update_row/delete_rowalso always reject an empty filter — "update/delete the whole table" is never possible through this gateway.
Every write attempt (accepted or denied) is written to the audit log; only which fields were written is logged, never data itself (so the audit log itself can't become a PII leak point).
createServer({
writesEnabled: true,
maxAffectedRowsPerWrite: 10,
tableRules: [
{ table: "orders", access: "allow", write: { insert: true, update: true, delete: false } },
],
roles: [
{ name: "ai-agent", allowTables: ["orders"], write: { insert: true, update: true, delete: false } },
],
activeRole: "ai-agent",
});
Prisma Adapter
Pass a PrismaClient directly — table name → model mapping is automatic:
| Query table | Prisma model |
|---|---|
"users" | prisma.user |
"audit_logs" | prisma.auditLog |
"orders" | prisma.order |
"orderItems" | prisma.orderItem |
Development
npm run dev # dev mode via tsx
npm run build # TypeScript compile
npm test # 195 unit tests
npm run test:watch # watch mode
npm run type-check # type-check only
License
MIT — GuardBee
From the server's repository — third-party content, read it before running anything it suggests.
More in Databases
Qdrant Memory
io.github.cnkang
Persistent scoped Qdrant memory for agents, through four explicit stdio MCP tools.
Shippable
io.github.Egarter
Find leaks in AI-built apps: open Supabase/Firebase data, exposed keys, unsafe edge functions
vector-store-scanner
io.github.GuardBee
Probes vector-database endpoints for unauthenticated exposure of embeddings/RAG data
migrationpilot
io.github.mickelsamuel
112 rules that catch dangerous PostgreSQL migrations, plus a pass/fail gate to call before any DDL.
SupaForge
io.github.akalsoftware
Find and fix drift between Supabase environments: schema, RLS, roles, auth, storage and more.
Open Quality
us.openquality
Open healthcare quality measures: CQL, SQL and FHIR with provenance and known issues
Listing from the official MCP Registry · repository facts from GitHub, 13m ago.