Db Gateway

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

概览

AI 生成的概览

一个本地 MCP 网关,位于 AI 助手与数据库之间,自动脱敏敏感字段、按角色控制表访问、限流并审计每次查询。

可以做什么
  • 列出并描述可访问的表,包括其脱敏策略与角色限制
  • 按条件查询表数据,敏感字段自动脱敏
  • 按表、工具、操作类型、仅拒绝记录或时间范围查询网关自身的审计历史
  • 查看当前网关配置、字段规则、表策略与审计设置
  • 在显式开启写入后,按表与角色权限插入、更新或删除行
适用场景
当助手需要读取真实数据库,但你希望用 PII 脱敏、表级访问控制、限流和审计日志代替直接提供数据库凭据时使用。适合有 KVKK 或 GDPR 合规要求、希望为 LLM 提供默认只读受控通道的团队。
运行要求
以本地进程运行,通过 npm(Node.js)安装或经 Smithery 添加。需要数据库连接:命令行方式使用 DATABASE_URL,或在代码中传入已有的 Prisma、pg、mysql2、better-sqlite3 或 MongoDB 客户端。活动角色通过 GATEWAY_ROLE 或配置指定;可选设置包括 GATEWAY_PII_SCAN 与 GUARDBEE_TELEMETRY。
安装前请注意
网关持有数据库凭据,所提供的 DATABASE_URL 会带来该账号的全部权限。写入默认关闭;开启 writesEnabled 并授予表与角色写权限后,助手可修改或删除数据,但空过滤条件与超出上限的匹配会被拒绝。审计历史保存在内存中,进程重启即清空。遥测默认关闭,但设置 GUARDBEE_TELEMETRY=1 会发送工具名、耗时及文件路径等短参数值。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Db Gateway,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

@guardbee/mcp-db-gateway

🇬🇧 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=1 it sends the server, tool, success, duration and short parameter values such as a file path; longer strings and fields like content, token or password are replaced with a placeholder. See @guardbee/mcp-telemetry.

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, a pg Pool, a mysql2 Pool, a better-sqlite3 Database, or a MongoDB Db directly
  • 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:

typescript
// Tool called from the Claude side:query_audit_log({ table: "orders", deniedOnly: true, limit: 20 })
  • How it works: 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.
  • 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) 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.
  • Tests: 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).

2. The SQLite adapter

The gateway now works with better-sqlite3 alongside Prisma/Postgres/MySQL:

typescript
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 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.
  • Bonus fix: the 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().
  • Tests: 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.

3. The MongoDB adapter

The gateway now also works with a Db instance from the mongodb driver:

typescript
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/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:
    • any key starting with $ (in both filter and data)
    • any key containing . — 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)
    • any non-scalar (object/array) filter value — including an operator-object bypass attempt like { 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.
  • "Table" = collection. tables() maps to listCollections(); insert attaches _id (the driver's insertedId) to the returned row; update runs updateMany with { $set: data }; delete runs deleteMany.
  • 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

bash
npm install -g @guardbee/mcp-db-gateway

Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):

json
{  "mcpServers": {    "guardbee-db-gateway": {      "command": "guardbee-gateway",      "env": {        "DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb"      }    }  }}

Windows: %APPDATA%\Claude\claude_desktop_config.json Linux: ~/.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)

bash
npm install @guardbee/mcp-db-gateway
typescript
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:

typescript
// 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" } */));
typescript
// 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" } */));
typescript
// SQLiteimport Database from "better-sqlite3";import { createServer, createSqliteAdapter } from "@guardbee/mcp-db-gateway";
const db = new Database("./app.db");const server = createServer({}, createSqliteAdapter(db));
typescript
// 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")));

Security note: unlike the Prisma adapter, the pg/mysql2/better-sqlite3 adapters generate raw SQL. Because table and column names can't be parameterized, they are validated against the live schema on every query (information_schema, or PRAGMA table_info for 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:

ToolDescription
query_tableQuery rows from a table (PII is masked automatically)
list_tablesList accessible tables (role restrictions applied)
describe_tableShow a table's schema and masking policy
query_audit_logQuery the gateway's own audit history (filterable by table/tool/operation/deniedOnly/since) — in-memory, capped by audit.bufferSize, resets on process restart
gateway_statusShow the active config, roles, and rate-limit status

When writesEnabled: true (see Write Support), 3 more write tools are added:

ToolDescription
insert_rowInserts a new row
update_rowUpdates rows matching a filter (an empty filter is rejected)
delete_rowDeletes rows matching a filter (an empty filter is rejected)

Configuration

typescript
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

StrategyDescriptionExample
redactThe field is removed entirely[REDACTED]
maskThe middle of the value is starred outah***@example.com / 530***67
hashSHA-256 (first 16 characters)a665a45920422f9d
allowPassed through unchanged[email protected]

Glob pattern support: *Password*, *Token*, *Secret*

PII inside text columns (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.

ts
createServer({ piiScan: { enabled: true, preset: "KVKK", disable: ["address"] } }, adapter);

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.

ts
createServer({ piiScan: { enabled: true, blockPatterns: [{ pattern: "CL-\\d{6}", category: "CLIENT_NO" }], allowPatterns: ["destek@acme\\.com\\.tr"] } }, adapter);

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.

bash
GATEWAY_ROLE=analyst node dist/cli.js

Rule precedence (highest to lowest):

  1. Global tableRules deny
  2. Role denyTables
  3. Role allowTables (a whitelist — if set, the table must be in this list)
  4. Role fieldRules → global fieldRules

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:

  1. writesEnabled: true — the global kill-switch. While false (default), insert_row/update_row/delete_row are never visible to Claude.
  2. Table permission — tableRules[].write.{insert,update,delete} — per table, off by default for all.
  3. Role permission (when a role is active) — 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:

  • Protected-field guarding — the LLM can never write a value into a field marked 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).

typescript
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 tablePrisma model
"users"prisma.user
"audit_logs"prisma.auditLog
"orders"prisma.order
"orderItems"prisma.orderItem

Development

bash
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

License

MIT — GuardBee

Feedback

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

工具

5

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

list_tables

List all available database tables. Tables marked as 'deny' in gateway policy are omitted.

无需输入参数。

describe_table

Get the column names and gateway masking policy for a table — helps the LLM understand what data it can access.

输入参数table必填

query_audit_log

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).

输入参数toollimitsincetableoperationdeniedOnly

gateway_status

Show the active gateway configuration: field masking rules, table policies, and audit settings.

无需输入参数。

版本历史

9
  1. v0.7.1最新Oct 30, 2026
  2. v0.6.2Oct 29, 2026
  3. v0.6.1Oct 22, 2026
  4. v0.5.4Oct 21, 2026
  5. v0.5.3Oct 20, 2026
  6. v0.5.1Oct 19, 2026
  7. v0.5.0Oct 16, 2026
  8. v0.4.0Oct 15, 2026
  9. v0.3.6Oct 13, 2026

评分

已满足适用检查项的 90%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分
  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具必需15 分
  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分