query_table
Query rows from a database table with optional filters. All results pass through the KVKK/GDPR gateway — sensitive fields are automatically masked.
limittable必填filter
io.github.GuardBee由 GitHub 帳號 GuardBee 發布v0.7.1更新於 Nov 3, 2026
KVKK/GDPR-compliant LLM-to-database gateway: PII masking, RBAC, rate limiting, audit log
一個本機 MCP 閘道,位於 AI 助理與資料庫之間,自動遮罩敏感欄位、依角色控管資料表存取、限流並稽核每次查詢。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
參照 儲存庫 中的啟動說明。
🇬🇧 English | 🇹🇷 Türkçe | 🇨🇳 中文 | 🇯🇵 日本語 | 🇰🇷 한국어 | 🇧🇷 Português | 🇪🇸 Español | 🇩🇪 Deutsch
[npm version] [npm downloads] [License: MIT] [Smithery]
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.
Telemetry is off by default. With
GUARDBEE_TELEMETRY=1it sends the server, tool, success, duration and short parameter values such as a file path; longer strings and fields likecontent,tokenorpasswordare replaced with a placeholder. See@guardbee/mcp-telemetry.
PrismaClient, a pg Pool, a mysql2 Pool, a better-sqlite3 Database, or a MongoDB Db directlyThree new steps in this package's AI Gateway growth work:
query_audit_log toolPreviously 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:
AuditLogger gained 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 while audit.enabled: false.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.registerDbTools (read tools) and registerWriteTools (write tools) previously each built their own GatewayPipeline/AuditLogger instance. That meant audit events from insert_row/update_row/delete_row calls would never have appeared in the buffer that query_audit_log reads — there were two separate, unaware-of-each-other buffers. server.ts now builds a single GatewayPipeline instance and shares it across both tool groups.src/__tests__/audit-logger.test.ts (10 tests — buffer capping, every filter, ring-buffer behavior) plus 3 tests added to pipeline.test.ts (verifying that reads and writes land in the same buffer, that deniedOnly + table filters compose correctly, and that the buffer stays empty while audit.enabled: false).The gateway now works with better-sqlite3 alongside Prisma/Postgres/MySQL:
PRAGMA table_info(table) instead of information_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-sqlite3 is only an optional peerDependency — it was not added to the package's own devDependencies, since it requires a native binding; tests (like the pg/mysql tests) were written against a simple mock object matching the SqliteQueryable interface, without importing the real package.lastInsertRowid instead of RETURNING for insert: since SQLite's RETURNING support is version-dependent, this follows the same approach as the MySQL adapter's insertId — if the table has an id column and it's not already in data, the inserted row gets lastInsertRowid attached.AuditEvent/AuditQueryFilter types were not exported from the package root (index.ts) at all — added for consumers who want to type their own calls to GatewayPipeline.queryAuditLog().src/__tests__/sqlite-adapter.test.ts (21 tests) — the same scenario set as pg-adapter.test.ts: table listing, filtered/unfiltered queries, limits, unknown table/column rejection, injection-attempt rejection, insert (including id assignment), update, delete.The gateway now also works with a Db instance from the mongodb driver:
filter/data is 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:
$ (in both filter and data). — a dotted path (e.g. "passwordHash.reset") is both an operator-like risk and a way to bypass findProtectedWriteFields's exact-name matching (outside of its glob support){ status: { $ne: "active" } }mongodb is only an optional peerDependency — same as the SQLite adapter, tests were written against a mock object matching the MongoDatabase interface, without importing the real package.tables() maps to listCollections(); insert attaches _id (the driver's insertedId) to the returned row; update runs updateMany with { $set: data }; delete runs deleteMany.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.You can add this to Claude Desktop with one click via Smithery.
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
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.
If you don't use Prisma, you can also pass a raw pg or mysql2 connection directly:
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.
The gateway always exposes these 5 read tools to Claude:
When writesEnabled: true (see Write Support), 3 more write tools are added:
Glob pattern support: *Password*, *Token*, *Secret*
piiScan)Field rules go by column name, so a free-text column (notes, description) can still carry an e-mail, a TC kimlik or an API key. piiScan (off by default) masks those by value in every text field no field rule covers, nested objects and arrays included; an allow rule turns it off for that field. preset / enable / disable pick the detectors, with the same presets as the security proxy: ALL (default), KVKK, IDENTITY, IDENTIFIERS, SECRETS.
From the CLI: GATEWAY_PII_SCAN=true (every default detector) or GATEWAY_PII_SCAN=KVKK (a preset). An unknown preset or detector name fails at startup.
piiScan.blockPatterns and piiScan.allowPatterns add your own patterns, as in the security proxy: block masks your ids as <CATEGORY_n>, allow leaves a detected value whose whole text matches. A block pattern that runs over its 100 ms budget fails the query rather than return rows it may not have masked.
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.
Rule precedence (highest to lowest):
tableRules denydenyTablesallowTables (a whitelist — if set, the table must be in this list)fieldRules → global fieldRulesThe gateway is fully read-only by default. Letting the LLM modify data requires deliberately unlocking a few gates:
writesEnabled: true — the global kill-switch. While false (default), insert_row/update_row/delete_row are never visible to Claude.tableRules[].write.{insert,update,delete} — per table, off by default for all.roles[].write.{insert,update,delete}. If a role never defines write (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 say true (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:
redact/mask/hash in fieldRules (e.g. tcKimlik, passwordHash); if insert_row/update_row sees such a field in data, the whole request is rejected.maxAffectedRowsPerWrite — before update_row/delete_row runs, 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_row also 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).
Pass a PrismaClient directly — table name → model mapping is automatic:
MIT — GuardBee
Questions, ideas, or a false positive? Start a thread in GitHub Discussions or open an issue. If GuardBee is useful to you, a ⭐ on the repository helps others find it.
來源:packages/db-gateway/README.md,提交 bc5997d
Query rows from a database table with optional filters. All results pass through the KVKK/GDPR gateway — sensitive fields are automatically masked.
limittable必填filterList all available database tables. Tables marked as 'deny' in gateway policy are omitted.
無需輸入參數。
Get the column names and gateway masking policy for a table — helps the LLM understand what data it can access.
table必填Query the gateway's own audit log — inspect which tools were called, on which tables, whether they were allowed or denied (and why), and which fields were redacted/written. Covers events since this server process started; count is capped by the gateway's audit.bufferSize (default 200).
toollimitsincetableoperationdeniedOnlyShow the active gateway configuration: field masking rules, table policies, and audit settings.
無需輸入參數。
已滿足適用檢查項的 90%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
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)// Tool called from the Claude side:query_audit_log({ table: "orders", deniedOnly: true, limit: 20 })import Database from "better-sqlite3";import { createServer, createSqliteAdapter } from "@guardbee/mcp-db-gateway";
const db = new Database("./app.db");const server = createServer({}, createSqliteAdapter(db));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")));npm install -g @guardbee/mcp-db-gateway{ "mcpServers": { "guardbee-db-gateway": { "command": "guardbee-gateway", "env": { "DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb" } } }}npm install @guardbee/mcp-db-gatewayimport { 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));// Postgresimport { 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" } */));// MySQLimport 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" } */));// SQLiteimport Database from "better-sqlite3";import { createServer, createSqliteAdapter } from "@guardbee/mcp-db-gateway";
const db = new Database("./app.db");const server = createServer({}, createSqliteAdapter(db));// MongoDBimport { 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")));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",});createServer({ piiScan: { enabled: true, preset: "KVKK", disable: ["address"] } }, adapter);createServer({ piiScan: { enabled: true, blockPatterns: [{ pattern: "CL-\\d{6}", category: "CLIENT_NO" }], allowPatterns: ["destek@acme\\.com\\.tr"] } }, adapter);GATEWAY_ROLE=analyst node dist/cli.jscreateServer({ 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",});npm run dev # dev mode via tsxnpm run build # TypeScript compilenpm test # 195 unit testsnpm run test:watch # watch modenpm run type-check # type-check only