Skip to main content
Glama
hand-by-hand

dynamic-db-mcp-server

by hand-by-hand
README.md
# Dynamic DB MCP Server

把已有关系数据库通过简单配置变成安全的 MCP Server:API Key 授权、读/写控制、表级黑白名单、AST 级 SQL 校验、Key 有效期与限流/熔断、审计日志、调用统计图表、Web 管理控制台。

> 📖 管理员操作手册见 [docs/usage.md](docs/usage.md)(功能使用说明)。

支持 PostgreSQL / MySQL / SQLite / SQL Server / Oracle 关系库,以及 **Prometheus**(PromQL,只读,兼容 VictoriaMetrics),适配器接口化可继续扩展。

---

## 功能截图

| 概览(统计 + 异常预警) | 数据源管理 |
|---|---|
| ![概览](docs/screenshots/01-overview.png) | ![数据源](docs/screenshots/02-datasources.png) |

| API Key 授权(有效期/限流/并发/终端名单) | MCP 接入(端点与客户端配置片段) |
|---|---|
| ![API Keys](docs/screenshots/03-apikeys.png) | ![MCP 接入](docs/screenshots/04-mcp-access.png) |

| 权限模拟检验(Key 视角 dry-run) | 查询控制台 / 审计日志 |
|---|---|
| ![模拟检验](docs/screenshots/05-simulator.png) | ![查询控制台](docs/screenshots/06-query-console.png) |

---

## 快速开始(开发模式)

### 1. 环境要求

- **Node.js ≥ 20**(推荐 24,内置 `node:sqlite`,项目零原生依赖)
- npm(随 Node 自带)
- 无需安装任何数据库——配置存储用内置 SQLite

### 2. 安装依赖

```bash
# 后端依赖(在根目录,后端依赖已合并进根 package.json)
npm install

# 前端依赖
npm --prefix web install
```

### 3. 启动

```bash
npm run dev          # 后端(8787) + 前端(Vite 3000) 一起启动
npm run dev -- --port 7100   # 指定前端端口(参数会转发给 Vite)
```

- 前端控制台:http://localhost:3000 (Vite 已配置 `/api`、`/mcp` 代理到后端)
- 后端 API:http://localhost:8787/api

### 4. 首次使用

1. 打开前端地址 → **引导页创建管理员账号**(单管理员,密码 ≥ 6 位)
2. 系统已内置 SQLite 演示库 `demo`(只读,`user_passwords` 在黑名单),可直接体验
3. 「数据源」页添加真实数据库(PostgreSQL / MySQL / SQLite / SQL Server / Oracle),点「测试连接」确认
   - SQL Server:默认端口 1433;内网实例已默认 `trustServerCertificate`
   - Oracle:默认端口 1521,「数据库名」填**服务名或 SID**(如 `ORCL`、`XEPDB1`);连接串可填 `host:1521/服务名` 或完整 connect descriptor;使用 oracledb Thin 模式,**服务器无需安装 Oracle 客户端**
4. 「API Keys」页创建密钥,勾选可访问数据源及读/写权限,按需设置有效期与限流/熔断(**完整 Key 只显示一次**)
5. 「MCP 接入」页复制端点与客户端配置片段,粘贴到 Cursor / Claude Desktop 等
6. 「模拟检验」页以 Key 视角验证有效表清单和 SQL 放行/拦截
7. 「审计日志」页查看全部调用流水(本地时间显示)

---

## 生产模式部署

生产模式下后端单进程托管一切:**管理 API + MCP 端点 + 前端静态文件**,只暴露一个端口(默认 8787)。

### 0. Docker 部署(最省心)

```bash
# 构建镜像(多阶段:前端构建 + 后端运行时,单容器全托管)
docker build -t dynamic-db-mcp-server .

# 运行(data 卷持久化配置/审计,首次启动在控制台创建管理员)
docker run -d --name dmcp -p 8787:8787 -v dmcp-data:/app/data dynamic-db-mcp-server

# 或 docker compose
docker compose up -d
```

访问 `http://<服务器>:8787/`。镜像基于 `node:24-alpine`,内置健康检查(`/health`);自定义端口用 `-e PORT=9000 -p 9000:9000`。**注意**:容器内连接数据库时,主机地址不能用 `localhost`(指容器自身),请用数据库实际 IP;SQLite 数据源的文件路径需位于挂载进容器的目录中。

