Skip to main content
Glama
README.md
# MCP 数据库问答系统

通过自然语言(中文)查询 **MySQL / 达梦 DM / 人大金仓 KingbaseES** 三种数据库的内容。

> 例如:用户问 _"2025 年固废重量排名前十的企业"_,系统自动定位 `t_solid_waste` 表,生成并执行 SQL,返回排行榜。

---

## 架构概览

```
┌───────────────┐     ┌─────────────────────┐
│ 浏览器 Web UI │ ──▶ │  FastAPI 后端       │
└───────────────┘     │  /api/ask           │
                      │  ├─ DeepSeek (生成SQL)│
                      │  ├─ SQL 安全校验     │
                      │  └─ 数据库适配层     │
                      └──────┬───────────────┘
                             │
       ┌─────────────────────┼─────────────────────┐
       ▼                     ▼                     ▼
   MySQL                  达梦 DM            人大金仓
                       (dmPython)          (psycopg2)


另外,项目也暴露一个标准的 MCP Server(stdio 协议),
可以直接挂到 Cursor / Claude Desktop 里使用。
```

---

## 目录结构

```
10.mcp/
├── requirements.txt
├── .env.example                # 环境变量模板(API Key、端口等)
├── config/
│   ├── databases.yaml.example  # 数据库连接配置模板
│   └── glossary.yaml           # 业务术语表 + Few-shot 样例
├── src/
│   ├── core/
│   │   ├── config.py           # 配置加载
│   │   ├── manager.py          # 数据库连接管理
│   │   ├── adapters/           # 三数据库适配器(mysql/dameng/kingbase)
│   │   └── security/
│   │       └── sql_validator.py  # SQL 安全校验(白名单 + 强制 LIMIT)
│   ├── mcp_server/
│   │   └── server.py           # MCP Server 入口(stdio)
│   └── webapp/
│       ├── main.py             # FastAPI 后端
│       ├── llm.py              # DeepSeek 调用
│       ├── prompts.py          # 系统提示词
│       └── static/index.html   # 单页 Web UI
├── scripts/
│   ├── install_dameng_driver.md
│   ├── install_kingbase_driver.md
│   └── platform_db_init.py     # 平台 PG 建表(Alembic upgrade head)
├── migrations/
│   └── platform/               # Alembic 迁移(多企业租户控制面)
└── tests/
    └── test_sql_validator.py
```

---

## 安装与启动

### 1. 创建虚拟环境(首次)

```powershell
cd G:\phpstudy_pro\WWW\10.mcp
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
```

> 达梦驱动需要单独装:见 `scripts/install_dameng_driver.md`

### 2. 配置

```powershell
copy .env.example .env
copy config\databases.yaml.example config\databases.yaml
```

然后编辑:

- `.env` —— 填入 `DEEPSEEK_API_KEY`
- `config/databases.yaml` —— 填入三个数据库的连接信息(**强烈建议使用只读账号!**)

### 2b. 平台 PostgreSQL(多企业租户,Phase 0)

运维先建空库 `mcp_platform` 与应用账号 `mcp_app`(见 `docs/MCP多企业租户升级需求与Cursor提问手册.md` §4.8.3),再在 `.env` 配置:

```env
PLATFORM_DATABASE_URL=postgresql://mcp_app:密码@127.0.0.1:5432/mcp_platform
PLATFORM_SECRET_KEY=<openssl rand -hex 32>
```

**表结构由 Alembic 自动创建**(勿手工执行 §4.2 SQL):

```powershell
.\.venv\Scripts\pip.exe install -r requirements.txt
.\.venv\Scripts\python.exe scripts\platform_db_init.py
```

验收:`\dt` 应见 `enterprises`、`users`、`database_connections`、`skills`、`agents`、`mcp_instances`、`audit_logs` 及 `alembic_version`。

> Phase 0 阶段 yaml/json 配置仍并行可用;Web 启动时若已配置 `PLATFORM_DATABASE_URL` 会校验连通与 schema 版本。

### 2c. 登录与初始超管(Phase 1)

在 `.env` 增加(首次部署必填 `BOOTSTRAP_ADMIN_PASSWORD`):

```env
BOOTSTRAP_ADMIN_USER=admin
BOOTSTRAP_ADMIN_PASSWORD=你的强密码
```

