database-analysis-mcp
by JY-Zee
README.md
# database-analysis-mcp
基于 [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) 的 MySQL 数据库分析工具服务器,支持 stdio 和 SSE 两种传输模式。
## 技术栈
- **运行时**: Node.js >= 18
- **语言**: TypeScript
- **MCP SDK**: @modelcontextprotocol/sdk
- **数据库**: mysql2 (连接池)
- **参数校验**: zod v4
- **日志**: pino (stderr + 文件)
- **构建**: tsup
- **包管理**: pnpm
## 功能
### 连接管理
| 工具 | 说明 |
|------|------|
| `db_connect` | 连接到配置中的数据库,默认连接 `default` |
| `db_disconnect` | 断开指定数据库连接 |
| `db_status` | 查看所有数据库连接状态(已配置/已连接/当前活动) |
| `db_switch` | 切换当前活动的数据库连接 |
| `db_refresh_schema` | 清空表结构缓存并重新加载,表结构变更后需调用此工具刷新 |
### 查询执行
| 工具 | 说明 |
|------|------|
| `execute_query` | 执行 SQL 查询,支持参数化查询、`preview_only` 先预览 SQL 再确认执行,内置危险操作拦截(DROP DATABASE / TRUNCATE) |
### 结构分析
| 工具 | 说明 |
|------|------|
| `list_tables` | 列出当前数据库所有表(含注释、引擎、预估行数) |
| `describe_table` | 获取表的完整结构:列信息、索引、外键 |
### 数据统计
| 工具 | 说明 |
|------|------|
| `table_statistics` | 获取表/列级统计:总行数、去重数、空值数、最大最小值、平均值、数据大小 |
### 性能分析
| 工具 | 说明 |
|------|------|
| `slow_query_analysis` | 分析慢查询,支持 slow_log 表和 performance_schema 两种来源 |
| `explain_query` | 执行 EXPLAIN 查看查询计划,支持 traditional / json / tree 格式 |
### ER 图生成
| 工具 | 说明 |
|------|------|
| `generate_er_diagram` | 生成 Mermaid 格式的 ER 关系图,可指定表范围,自动识别外键关系 |
## 安装
```bash
pnpm install
```
## 配置
编辑 `config/default.json` 填入数据库连接信息:
```json
{
"server": {
"name": "database-analysis-mcp",
"version": "1.0.0",
"sse": {
"port": 3000,
"host": "localhost"
}
},
"databases": {
"default": {
"host": "localhost",
"port": 3306,
"user": "root",
"password": "your_password",
"database": "your_database"
},
"production": {
"host": "prod-host",
"port": 3306,
"user": "readonly",
"password": "xxx",
"database": "prod_db"
}
},
"logging": {
"level": "info",
"dir": "./logs"
},
"autoConnect": "default",
"preloadSchema": true
}
```
支持通过 `--config` 参数指定自定义配置文件,会与 `default.json` 深度合并:
```bash
pnpm dev -- --config config/local.json
```
**通过 mcp.json 配置数据库(环境变量)**:
当使用 **command + env** 方式在 Cursor 的 mcp.json 中启动本 MCP 时,可在 `env` 中直接填写数据库连接参数,无需修改项目内 `config/default.json`。环境变量会覆盖 `databases.default` 的对应字段。
| 环境变量 | 含义 | 示例 |
|----------|------|------|
| `DB_HOST` | 主机 | `192.168.1.1(etc)` |
| `DB_PORT` | 端口 | `3306` |
| `DB_USER` | 用户 | `XXX` |
| `DB_PASSWORD` | 密码 | `XXX` |
| `DB_NAME` | 数据库名 | `XXXX` |
示例(mcp.json,stdio 模式):
```json
{
"mcpServers": {
"database-analysis-mcp": {
"command": "node",
"args": ["d:/demo/database-analysis-mcp/dist/index.js", "--transport", "stdio"],
"env": {
"DB_HOST": "192.168.1.1(etc)",
"DB_PORT": "3306",
"DB_USER": "XXX",
"DB_PASSWORD": "XXX",
"DB_NAME": "XXXX"
}
}
}
}
```
说明:
- 仅当至少设置一个上述环境变量时才会覆盖 `default`;未设置的字段保留配置文件中的值。
- **URL 模式**(`"url": "http://localhost:3000/sse"`)下 Cursor 不启动 MCP 进程,无法通过 mcp.json 的 `env` 传参,数据库配置仍需在 `config/default.json` 或启动 MCP 时的环境变量中配置。
**启动优化配置**:
| 配置项 | 说明 |
|--------|------|
| `autoConnect` | 启动时自动连接的数据库名,`true` 表示 `default`;不配置则需手动调用 `db_connect` |
| `preloadSchema` | 连接成功后是否预加载表结构到缓存,`list_tables` / `describe_table` 命中缓存时无 DB 往返,默认 `true` |
**MCP 描述与触发(何时调用本 MCP)**:
本 MCP 支持两类描述,便于用户在 Cursor 中说「查询数据库 XXX」等话时被正确调用:
1. **服务器级 instructions**:通过 MCP 协议发给 Cursor 等客户端,说明「在什么情况下应使用本 MCP」。不配置时使用内置默认说明。
2. **工具级 description**:每个工具都有描述,部分工具已加入「查询数据库」「查表」「表结构」等触发语,便于模型选择对应工具。
**默认触发语**(内置 instructions 与工具描述中已包含):
查询数据库、查一下数据库、查库、执行 SQL、查表、查表数据、有哪些表、表结构、表有哪些字段、分析表、统计数据、慢查询、ER 图。
**自定义说明(可选)**:在 `config/default.json` 的 `server` 中增加 `instructions` 可覆盖默认说明,例如:
```json
{
"server": {
"name": "database-analysis-mcp",
"version": "1.0.0",
"sse": { "port": 3000, "host": "localhost" },
"instructions": "当用户要查询数据库、执行 SQL、查表、分析数据时,请使用本 MCP 的工具。"
}
}
```
| 配置项 | 说明 |
|--------|------|
| `server.instructions` | 可选。服务器使用说明,会发给 MCP 客户端。不配置则使用内置默认说明(含上述触发语)。 |
## 使用
### 开发模式
```bash
# SSE 模式(默认,常驻进程)
pnpm dev
# stdio 模式
pnpm dev:stdio
# 显式指定 SSE
pnpm dev:sse
```
### 生产模式
```bash
pnpm build
pnpm start # SSE(默认)
pnpm start:stdio # stdio
pnpm start:sse # SSE
```
### Windows 控制台中文乱码
Windows 控制台默认使用 GBK,与 Node 输出的 UTF-8 不匹配会导致中文乱码。项目已通过 `scripts/launcher.js` 在启动时自动执行 `chcp 65001` 切换为 UTF-8,使用 `pnpm dev` / `pnpm start` 即可正常显示。
若直接运行 `node dist/index.js` 仍乱码,可先执行:
```powershell
chcp 65001
```
### 在 Cursor 中配置
在 `.cursor/mcp.json` 中添加:
**生产模式(需先 `pnpm build`):**
```json
{
"mcpServers": {
"database-analysis-mcp": {
"command": "node",
"args": ["dist/index.js"],
"cwd": "/path/to/database-analysis-mcp"
}
}
}
```
**开发模式:**
```json
{
"mcpServers": {
"database-analysis-mcp": {
"command": "npx",
"args": ["tsx", "src/index.ts"],
"cwd": "/path/to/database-analysis-mcp"
}
}
}
```
**SSE 模式:**
先启动服务 `pnpm dev:sse`,然后配置:
```json
{
"mcpServers": {
"database-analysis-mcp": {
"url": "http://localhost:3000/sse"
}
}
}
```
### 在其他窗口/项目中使用时出现「没有 tools 子目录」的说明
通过 URL(如 `"url": "http://localhost:3000/sse"`)连接本 MCP 时,Cursor 会在**当前项目**下为该 MCP 创建 `mcps/user-database-analysis-mcp/`,但通常只包含 `SERVER_METADATA.json` 和 `INSTRUCTIONS.md`,**不会**自动生成 `tools/` 下的工具描述 JSON(工具由 MCP 服务器在运行时注册)。若依赖「从项目文件读取工具描述」的流程,就会报错:*只有 SERVER_METADATA.json 和 INSTRUCTIONS.md,没有 tools 子目录或任何工具描述 JSON*。
**解决办法**:本仓库已包含完整的工具描述符,位于 **`mcps/user-database-analysis-mcp/`**(含 `tools/*.json`)。在**其他项目/窗口**中使用本 MCP 时,请将该目录复制到该项目的 MCP 描述符根目录下(即该项目中 Cursor 使用的 `mcps` 所在位置,一般为该项目的 `.cursor` 或工作区下的 `mcps`),使该窗口下存在 `mcps/user-database-analysis-mcp/tools/` 及所有工具 JSON。复制后无需改 URL 配置,MCP 仍通过 `http://localhost:3000/sse` 连接,只是工具描述从本仓库提供的文件中读取。
**本仓库中的 mcps 结构**(可直接复制整目录):
```
mcps/user-database-analysis-mcp/
├── SERVER_METADATA.json
├── INSTRUCTIONS.md
└── tools/
├── db_connect.json
├── db_disconnect.json
├── db_status.json
├── db_switch.json
├── db_refresh_schema.json
├── execute_query.json
├── list_tables.json
├── describe_table.json
├── table_statistics.json
├── slow_query_analysis.json
├── explain_query.json
└── generate_er_diagram.json
```
### 故障排除
#### Cursor 中关闭再开启 MCP 导致命令行服务退出
**现象**:在 Cursor 的 MCP 页面关闭该 MCP 再重新开启后,命令行里通过 `pnpm dev:sse` 启动的进程报错并退出:
```
Error: Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection.
```
**原因**:MCP SDK 规定一个 `McpServer` 实例只能 `connect` 一个 transport。原先实现里,所有 GET `/sse` 请求共用一个 server;当 Cursor 关闭再打开 MCP 时会发起新的 GET `/sse`,再次对同一 server 执行 `connect` 就会触发上述错误。未捕获的异常导致 Node 进程退出,表现为“命令行里的服务被关掉”。
**解决方案**(已实现):SSE 模式下改为**每个 GET `/sse` 连接使用独立的 McpServer 实例**,共享同一个 `DatabaseManager`(数据库连接仍由单例管理)。这样 Cursor 重新连接时会新建 server + transport,不再报错,命令行进程也不会因异常退出。
#### 对话框发起查询时 MCP 无法连接 / 工具列表不显示
**现象**:在 Cursor 对话框里发起「查询 XXX」等需求时,无法正常使用 MCP;界面显示 MCP 已连接,但工具列表不再显示,对话无法继续自动执行。只有手动关闭该 MCP 再重新启用后,工具列表才恢复,对话才能继续。
**原因**:
1. **SSE 连接断开后未清理**:客户端(如 Cursor)与 MCP 的 SSE 连接因超时、网络或内部重连等原因断开后,服务端仍保留对旧 `sseTransport` 的引用,后续 POST `/messages` 请求仍发往已关闭的连接,导致无响应或异常,Cursor 端表现为「已连接但工具不可用」。
2. **POST 处理未捕获异常**:`handlePostMessage` 若抛出未捕获异常(例如连接已关闭时 SDK 报错),会导致连接状态异常,加重「假连接」现象。
**解决方案**(已实现,见 `src/server/transports.ts`):
1. **连接关闭时清除 transport**:在 GET `/sse` 建立连接后,对响应对象 `res` 监听 `close` 和 `error`;在回调中若当前全局 `sseTransport` 仍是本次连接的 transport,则将其置为 `null` 并打日志。这样连接断开后不再向旧连接写数据,Cursor 下次重连(再次 GET `/sse`)会重新建立连接并正常列出工具。
2. **POST `/messages` 异常处理**:对 `sseTransport.handlePostMessage(req, res)` 做 `try/catch`;发生异常时记录日志、将 `sseTransport` 置为 `null`,并在响应仍可写时返回 500。避免未处理异常导致连接挂起,同时便于客户端重连。
若问题仍出现,可查看 `logs/mcp-server.log` 中是否有「SSE 客户端已断开」或「POST /messages 处理异常」等日志,便于进一步排查。
## 项目结构
```
database-analysis-mcp/
├── config/
│ └── default.json # 默认配置文件
├── src/
│ ├── index.ts # 入口:解析 CLI 参数,启动服务
│ ├── config/
│ │ ├── types.ts # AppConfig / DatabaseConfig 等类型
│ │ └── index.ts # 配置加载与深度合并
│ ├── utils/
│ │ ├── logger.ts # pino 日志(stderr + 文件输出)
│ │ ├── errors.ts # 5 种自定义业务错误类
│ │ └── index.ts
│ ├── database/
│ │ ├── types.ts # 表结构 / 统计 / 慢查询 / ER 图类型
│ │ ├── manager.ts # MySQL 多连接池管理器
│ │ ├── schema-cache.ts # 表结构缓存(预加载 / 刷新)
│ │ ├── schema-utils.ts # schema 查询可复用函数
│ │ └── index.ts
│ ├── tools/
│ │ ├── types.ts # ToolDefinition + defineTool 辅助函数
│ │ ├── connection.ts # 连接管理 (5 个工具)
│ │ ├── query.ts # SQL 查询 (1 个工具)
│ │ ├── schema.ts # 表结构分析 (2 个工具)
│ │ ├── statistics.ts # 数据统计 (1 个工具)
│ │ ├── slow-query.ts # 慢查询分析 (2 个工具)
│ │ ├── er-diagram.ts # ER 图生成 (1 个工具)
│ │ └── index.ts # 聚合注册所有工具
│ └── server/
│ ├── mcp-server.ts # McpServer 创建与工具注册
│ ├── transports.ts # stdio / SSE 双传输层
│ └── index.ts
├── package.json
├── tsconfig.json
└── .gitignore
```
## 日志
日志同时输出到 **stderr** 和 **文件**:
- 文件位置:`logs/mcp-server.log`
- stdio 模式下 stdout 被 MCP 协议占用,所有日志通过 stderr 输出,不会干扰通信
- 日志级别可在 `config/default.json` 中配置:`trace | debug | info | warn | error | fatal`
## 扩展工具
在 `src/tools/` 下新建文件,使用 `defineTool` 定义工具:
```typescript
import { z } from 'zod';
import { defineTool, type ToolDefinition } from './types.js';
export const createMyTools = (db: DatabaseManager): ToolDefinition[] => [
defineTool({
name: 'my_tool',
description: '工具描述',
inputSchema: {
param1: z.string().describe('参数说明'),
},
handler: async ({ param1 }) => {
return {
content: [{ type: 'text', text: `结果: ${param1}` }],
};
},
}),
];
```
然后在 `src/tools/index.ts` 的 `createAllTools` 中注册即可。
## License
MIT