### 1. npm 包安装(免构建)

项目可作为单个 npm 包分发:包内已含**预构建的前端控制台**,安装方只需 Node.js ≥ 20,无需 clone 仓库、无需构建。

```bash
# 在仓库内打包(prepack 钩子会自动构建前端,产出 dynamic-db-mcp-server-x.y.z.tgz)
npm pack

# 目标机器安装(tgz 直接分发,或先 npm publish 到公网/私有 registry)
npm i -g ./dynamic-db-mcp-server-x.y.z.tgz
# 或:npm i -g dynamic-db-mcp-server

# 在任意目录启动;配置与审计数据落在当前目录 ./data(可用 DMCP_DATA_DIR 覆盖)
mkdir dmcp && cd dmcp
dynamic-db-mcp-server        # 或简写 dmcp;dmcp --port 9000 自定义端口(--host/--data-dir 见 dmcp --help)
```

也可以作为项目依赖安装后用 `npx dynamic-db-mcp-server` 启动,效果相同。

### 2. 源码构建与启动

```bash
# 安装依赖(后端只装生产依赖,在根目录)
npm install --omit=dev
npm --prefix web install

# 构建前端产物到 web/dist(后端会自动托管)
npm run build

# 启动生产服务
npm start
# 或自定义监听地址与端口
PORT=9000 HOST=0.0.0.0 npm start        # Linux/macOS
set PORT=9000 && npm start              # Windows cmd
$env:PORT=9000; npm start               # Windows PowerShell
```

启动后访问:

| 入口 | 地址 |
|---|---|
| Web 控制台 | `http://<服务器>:8787/` |
| 管理 API | `http://<服务器>:8787/api` |
| MCP 端点 | `http://<服务器>:8787/mcp`(Header `Authorization: Bearer <API_KEY>`) |
| 健康检查 | `http://<服务器>:8787/health` |

环境变量:

| 变量 | 默认 | 说明 |
|---|---|---|
| `PORT` | `8787` | 监听端口 |
| `HOST` | `0.0.0.0` | 监听地址;仅本机使用可设 `127.0.0.1` |
| `DMCP_SECRET` | 自动生成 | 数据源密码加密主密钥。不设置时首次启动生成随机密钥存入 `data/.secret`;生产环境建议显式设置为高强度随机串,便于密钥统一管理与灾备恢复 |
| `DMCP_DATA_DIR` | 见说明 | 数据目录(config.db / demo.db / .secret)。`npm start` 源码运行默认 `<仓库>/data`;`dynamic-db-mcp-server` 命令启动默认 `<当前目录>/data` |

### 3. 数据与备份

- 所有配置(管理员、数据源、Key 哈希、审计日志)在 `data/config.db`,演示库在 `data/demo.db`
- **数据源密码落盘加密**:`config_json` 中的 `password` / `connectionString` 以 AES-256-GCM 加密存储(`enc:v1:` 前缀),主密钥不入库——来自环境变量 `DMCP_SECRET`,或首次启动自动生成到 `data/.secret`(权限 600)。旧版本写入的明文会在启动时**自动迁移**为密文
- **备份 = 复制 `data/` 目录(务必包含 `data/.secret`)**;若主密钥通过 `DMCP_SECRET` 注入,则需单独妥善保管该值。**丢失主密钥 = 所有数据源密码无法还原**(其余数据不受影响)
- 数据库密码等敏感配置只存在服务端,API 返回一律脱敏(`••••••••`);编辑数据源时不改密码则保留原密码

### 4. 进程常驻

**Linux(systemd)** `/etc/systemd/system/dmcp.service`:

```ini
[Unit]
Description=Dynamic DB MCP Server
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/dynamic_mcp_server
Environment=PORT=8787 HOST=0.0.0.0
ExecStart=/usr/bin/node server/src/index.js
Restart=always
RestartSec=3
User=dmcp

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl enable --now dmcp
```

**跨平台(pm2)**:

```bash
npm install -g pm2
pm2 start server/src/index.js --name dmcp
pm2 save && pm2 startup     # 开机自启
```