启动 Web 后,若 `users` 表为空会自动创建 `platform_admin`。浏览器打开首页即显示登录页;侧栏底部可查看当前用户与登出。

用户/企业数据全部在 PostgreSQL;`/api/databases` 等配置接口在 Phase 2 前仍读 yaml,但已要求 JWT 登录。

### 2d. 配置迁入 PostgreSQL(Phase 2)

1. 升级 schema:`python scripts/platform_db_init.py`(含 migration 002)
2. 导入遗留配置:

```powershell
.\.venv\Scripts\python.exe scripts\migrate_files_to_platform_pg.py --dry-run
.\.venv\Scripts\python.exe scripts\migrate_files_to_platform_pg.py `
  --default-enterprise-code default --default-enterprise-name 默认企业
```

3. 平台管理员在顶栏切换「当前管理企业」;API 通过 `X-Enterprise-Id` 隔离租户。
4. 回退只读文件模式:`.env` 设置 `LEGACY_FILE_CONFIG=1`(不写回 yaml/json)。

### 3. 跑测试(可选)

```powershell
.\.venv\Scripts\python.exe -m pytest tests/ -v
```

### 4. 启动 Web 服务

```powershell
.\.venv\Scripts\python.exe -m uvicorn src.webapp.main:app --host 127.0.0.1 --port 8765 --reload
```

打开浏览器访问:<http://127.0.0.1:8765>

### 5. (可选)MCP Server

#### 5a. Cursor / Claude Desktop(stdio,默认)

在 Cursor 的 MCP 设置里加:

```json
{
  "mcpServers": {
    "db-qa": {
      "command": "G:/phpstudy_pro/WWW/10.mcp/.venv/Scripts/python.exe",
      "args": ["-m", "src.mcp_server.server"],
      "cwd": "G:/phpstudy_pro/WWW/10.mcp"
    }
  }
}
```

之后在 Cursor 里就能直接 `@db-qa` 调用以下工具:

- `list_databases` —— 列所有可用数据库
- `list_tables` —— 列某个库的所有表
- `describe_table` —— 查看表结构
- `search_schema` —— 按关键词搜索表/字段
- `execute_query` —— 执行 SELECT(带安全校验)

> 不设置 `MCP_TRANSPORT` 时默认为 **stdio**,与上述 Cursor 配置兼容。

#### 5b. Dify / HTTP 客户端(streamable-http)

Dify 等需要 **MCP HTTP** 传输时,启动现行 8001 或 8002 实例(与 Web 8765 互不冲突):

```powershell
cd G:\phpstudy_pro\WWW\10.mcp
.\scripts\start-mcp-8001-local-test.ps1
# 或:.\scripts\start-mcp-8002-zh-data-dm-only.ps1 -InstanceId <8002实例UUID>
```

端点:`http://127.0.0.1:8001/mcp`、`http://127.0.0.1:8002/mcp`

- Dify 在本机:URL 填 `http://127.0.0.1:8001/mcp` 或 `http://127.0.0.1:8002/mcp`
- Dify 在 Docker(Windows/Mac):将主机改为 `host.docker.internal`

详见 `docs/Dify+MCP问数方案分析.md`;安装前检查见 `docs/Dify本机安装前检查清单.md`。

#### 5c. 多 Dify 应用数据库隔离(多 MCP 实例)

不同 Dify 应用连接不同端口的 MCP 进程。生产实例由 Web 平台生成独立
`data/instances/<UUID>.env` 和 `databases.fragment.yaml`,工具与数据源由
`tool_profile` 控制:

```powershell
# 首次执行:创建隔离的本地测试企业并复制数据库连接(正式企业不修改)
.\.venv\Scripts\python.exe scripts\local\provision_local_test_enterprise.py --pg-host 192.168.1.185

# 8001:只从 zhenhai-local-test 企业渲染配置,含 26 个工具
.\scripts\start-mcp-8001-local-test.ps1 -EnterpriseCode zhenhai-local-test

# 8002:zh_data_dm_only,25 个工具,可见 alias 只能是 ZH_DATA + ZH_DWD
.\scripts\start-mcp-8002-zh-data-dm-only.ps1 -InstanceId <8002实例UUID>
```

