Skip to main content
Glama
zhouruoye

archery-mcp

by zhouruoye
README.md
# archery-mcp

通过 Archery 网关安全查询 MySQL / Redis / MongoDB 的 MCP Server,支持性能诊断。

## 数据访问路径

```
MCP Client (Trae/Cursor/Claude Code) -> archery-mcp (HTTP /mcp) -> Archery Web -> Database
```

本项目不直连数据库,所有查询都通过 Archery 的 `/query/` 接口执行。

## 功能特性

- 支持 MySQL / Redis / MongoDB 三种数据库查询
- 只读安全策略:MySQL 仅允许 `SELECT/SHOW/EXPLAIN`,阻塞写、DDL、多语句、会话操作和文件导出
- 自动补 LIMIT:缺 LIMIT 的 SELECT 自动追加
- 预定义性能诊断集:MySQL 8 项 / Redis 9 项 / MongoDB 7 项
- 模拟登录:自动处理 CSRF + Cookie,session 缓存 1 小时,失效自动重登
- HTTP `/mcp` 接口:支持多会话隔离(`Mcp-Session-Id`),可选 Bearer 鉴权
- 错误信息脱敏:含 `password/cookie/csrf/session/token` 的消息自动替换

## 环境要求

- Node.js `>= 20`
- npm
- 可访问目标 Archery 网关

## 安装与运行

```bash
git clone <repo-url> archery-mcp
cd archery-mcp
npm install
npm run build
```

支持两种传输模式:**stdio**(默认,推荐)和 **HTTP**。

### stdio 模式(默认)

客户端(Trae/Cursor/Claude Code)自动拉起子进程,所有配置写在 `mcpServers` 里,无需单独启动 server。

```bash
node dist/cli.js --archery-url https://archery.example.com --username your-user --password your-password
```

或用环境变量:

```bash
export ARCHERY_BASE_URL=https://archery.example.com
export ARCHERY_LOGIN_USERNAME=your-user
export ARCHERY_LOGIN_PASSWORD=your-password
node dist/cli.js
```

stdio 模式下凭证不暴露在进程列表(`ps aux`)里,相对安全。

### HTTP 模式

适合多客户端共享同一个 server,需要单独启动:

```bash
export ARCHERY_LOGIN_USERNAME="your-archery-user"
export ARCHERY_LOGIN_PASSWORD="your-archery-password"
node dist/cli.js --transport http --port 8080
```

默认监听 `127.0.0.1:8080`。如需开放给内网,显式指定 host:

```bash
node dist/cli.js --transport http --host 0.0.0.0 --port 8080
```

## 环境变量

| 变量 | 必需 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `ARCHERY_BASE_URL` | **是** | - | Archery 网关地址 |
| `ARCHERY_LOGIN_USERNAME` | **是** | - | Archery 登录用户名 |
| `ARCHERY_LOGIN_PASSWORD` | **是** | - | Archery 登录密码 |
| `ARCHERY_QUERY_PATH` | 否 | `/query/` | Archery 查询接口路径 |
| `ARCHERY_QUERY_DEFAULT_LIMIT` | 否 | `100` | 缺省 LIMIT |
| `ARCHERY_QUERY_MAX_LIMIT` | 否 | `500` | 最大 LIMIT |
| `ARCHERY_QUERY_ALLOWED_INSTANCES` | 否 | 空 | 实例白名单,逗号分隔 |
| `ARCHERY_QUERY_ALLOWED_DATABASES` | 否 | 空 | 数据库白名单 |
| `ARCHERY_QUERY_BLOCKED_TABLES` | 否 | 空 | 阻塞表名片段 |
| `ARCHERY_MCP_HTTP_HOST` | 否 | `127.0.0.1` | HTTP 监听地址(HTTP 模式) |
| `ARCHERY_MCP_HTTP_PORT` | 否 | `8080` | HTTP 监听端口(HTTP 模式) |
| `ARCHERY_MCP_HTTP_API_KEY` | 否 | 空 | `/mcp` Bearer 鉴权 token(HTTP 模式) |
| `ARCHERY_BLOCKED_URLS` | 否 | 空 | **黑名单**:禁止访问的 Archery 地址,逗号分隔 |
| `ARCHERY_BLOCKED_USERNAMES` | 否 | 空 | **黑名单**:禁止使用的 Archery 用户名,逗号分隔 |
| `ARCHERY_BLACKLIST_FILE` | 否 | 空 | **黑名单**:JSON 文件路径,未设置时自动查找 `./blacklist.json` |

CLI 参数会覆盖同名环境变量。完整参数列表见 `node dist/cli.js --help`。

### 黑名单配置

用于禁止特定 Archery 地址或用户名访问本服务。启动时会校验,命中黑名单则拒绝启动:

```bash
# 禁止访问生产环境 Archery
export ARCHERY_BLOCKED_URLS="https://archery-prod.example.com,https://archery-prod2.example.com"

# 禁止某些用户名
export ARCHERY_BLOCKED_USERNAMES="admin,root,sa"

# 然后正常启动
node dist/cli.js
```

#### 从文件读取(推荐用于较长黑名单)

当黑名单条目较多时,可改用 JSON 文件维护。文件查找优先级:

1. **CLI 参数** `--blacklist-file <path>`(最高)
2. **环境变量** `ARCHERY_BLACKLIST_FILE`
3. **自动发现**:当前工作目录(项目根)下的 `./blacklist.json`(最低)

```json
// blacklist.json
{
  "urls": [
    "https://archery-prod.example.com",
    "https://archery-prod2.example.com/"
  ],
  "usernames": ["admin", "root", "sa"]
}
```

```bash
# 方式一:在项目根放置 blacklist.json,自动发现(无需任何参数)
node dist/cli.js

# 方式二:环境变量指定路径
export ARCHERY_BLACKLIST_FILE="/path/to/blacklist.json"
node dist/cli.js

# 方式三:CLI 参数(覆盖环境变量与自动发现)
node dist/cli.js --blacklist-file /path/to/blacklist.json
```

说明:
- `urls` 与 `usernames` 均为可选字段,缺失视为空数组;文件中未知字段会被忽略。
- **自动发现的 `./blacklist.json` 是可选的**:不存在则忽略(不报错),存在则必须为合法 JSON,否则启动失败(fail-fast)。
- **显式指定的路径**(CLI/env)必须存在,否则启动失败。
- **文件与 `ARCHERY_BLOCKED_URLS` / `ARCHERY_BLOCKED_USERNAMES` 环境变量会合并去重**(非互斥)。典型用法:文件维护长期黑名单,环境变量临时追加屏蔽项。
- 下方「匹配规则」对两种来源都生效。
- 文件读取/解析失败会直接拒绝启动(fail-fast),错误信息会包含文件路径。

匹配规则:
- **URL**:不区分大小写,自动忽略末尾 `/`,精确匹配
- **用户名**:不区分大小写,精确匹配
- 两者命中任一即拒绝

## 客户端配置示例

### stdio 模式(推荐)

#### Trae / Cursor / Claude Code

```json
{
  "mcpServers": {
    "archery": {
      "command": "node",
      "args": [
        "/absolute/path/to/archery-mcp/dist/cli.js"
      ],
      "env": {
        "ARCHERY_BASE_URL": "https://archery.example.com",
        "ARCHERY_LOGIN_USERNAME": "your-archery-user",
        "ARCHERY_LOGIN_PASSWORD": "your-archery-password"
      }
    }
  }
}
```

或用 CLI 参数传凭证(更直观,但密码会出现在进程列表):

```json
{
  "mcpServers": {
    "archery": {
      "command": "node",
      "args": [
        "/absolute/path/to/archery-mcp/dist/cli.js",
        "--archery-url", "https://archery.example.com",
        "--username", "your-archery-user",
        "--password", "your-archery-password"
      ]
    }
  }
}
```

stdio 模式不需要 `--transport` 参数(默认就是 stdio)。凭证只在该子进程内可见,不会污染全局环境。

### HTTP 模式

#### Trae / Cursor

```json
{
  "mcpServers": {
    "archery": {
      "type": "http",
      "url": "http://localhost:8080/mcp",
      "headers": {
        "Authorization": "Bearer your-api-key"
      }
    }
  }
}
```

若未配置 `ARCHERY_MCP_HTTP_API_KEY`,可省略 `headers`。

### curl 调试

```bash
# 1. initialize
curl -i -X POST http://localhost:8080/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

# 从响应头取 mcp-session-id
SID="..."

# 2. notifications/initialized
curl -X POST http://localhost:8080/mcp \
  -H "mcp-session-id: $SID" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

# 3. tools/list
curl -X POST http://localhost:8080/mcp \
  -H "mcp-session-id: $SID" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# 4. tools/call
curl -X POST http://localhost:8080/mcp \
  -H "mcp-session-id: $SID" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"archery_run_diagnostic","arguments":{"instanceName":"prod-mysql","dbName":"mydb","diagnosticKey":"processlist"}}}'
```

## MCP 工具

| 工具 | 说明 |
| --- | --- |
| `archery_list_instances` | 列出 Archery 注册的实例,可选 `dbType` 过滤 |
| `archery_list_databases` | 列出某实例下的所有数据库 |
| `archery_execute_query` | 执行只读 SQL(MySQL)或诊断命令(Redis/MongoDB),自动校验和补 LIMIT |
| `archery_describe_table` | 读取表结构元数据 |
| `archery_query_history` | 搜索查询历史摘要 |
| `archery_run_diagnostic` | 运行预定义性能诊断命令 |

### `archery_execute_query`