**Windows**:推荐用 **NSSM** 注册为系统服务(GUI 操作)。

**NSSM 安装为 Windows 服务**:

1. 下载 NSSM https://nssm.cc/download 解压后使用 `win64\nssm.exe`
2. 以**管理员身份**打开 cmd / PowerShell,执行:

```bat
nssm install DynamicMCPServer
```

3. 在弹出的图形界面中填写:

| 页签 | 字段 | 值 |
|---|---|---|
| Application | Path | `C:\Program Files\nodejs\node.exe`(你的 node.exe 实际路径) |
| Application | Startup directory | `D:\apps\dynamic_mcp_server`(项目根目录) |
| Application | Arguments | `server\src\index.js` |
| Details | Display name / Description | `Dynamic DB MCP Server` 等(可选) |
| Details | Startup type | Automatic |
| I/O | Output / Error | `D:\apps\dynamic_mcp_server\data\service.log`(可选,日志落盘) |
| Environment | 环境变量 | `PORT=8787`、`HOST=0.0.0.0`(按需) |

4. 点击 **Install service** 完成注册,然后启动:

```bat
nssm start DynamicMCPServer
```

验证:浏览器打开 `http://<服务器>:8787/health` 返回 `{"ok":true,...}` 即成功;后续可在「服务」(services.msc)中管理 `DynamicMCPServer`,卸载用 `nssm remove DynamicMCPServer confirm`。

**注意**:SQLite 数据源里的文件路径是**服务器本机路径**,服务默认以 LocalSystem 运行;如需访问网络共享盘上的库文件,在 NSSM 的 **Log on** 页签指定有权限的域/本机账号。

**npm 全局安装的场景**(`npm i -g dynamic-db-mcp-server`):命令入口在 npm 全局前缀下,直接用 NSSM CLI 注册(无需 GUI):

```bat
:: 1. 查 node 与 npm 全局前缀的实际路径
where.exe node
npm config get prefix

:: 2. 注册服务(路径按上一步输出替换;--port 等参数直接跟在入口脚本后)
nssm install DynamicDBMCPServer "C:\Program Files\nodejs\node.exe" "C:\Users\<你>\AppData\Roaming\npm\node_modules\dynamic-db-mcp-server\bin\dmcp.js" --port 8787

:: 3. 工作目录即数据根目录:服务启动后数据在 D:\apps\dmcp\data
nssm set DynamicDBMCPServer AppDirectory D:\apps\dmcp

:: 4. 可选:日志落盘 / 显式指定数据目录
nssm set DynamicDBMCPServer AppStdout D:\apps\dmcp\data\service.log
nssm set DynamicDBMCPServer AppStderr D:\apps\dmcp\data\service.log
nssm set DynamicDBMCPServer AppEnvironmentExtra DMCP_DATA_DIR=D:\dmcp-data

:: 5. 启动
nssm start DynamicDBMCPServer
```

提示:npm 全局前缀默认在**安装用户的目录**下(`C:\Users\<你>\AppData\Roaming\npm`),服务以 LocalSystem 运行通常也能读取;若希望路径与具体用户解耦,先执行 `npm config set prefix D:\npm-global` 再全局安装,入口路径相应变为 `D:\npm-global\node_modules\dynamic-db-mcp-server\bin\dmcp.js`。

### 5. 反向代理 + HTTPS(推荐)

MCP 客户端跨机访问时建议在前面挂 Nginx/Caddy 终止 TLS。注意 **Streamable HTTP 需要关闭响应缓冲**:

```nginx
server {
    listen 443 ssl;
    server_name mcp.example.com;
    ssl_certificate     /etc/nginx/certs/mcp.pem;
    ssl_certificate_key /etc/nginx/certs/mcp.key;

    location / {
        proxy_pass http://127.0.0.1:8787;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;            # MCP/SSE 必需
        proxy_read_timeout 3600s;
    }
}
```

此时后端可只绑本机:`HOST=127.0.0.1 npm start`,MCP 客户端配置 `https://mcp.example.com/mcp`。

### 6. 客户端接入(生产)

```json
{
  "mcpServers": {
    "dmcp-prod": {
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer <API_KEY>" }
    }
  }
}
```