- 当前只允许 `full`(8001)和 `zh_data_dm_only`(8002)两个 Profile。
- 本地数据库管理固定选择 `zhenhai-local-test`;修改同名 alias 不会改动正式 `zhenhai` 企业的连接。
- 8001 的许可、环评、验收、镇海空气等主题按固定路由读取达梦;其它现行 PG 主题使用本地测试企业配置的内网地址。
- `.env.local`、`scripts/local/`、`data/local-test/` 和本地 8001 渲染/启动脚本均被发布检查拒绝,不能进入正式部署包。
- 8002 禁止访问 PostgreSQL,不在空结果、SQL 错误或超时后自动切库。
- `list_databases` 只返回白名单内 alias;其它工具对越权 `db_alias` 会拒绝。
- 8001 和 8002 分别由独立进程监听。

**服务器重启后**:见 `docs/服务启动说明.md`。8002 的 Dify 提示词见
`docs/Dify-8002-纯达梦zh_data_dm_only-系统提示词.txt`。

---

## 安全策略

| 层级 | 防护措施 |
|---|---|
| 数据库账号 | 强烈建议**只读账号**(最强保险) |
| SQL 校验 | AST 解析 + 关键字粗筛 + 黑名单表 |
| 拦截类型 | INSERT / UPDATE / DELETE / DROP / ALTER / TRUNCATE / CREATE / GRANT / 多语句 / SLEEP / OUTFILE |
| 资源限制 | 强制注入 `LIMIT`、设置 `SQL_TIMEOUT`、`SQL_MAX_ROWS` |
| 审计 | 所有问答和 SQL 落到 `logs/audit-YYYY-MM-DD.log` |

测试:

```powershell
.\.venv\Scripts\python.exe -m pytest tests/ -v
# 已覆盖 20 个用例:合法 SELECT 通过 / 各种 DML/DDL/多语句/危险函数 全部拦截
```

---

## 提升 SQL 准确率的关键

LLM 能不能把 _"固废重量排名前十的企业"_ 翻译成正确的 SQL,**取决于它能不能从你的库里看出业务关系**。
有三个杠杆按效果从高到低:

1. **数据库表/字段必须有中文 `COMMENT`**(最重要!)
   - MySQL:`ALTER TABLE t_solid_waste MODIFY weight DECIMAL(10,2) COMMENT '固废重量(吨)';`
   - 达梦:`COMMENT ON COLUMN t_solid_waste.weight IS '固废重量(吨)';`

2. **维护 `config/glossary.yaml` 的业务术语表 + Few-shot 样例**
   - 把行业黑话("固废 = t_solid_waste")告诉 LLM
   - 把典型问答 SQL 当作示例写进去

3. **后续可加的优化**(待实现)
   - 表结构向量化检索(RAG):表多了之后,把全部 schema 塞 prompt 会爆 token
   - 错误反馈循环:LLM 生成的 SQL 报错时,把错误信息再喂回去让它重试

---

## 常见问题

**Q1:能不能加增、删、改?**
A:故意不加。LLM 偶尔会"幻觉",写错条件可能批量删数据。如果业务确实需要,建议走"生成 SQL → 人工确认 → 单独走业务后台执行"的流程。

**Q2:phpStudy 的 MySQL 默认账号是 root,怎么建只读账号?**

```sql
CREATE USER 'mcp_readonly'@'localhost' IDENTIFIED BY 'a-strong-password';
GRANT SELECT ON your_db.* TO 'mcp_readonly'@'localhost';
FLUSH PRIVILEGES;
```

**Q3:DeepSeek 怎么收费?**
A:deepseek-chat 模型按 token 计费,输入约 ¥0.001/千 token,输出 ¥0.002/千 token。
一次问答(schema + 问题 + 回答)通常 1~3 千 token,单次成本 < ¥0.005。

**Q4:可以换成本地 Ollama 模型吗?**
A:可以。改 `.env` 的 `DEEPSEEK_BASE_URL` 指向 Ollama 的 OpenAI 兼容端点(如 `http://localhost:11434/v1`),`DEEPSEEK_MODEL` 改成本地模型名(如 `qwen2.5:14b`)。

---

## 后续路线图

- [ ] RAG 向量检索:先用 bge-m3 嵌入表结构,提问时只取 Top-K 相关表
- [ ] SQL 报错自愈:执行失败后把 error 反馈给 LLM 重试一次
- [ ] 多轮对话上下文:把上一次的 SQL/结果带入下一轮
- [ ] 角色权限:不同账号能看到不同的库/表
- [ ] 图表可视化:检测到聚合查询时自动出柱状/折线图