山宁泰DB MCP服务器
 ](https://ghcr.io/ruminaider/sanitized-db-mcp) 
在AST级别重写SQL查询以防止PII/PHI暴露的MCP服务器。
为什么存在
AI代理编写SQL。他们还会产生列名幻觉,忽略访问控制,并愉快地从充满个人数据的表中选择\*。该服务器位于代理和PostgreSQL数据库之间,重写每个查询,使隐藏列返回保留类型的占位符而不是真实值。该代理获得了有用的结果;你的用户会保护他们的隐私。
快速开始
1.创建一个满负荷列表
从数据库架构生成脚手架:
# Install the CLI (skip if using uvx or Docker below)
pip install sanitized-db-mcp
sanitized-db-mcp generate-allowlist --database-url postgresql://user:pass@host:5432/mydb > allowlist.yaml编辑YAML以仅显示代理应该看到的列(请参见 生成允许列表 在......下面
2.运行服务器
从下面四种方法中选择一种,并将配置添加到您的 .mcp.json.
方法1:uvx(推荐——零安装)
uvx 在没有永久安装和依赖冲突的隔离环境中运行该包。与pip不同,它不需要安装或管理任何东西。与Docker不同,不需要配置卷挂载或路径映射。
{
"sanitized-db": {
"type": "stdio",
"command": "uvx",
"args": ["sanitized-db-mcp"],
"env": {
"ALLOWLIST_PATH": "./allowlist.yaml",
"DATABASE_URL": "postgresql://user:pass@host:5432/mydb"
}
}
}无需安装。 uvx 在隔离环境中下载并运行该包。
方法2:pip安装
pip install sanitized-db-mcp{
"sanitized-db": {
"type": "stdio",
"command": "python3",
"args": ["-m", "sanitized_db_mcp.server"],
"env": {
"ALLOWLIST_PATH": "./allowlist.yaml",
"DATABASE_URL": "postgresql://user:pass@host:5432/mydb"
}
}
}方法3:Docker(预构建镜像)
{
"sanitized-db": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "ALLOWLIST_PATH=/app/allowlist.yaml",
"-e", "DATABASE_URL",
"-v", "./allowlist.yaml:/app/allowlist.yaml:ro",
"ghcr.io/ruminaider/sanitized-db-mcp:latest"
],
"env": {
"DATABASE_URL": "postgresql://user:pass@host:5432/mydb"
}
}
}方法4:Docker(从源代码构建)
docker build -t sanitized-db-mcp:local .{
"sanitized-db": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "ALLOWLIST_PATH=/app/allowlist.yaml",
"-e", "DATABASE_URL",
"-v", "./allowlist.yaml:/app/allowlist.yaml:ro",
"sanitized-db-mcp:local"
],
"env": {
"DATABASE_URL": "postgresql://user:pass@host:5432/mydb"
}
}
}3.通过代理查询
服务器公开了一个MCP工具(query)它接受原始SQL并返回经过净化的结果。
生成允许列表
CLI工具连接到数据库,读取模式,并生成一个YAML文件,其中 默认情况下不可见。您可以通过取消注释来选择列。
sanitized-db-mcp generate-allowlist --database-url postgresql://user:pass@host:5432/mydb > allowlist.yaml添加 --deny-pii 标记看起来像PII(电子邮件、姓名、电话、地址等)的列:
sanitized-db-mcp generate-allowlist --database-url postgresql://... --deny-pii > allowlist.yaml输出示例:
# Generated by: sanitized-db-mcp generate-allowlist --deny-pii
#
# HOW TO USE:
# - Columns under "columns:" are VISIBLE to agents (currently empty)
# - Commented lines show available columns — uncomment to make visible
# - Lines marked "# PII" were flagged as likely PII/PHI — review carefully
# - After editing, restart the MCP server to apply changes
tables:
users:
columns: {}
# Available columns (uncomment to make visible):
# id: {type: integer, placeholder: 0}
# is_active: {type: boolean, placeholder: false}
# email: {type: varchar, placeholder: '[REDACTED]'} # PII
# password: {type: varchar, placeholder: '[REDACTED]'} # PII取消注释您希望代理看到的列。把其他东西都藏起来。
允许列表YAML格式
tables:
:
columns:
: {type:
, placeholder: }
allowed_functions:
- FUNCTION_NAME- 列出的列是可见的。 未列出的列将被隐藏,并替换为保留类型的占位符。
type:基本PostgreSQL类型(integer,varchar,boolean,timestamp,uuid,jsonb等等)。placeholder:替换隐藏列值的SQL文字。必须与类型兼容。allowed_functions:代理可以调用的SQL函数。其他人都被拒绝了。
配置
| 变量 | 必填 | 默认 | 描述 |
|---|---|---|---|
ALLOWLIST_PATH | 是 | -- | 路径 allowlist.yaml |
MCP_SERVER_NAME | 没有 | sanitized-db | MCP服务器名称(影响工具名称: mcp____query) |
DATABASE_URL | 如果不使用Render | -- | PostgreSQL连接字符串 |
RENDER_POSTGRES_ID | 如果使用Render | -- | 渲染Postgres实例ID |
RENDER_API_KEY | 如果使用Render | -- | Render API承载令牌 |
如果同时设置了“渲染API”凭据,则服务器会首选这两个凭据。为了地方发展, DATABASE_URL 足够了。
框架集成
- 姜戈:使用a
visible()字段装饰器标记安全字段,然后从模型元数据生成分配列表。 - 轨道/其他:使用CLI工具搭建脚手架,然后手动管理。或者构建自己的生成器,输出相同的YAML格式。
- 自定义生成器:YAML格式是合约。任何生成符合要求的YAML的工具都可以使用。
SSE传输(远程部署)
对于共享部署(例如Render.com),服务器支持通过HTTP的SSE传输。
安装
pip install 'sanitized-db-mcp[sse]'配置
| 变量 | 必填 | 默认 | 描述 |
|---|---|---|---|
MCP_TRANSPORT | 没有 | stdio | 运输方式: stdio 或 sse |
PORT | 没有 | 8000 | HTTP端口(渲染会自动设置此端口) |
MCP_API_KEY | 推荐 | -- | HTTP身份验证的承载令牌 |
MCP_MAX_CONNECTIONS | 否 | 无限制 | 最大并发连接数(uvicon limit_concurrency) |
MCP_SESSION_TIMEOUT | 否 | 无限制 | 最大SSE会话持续时间(秒) |
跑步
export MCP_TRANSPORT=sse
export MCP_API_KEY=your-secret-key
export ALLOWLIST_PATH=./allowlist.yaml
export DATABASE_URL=postgresql://...
python -m sanitized_db_mcp.server码头工人
docker run -e MCP_TRANSPORT=sse -e MCP_API_KEY=secret -e ALLOWLIST_PATH=/app/allowlist.yaml \
-e DATABASE_URL=postgresql://... -p 8000:8000 sanitized-db-mcpClaude代码客户端配置
在MCP客户端配置中,指向SSE端点:
{
"mcpServers": {
"sanitized-db": {
"type": "sse",
"url": "https://your-service.onrender.com/sse",
"headers": {
"Authorization": "Bearer your-secret-key"
}
}
}
}端点
GET /sse--SSE连接(MCP会话)POST /messages/--客户端到服务器消息GET /health--健康检查(无需身份验证)
部署指南
连接限制: 集 MCP_MAX_CONNECTIONS 以防止在攻击或配置错误的情况下资源耗尽。一个合理的值是预期并发客户端的2-5x(例如,当预期10-20个客户端时为100)。超出限制的新连接将接收HTTP 503。注:每个SSE会话和每个 /messages/ 来自该会话的POST计数为单独的并发连接,因此不要将其设置得太低。
会话超时: 集 MCP_SESSION_TIMEOUT (秒)关闭废弃的SSE会话。28800(8小时)是一个合理的起点。客户端在超时后自动重新连接。如果没有这一点,自uvicorn以来,被放弃的会话会无限期地消耗资源 timeout_keep_alive 不适用于活动的SSE流。
CORS: CORS标头被有意省略。服务器的客户端(Claude Code、MCP CLI工具)是忽略CORS的非浏览器应用程序。原生浏览器 EventSource API无法发送 Authorization 头文件,因此浏览器即使尝试跨源连接也无法进行身份验证。如果基于浏览器的MCP客户端成为受支持的消费者,请添加Starlette的 CORSMiddleware.
审核日志记录: 每个查询都以结构化JSON的形式记录,其中包含客户端IP、请求ID、会话ID和用户代理,以符合HIPAA标准。在反向代理(Render、nginx)后面,从中提取客户端IP X-Forwarded-For。根据HIPAA要求,配置日志聚合器以保留6年。
消毒剂的工作原理
服务器公开了一个MCP工具(query)它接受原始SQL并返回经过净化的结果。每个查询都要经过一个11步的管道:
Agent sends SQL
|
v
1. Parse (pglast) ───── syntax error? → QuerySyntaxError
|
v
2. Statement type ───── not SELECT? → StatementTypeError
| SELECT INTO? → StatementTypeError
| FOR UPDATE/SHARE? → StatementTypeError
v
3. Table validation ─── system catalog? → SystemCatalogError
| not in allowlist? → RestrictedColumnError
| TABLESAMPLE? → unwrap, validate inner table
v
4. Function check ───── always-blocked? → DisallowedFunctionError
| not in allowlist? → DisallowedFunctionError
| FILTER (WHERE ...)? → walk with WHERE rules
| inline OVER clause? → walk with WHERE rules
v
5. WHERE/JOIN check ─── hidden column? → RestrictedColumnError
v
6. Clause check ──────── ORDER BY / GROUP BY / DISTINCT ON / WINDOW
| hidden column? → RestrictedColumnError
v
7. Subquery check ───── hidden column in subquery/CTE SELECT? → RestrictedColumnError
v
8. Rewrite SELECT ───── hidden columns → type-preserving placeholders
| SELECT * → visible columns + redaction marker
v
9. Serialize AST ────── rewritten SQL string
v
10. Execute ───────────── read-only, 5s timeout, SSL
v
11. Audit log ─────────── structured JSON (original, rewritten, outcome)安全模型
所有200个测试均通过,0次失败。笔测试套件涵盖21种攻击类别:
| 类别 | 测试 | 防御 |
|---|---|---|
| ORDER BY/GROUP BY/DISTINCT ON | 13 | sort子句、GROUP子句、DISTINCT子句与WHERE规则一起走 |
| 窗口函数攻击 | 6 | 命名为Window并内联OVER,遵循WHERE规则 |
| 聚合过滤器子句 | 4 | agg_FILTER与WHERE规则一起运行 |
| CTE攻击 | 6 | CTE SELECT目标已验证;未使用的隐藏列CTE被拒绝 |
| LATERAL连接攻击 | 3 | 相关子查询WHERE子句已验证 |
| 模式/标识符技巧 | 8 | 引用标识符、unicode转义、处理pg_temp模式 |
| 复合类型/行攻击 | 3 | 字段选择被拒绝;ROW(),隐藏列已编辑 |
| ARRAY攻击 | 3 | ARRAY子查询、ARRAY_AGG、ARRAY\[\]已捕获隐藏列 |
| JSONB运算符攻击 | 5 | ->、->>、#>、@>、?已捕获隐藏列上的运算符 |
| 类型转换攻击 | 4 | 隐藏列上的链式转换已被编辑;在WHERE中铸造被拒绝 |
| FROM子句变体 | 3 | TABLESAMPLE展开,处理VALUES和generate_series |
| 锁定子句 | 3 | 用于更新,用于共享,在消毒剂级别被拒绝 |
| 子查询嵌套 | 5 | 三重嵌套、相关、不存在、任何均已验证 |
| 语句类型/多语句 | 5 | SELECT INTO被拒绝;空字节、注释、美元报价已测试 |
| 列解析不明确 | 3 | 保守地解决不合格列 |
| 编码边缘大小写 | 4 | 西里尔字母同形符、unicode转义符、别名中的分号 |
| 定时/侧通道 | 4 | pg_sleep、放大、交叉连接、递归CTE被阻止 |
| 基于错误的提取 | 4 | 通用错误消息;没有泄漏列名/表名 |
| 连接安全 | 5 | SSL、超时、只读、自动提交、无错误连接字符串 |
| 允许列表完整性 | 4 | 不区分大小写的查找,未知列默认为隐藏 |
| 审计日志 | 4 | 涵盖所有结果,HIPAA字段存在,最终阻止保证 |
运行测试
cd sanitized-db-mcp
# Full test suite (200 tests across 3 suites)
python -m pytest sanitized_db_mcp/tests/ -v
# Individual suites
python -m pytest sanitized_db_mcp/tests/test_sanitizer.py -v # Core rewriting (37 tests)
python -m pytest sanitized_db_mcp/tests/test_bypass.py -v # Bypass resistance (64 tests)
python -m pytest sanitized_db_mcp/tests/test_pentest.py -v # Pen test (99 tests)
python -m pytest sanitized_db_mcp/tests/test_allowlist.py -v # Allowlist loader关键文件
| 文件 | 目的 |
|---|---|
server.py | MCP服务器入口点, query(sql) 工具 |
sanitizer.py | AST级SQL重写引擎 |
allowlist.py | 记忆中的异体表示 |
connection.py | 呈现API+静态连接管理 |
errors.py | 山宁泰错误类(无模式泄漏) |
audit.py | 符合HIPAA标准的查询审核日志记录 |