不支持自定义 Header 的客户端用 URL 形式:`https://mcp.example.com/mcp/key/<API_KEY>`。

客户端连接后看到的工具按授权动态生成:`<数据源slug>_list_tables / _describe_table / _query / _sample_rows`,有写权限时另有 `<数据源slug>_execute`,外加全局 `list_datasources`。Prometheus 数据源使用时序语义命名:`_list_metrics / _describe_metric / _query(PromQL)/ _query_range(区间查询)/ _sample_series`,无 `_execute`。

### 7. 上线安全检查清单

- [ ] 修改初始管理员密码(侧栏底部「修改密码」)
- [ ] 每个客户端一个独立 API Key,最小化授权数据源与读/写
- [ ] 敏感表加入黑名单(如 `*.user_passwords`、`sys.*`)
- [ ] 生产数据源尽量用**只读数据库账号**连接(双保险)
- [ ] 跨机访问走 HTTPS 反代;仅本机使用设 `HOST=127.0.0.1`
- [ ] 定期备份 `data/` 目录(含 `.secret` 主密钥文件);审计日志可在控制台导出核查,建议设置**保留策略**(审计日志页,默认永久保留)自动清扫
- [ ] 生产环境用 `DMCP_SECRET` 显式管理加密主密钥,并将其纳入密钥管理/灾备流程

---

## 权限模型

- **数据源级**:启用开关、只读/允许写、表(或 Prometheus 指标)白名单/黑名单(每条按正则整体匹配表名或 `schema.table`,如 `orders`、`^order_.*$`、`^public\..*`;单独的 `*` 表示全部;非合法正则按 `*` 通配的 glob 处理,如 `*.user_passwords`;黑名单优先)、行数上限、查询超时、**列脱敏规则**(列名正则命中即整列替换,MCP 与管理控制台查询同时生效)、**写操作二次确认**(execute 首次调用返回一次性令牌,带令牌再次调用同一 SQL 才执行)
- **Key 级**:逐数据源读/写授权;写工具需「Key 授权写」且「数据源允许写」双条件;支持有效期(到期自动失效)、每分钟限流与超限熔断(熔断期返回 429 + `Retry-After`,冷却自动恢复)、**并发调用上限**(限制同一 Key 同时在执行的调用数,防慢查询占满连接池)、终端 IP 黑/白名单(`*` 通配,命中返回 403 并记审计)
- **异常预警**:概览页自动识别多终端使用(24h ≥ 3 个不同 IP)、访问频率过高(1h ≥ 600 次)、熔断中的 Key,并给出控制建议;可配置 **Webhook**(企业微信/钉钉群机器人)主动推送——熔断即时推送,其余每 5 分钟评估、同一异常 6 小时内不重复
- **管理台登录防爆破**:同一账号 + 终端 IP 连续失败 5 次锁定 10 分钟(429 + 剩余时间),锁定事件记审计
- **账号角色**:单管理员 + 可创建**只读审计员**账号(仅查看概览/审计/统计,写操作一律 403;删号即注销会话)
- **SQL 校验**:node-sql-parser AST 分类 + 表名提取;单语句限制;解析失败时写一律拒

## 验证

```bash
npm test    # 52 条端到端用例(setup/认证/MCP 全链路/权限拦截/审计/模拟器/Prometheus/脱敏/并发/写确认)
```

## 目录

```
dev.mjs             # 根 dev 启动器(转发 CLI 参数给 Vite)
bin/dmcp.js         # npm 包全局命令入口(dynamic-db-mcp-server / dmcp)
scripts/publish-readme.md  # npm 发布页专用简介(pack 时替换 README.md,由 scripts/pack-readme.mjs 驱动)
server/             # 后端:Express API + MCP 端点 + node:sqlite 配置存储
web/                # 前端:React + shadcn/ui 管理控制台
data/               # 运行时生成:config.db、demo.db
Dockerfile          # 多阶段一体化镜像(前端构建 + 后端单端口托管)
docker-compose.yml  # compose 编排(data 卷持久化)
docs/usage.md       # 功能使用说明(管理员操作手册)
```

## 开源许可

本项目以 [MIT License](LICENSE) 开源:允许自由使用、复制、修改、分发(包括商用),须保留版权声明;软件按"现状"提供,不附带任何担保。