```json
{
  "instanceName": "prod-mysql",
  "dbName": "mydb",
  "sql": "SELECT * FROM users WHERE created_at > '2026-01-01'",
  "limitNum": 100
}
```

### `archery_run_diagnostic`

```json
{
  "instanceName": "prod-redis",
  "dbName": "0",
  "diagnosticKey": "slowlog"
}
```

带额外参数(MongoDB 集合诊断):

```json
{
  "instanceName": "prod-mongo",
  "dbName": "mydb",
  "diagnosticKey": "collection_stats",
  "extraParams": { "collectionName": "users" }
}
```

## 支持的诊断命令

### MySQL

| key | 说明 |
| --- | --- |
| `processlist` | 当前所有连接和正在执行的 SQL |
| `status` | 全局状态计数器 |
| `variables` | 全局变量(配置) |
| `innodb_status` | InnoDB 引擎状态(锁、死锁、事务) |
| `slow_queries` | 最近 50 条慢查询 |
| `table_sizes` | 按 data_length 排序的前 50 大表 |
| `index_usage` | 按 read 次数排序的前 50 个索引 |
| `long_running` | 运行超过 60 秒的查询 |
| `locks` | 当前 InnoDB 行锁和等待事务 |

### Redis

> **注意**:Archery 的 Redis 引擎有 `safe_cmd` 白名单,只允许查询类命令。`SLOWLOG`、`CLIENT LIST`、`CONFIG GET` 等性能诊断命令会被 Archery 拒绝(返回"禁止执行该命令!")。因此 Redis 诊断以 key 查询为主。

| key | 说明 | 需要参数 |
| --- | --- | --- |
| `info` | Redis INFO 输出(部分 Archery 版本可能禁用) | - |
| `scan` | 扫描当前 DB 的 key(前 100 个) | - |
| `key_type` | 查看指定 key 的类型 | `keyName` |
| `key_ttl` | 查看指定 key 的 TTL(秒) | `keyName` |
| `key_exists` | 检查 key 是否存在 | `keyName` |
| `string_get` | 获取 string 类型 key 的值 | `keyName` |
| `string_strlen` | 获取 string 类型 key 的值长度 | `keyName` |
| `hash_getall` | 获取 hash key 的所有字段和值 | `keyName` |
| `hash_len` | 获取 hash key 的字段数量 | `keyName` |
| `list_len` | 获取 list key 的长度 | `keyName` |
| `list_range` | 获取 list key 的前 50 个元素 | `keyName` |
| `set_members` | 获取 set key 的所有成员 | `keyName` |
| `set_scard` | 获取 set key 的成员数量 | `keyName` |
| `zset_range` | 获取 zset key 的前 50 个成员(带 score) | `keyName` |
| `zset_zcard` | 获取 zset key 的成员数量 | `keyName` |

### MongoDB

| key | 说明 |
| --- | --- |
| `server_status` | 服务器状态概览 |
| `current_ops` | 当前活跃操作 |
| `slow_queries` | system.profile 中最近 50 条慢查询 |
| `connections` | 连接统计 |
| `collection_stats` | 指定 collection 的统计(需 `extraParams.collectionName`) |
| `index_stats` | 指定 collection 的索引使用统计(需 `extraParams.collectionName`) |
| `db_stats` | 数据库级别统计 |

## 安全说明

- **只读**:MySQL 仅允许 `SELECT/SHOW/EXPLAIN`,Redis 阻塞 `CONFIG SET`,MongoDB 阻塞 `insert/update/remove/drop` 等写操作
- **凭证安全**:username/password 只从环境变量读取,不写日志、不返回给 AI
- **错误脱敏**:含敏感关键词的 Archery 错误消息会被替换为通用提示
- **会话隔离**:HTTP `/mcp` 每个 `Mcp-Session-Id` 独立,session cookie 进程内存缓存 1 小时
- **默认本机**:HTTP 默认监听 `127.0.0.1`,仅当显式 `--host 0.0.0.0` 时开放外网;建议配合 `ARCHERY_MCP_HTTP_API_KEY` 使用

## 开发

```bash
npm run typecheck     # 类型检查
npm run build         # 构建
npm test              # 运行单元测试
npm run start:http    # 启动 HTTP 服务
```

目录结构:

```
src/
├── archeryClient.ts       # Archery HTTP 客户端
├── archeryForms.ts        # 表单构造
├── archeryHttpSupport.ts  # CookieJar、CSRF 提取
├── archeryMappers.ts      # 响应映射
├── archerySession.ts      # 模拟登录
├── cli.ts                 # CLI 入口
├── config.ts              # 环境变量加载
├── diagnostics.ts         # 诊断命令集
├── httpServer.ts          # HTTP /mcp 服务
├── mcpServer.ts           # MCP 工具注册
├── policy.ts              # 只读 SQL 策略
├── services.ts           # 依赖容器
└── tools.ts               # 工具 schema + 分发
```

## License

MIT