Skip to main content
Glama
JY-Zee

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