npm package: @massapi/redmine-mcp
Description: 一个基于 Node.js 的 Redmine MCP 服务，使用 `stdio` 作为传输层，适合被支持 MCP 的桌面客户端、CLI 或编辑器直接拉起。
Latest version: 1.0.2

# redmine-mcp

一个基于 Node.js 的 Redmine MCP 服务，使用 `stdio` 作为传输层，适合被支持 MCP 的桌面客户端、CLI 或编辑器直接拉起。

当前实现聚焦 **只读查询**，把 Redmine REST API 暴露为 4 个 MCP tools，方便大模型在不直接操作 Redmine 界面的情况下查询项目和问题信息。

## 功能概览

- 基于 `@modelcontextprotocol/sdk` 实现 MCP Server
- 使用 `stdio` 传输，适合本地命令方式接入
- 只提供只读工具，不包含创建、编辑、关闭 Issue 等写操作
- 支持项目列表查询、问题列表查询、单个问题详情查询
- 支持按项目和时间段直接汇总 issue summary，减少上下文消耗
- 自动为项目和问题补充可直接打开的 `link` 字段
- 查询单个 Issue 时始终附带 `journals`
- 从 `journals` 中提取 `progress_items`，便于模型总结问题进展
- 提供结构化 JSON 日志，日志级别可配置
- 支持通过 `REDMINE_TLS_INSECURE=true` 忽略 TLS 证书校验

## 已实现的 MCP Tools

### 1. `redmine_list_projects`

分页查询 Redmine 项目。

支持参数：

- `limit`：返回数量，默认 `100`，最大 `200`
- `offset`：分页偏移，默认 `0`
- `include`：可选，支持 Redmine 原生 `include` 参数，例如 `trackers`、`issue_categories`、`enabled_modules`

返回结果包含：

- `total_count`
- `offset`
- `limit`
- `projects`

每个项目会额外补充：

- `link`：项目页面链接
- `parent.link`：父项目链接（如果存在父项目）

### 2. `redmine_list_issues`

按条件分页查询问题列表。

支持参数：

- `limit`：返回数量，默认 `100`，最大 `200`
- `offset`：分页偏移，默认 `0`
- `project_id`：项目 ID 或项目标识符
- `subproject_id`
- `status_id`：支持 `*` 表示全部状态
- `assigned_to_id`
- `tracker_id`
- `priority_id`
- `author_id`
- `query_id`
- `created_from`：可选，按 `created_on` 过滤的开始日期，`YYYY-MM-DD`，包含当天
- `created_to`：可选，按 `created_on` 过滤的结束日期，`YYYY-MM-DD`，包含当天
- `closed_from`：可选，按 `closed_on` 过滤的开始日期，`YYYY-MM-DD`，包含当天
- `closed_to`：可选，按 `closed_on` 过滤的结束日期，`YYYY-MM-DD`，包含当天
- `sort`：例如 `updated_on:desc`
- `include`：可选 Redmine `include` 参数，但不允许 `attachments`、`children`

返回结果包含：

- `total_count`
- `offset`
- `limit`
- `issues`

每个问题会额外补充：

- `link`：问题页面链接
- `project.link`：所属项目链接（如果存在）
- `progress_items`：从 `journals` 中提取出的进展记录（仅在返回内容中包含可提取信息时出现）

同时会移除以下字段以减少上下文：

- `fixed_version`
- `description`
- `is_private`
- `estimated_hours`
- `total_estimated_hours`
- `spent_hours`

### 3. `redmine_get_issue`

查询单个问题详情。

支持参数：

- `issue_id`：问题 ID，必填

返回结果包含：

- `issue`

其中会额外补充：

- `link`
- `project.link`（如果存在）

同时会做以下裁剪以减少上下文：

- 请求固定只向 Redmine 查询 `journals`
- 去掉 `fixed_version`
- 去掉 `description`
- `journals` 仅保留最后 1 条
- 最后 1 条 `journal.notes` 最多保留 `64` 个字符

### 4. `redmine_summarize_issues`

按项目和时间段直接返回 issue 聚合统计，适合在不展开 issue 明细的情况下获取 summary；`start_date` 和 `end_date` 均可省略，也支持仅传其中一个作为开区间。特别注意：`end_date` 也是包含当天的。

支持参数：

- `project_id`：项目 ID 或项目标识符，必填
- `subproject_id`：可选，子项目过滤
- `start_date`：可选，开始日期，`YYYY-MM-DD`，包含当天
- `end_date`：可选，结束日期，`YYYY-MM-DD`，包含当天，且统计会包含 `end_date` 当天的全部更新/关闭/新建记录

返回结果包含：

- `project`
- `period`
- `summary`
- `issue_type_totals`
- `by_tracker`

统计口径：

- `total_count`：项目当前 issue 总数，不区分时间段
- `open_count`：项目当前打开 issue 总数，不区分时间段
- `done_ratio`：完成率，计算方式为 `(total_count - open_count) / total_count`
- `created_count`：时间范围内新建 issue 数；未传时间时表示全部历史
- `closed_count`：时间范围内关闭 issue 数；未传时间时表示全部历史
- `active_count`：时间范围内 `updated_on` 有更新的 issue 数；未传时间时表示全部历史
- `issue_type_totals`：按 issue 类型（tracker）汇总的总数
- `by_tracker`：按 issue 类型（tracker）分别统计上述全部指标，并额外返回 `created_issues`、`closed_issues`、`active_issues` 三组具体 issue 列表

直接调用示例：

```json
{
  "project_id": "demo-project",
  "start_date": "2026-03-01",
  "end_date": "2026-03-15"
}
```

如果只传开始时间：

```json
{
  "project_id": "demo-project",
  "start_date": "2026-03-01"
}
```

如果需要包含子项目过滤：

```json
{
  "project_id": "demo-project",
  "subproject_id": "frontend",
  "start_date": "2026-03-01",
  "end_date": "2026-03-15"
}
```

## 环境要求

- Node.js `>=20`
- 一个可访问的 Redmine 服务
- 对应 Redmine 的 API Key

## 环境变量

启动前需要设置以下环境变量：

| 变量名 | 必填 | 说明 |
| --- | --- | --- |
| `REDMINE_BASE_URL` | 是 | Redmine 基础地址，例如 `https://redmine.example.com` |
| `REDMINE_API_KEY` | 是 | Redmine API Key |
| `LOG_LEVEL` | 否 | 日志级别：`DEBUG` / `INFO` / `WARN` / `ERROR`，默认 `WARN` |
| `REDMINE_TLS_INSECURE` | 否 | 设为 `true` / `1` / `yes` / `on` 时忽略 HTTPS 证书校验 |

### 关于 TLS / 自签名证书

如果你的 Redmine 使用自签名证书或私有 CA，有两种方式：

- 推荐方式：设置 `NODE_EXTRA_CA_CERTS=/path/to/ca.pem`
- 临时方式：设置 `REDMINE_TLS_INSECURE=true`

注意：启用 `REDMINE_TLS_INSECURE=true` 后，程序会设置 `NODE_TLS_REJECT_UNAUTHORIZED=0`，仅建议在受控环境下使用。

## 安装与构建


```bash
npm install
npm run build
npm config set //registry.npmjs.org/:_authToken npm_token
npm publish --access public
```

开发模式：

```bash
export REDMINE_BASE_URL="https://redmine.example.com"
export REDMINE_API_KEY="your-redmine-api-key"
export LOG_LEVEL="INFO"

npm run dev
```

如果需要忽略证书校验：

```bash
export REDMINE_TLS_INSECURE="true"
```

生产启动：

```bash
export REDMINE_BASE_URL="https://redmine.example.com"
export REDMINE_API_KEY="your-redmine-api-key"
export LOG_LEVEL="WARN"

npm start
```

## 可用脚本

```bash
npm run dev        # 使用 tsx 直接运行 TypeScript 源码
npm run build      # 编译到 dist/
npm run start      # 运行 dist/index.js
npm run typecheck  # 仅做 TypeScript 类型检查
```

## 命令行中直接启动 redmine-mcp

先安装依赖并构建：

```bash
npm install
npm run build
```

然后在命令行里直接启动 MCP Server：

```bash
export REDMINE_BASE_URL="https://redmine.example.com"
export REDMINE_API_KEY="your-redmine-api-key"
export LOG_LEVEL="WARN"

node dist/index.js
```

如果希望直接运行 TypeScript 源码：

```bash
export REDMINE_BASE_URL="https://redmine.example.com"
export REDMINE_API_KEY="your-redmine-api-key"
export LOG_LEVEL="INFO"

npx tsx src/index.ts
```

注意：上面两个命令只是**启动** [`redmine-mcp`](package.json:2) 这个 stdio MCP Server。它会等待 MCP Client 通过标准输入/输出发送协议消息，而不是像普通 REST API 那样直接在 shell 里追加一个 tool 名称就立即返回结果。

## 在命令行里调用 MCP tools

如果你想在命令行环境里**直接调试并调用** [`redmine_list_projects()`](src/mcp-server.ts:47)、[`redmine_list_issues()`](src/mcp-server.ts:64)、[`redmine_get_issue()`](src/mcp-server.ts:90)，推荐使用 MCP Inspector：

```bash
HOST=0.0.0.0 npx @modelcontextprotocol/inspector \
  -e REDMINE_BASE_URL=https://redmine.example.com \
  -e REDMINE_API_KEY=your-redmine-api-key \
  -e REDMINE_TLS_INSECURE=true \
  -e LOG_LEVEL=WARN \
  node /workspace/dist/index.js
```

说明：

- 这个项目使用的是 `stdio` MCP 传输，`dist/index.js` 本身不会暴露 HTTP API；网页入口由 Inspector 提供。
- 推荐使用 `-e KEY=VALUE` 显式传递环境变量，避免 Inspector 子进程没有继承当前 shell 环境，导致 MCP Server 启动时报缺少 `REDMINE_BASE_URL` 或 `REDMINE_API_KEY`。
- `HOST=0.0.0.0` 表示 Inspector 监听所有网卡地址；如果浏览器里打不开它输出的 `http://0.0.0.0:6274/...`，请把地址里的 `0.0.0.0` 改成 `127.0.0.1` 或当前机器的实际 IP。
- 如果你是在 Docker、远程开发机、云 IDE 或 Codespace 里运行，还需要额外暴露 / 转发 Inspector 使用的端口。

也可以先用 CLI 模式快速验证 MCP Server 是否启动成功：

```bash
npx @modelcontextprotocol/inspector --cli \
  -e REDMINE_BASE_URL=https://redmine.example.com \
  -e REDMINE_API_KEY=your-redmine-api-key \
  -e REDMINE_TLS_INSECURE=true \
  -e LOG_LEVEL=WARN \
  node /workspace/dist/index.js --method tools/list
```

如果你想直接在命令行里测试具体 tool，可以继续使用 `--cli` 模式：

`redmine_list_projects`

```bash
npx @modelcontextprotocol/inspector --cli \
  -e REDMINE_BASE_URL=https://redmine.example.com \
  -e REDMINE_API_KEY=your-redmine-api-key \
  -e REDMINE_TLS_INSECURE=true \
  -e LOG_LEVEL=WARN \
  node /workspace/dist/index.js \
  --method tools/call \
  --tool-name redmine_list_projects \
  --tool-arg limit=20 \
  --tool-arg offset=0
```

`redmine_list_issues`

```bash
npx @modelcontextprotocol/inspector --cli \
  -e REDMINE_BASE_URL=https://redmine.example.com \
  -e REDMINE_API_KEY=your-redmine-api-key \
  -e REDMINE_TLS_INSECURE=true \
  -e LOG_LEVEL=WARN \
  node /workspace/dist/index.js \
  --method tools/call \
  --tool-name redmine_list_issues \
  --tool-arg project_id=demo \
  --tool-arg status_id='*' \
  --tool-arg created_from=2026-03-01 \
  --tool-arg created_to=2026-03-15 \
  --tool-arg limit=20 \
  --tool-arg sort=updated_on:desc \
  --tool-arg include=journals
```

`redmine_get_issue`

```bash
npx @modelcontextprotocol/inspector --cli \
  -e REDMINE_BASE_URL=https://redmine.example.com \
  -e REDMINE_API_KEY=your-redmine-api-key \
  -e REDMINE_TLS_INSECURE=true \
  -e LOG_LEVEL=WARN \
  node /workspace/dist/index.js \
  --method tools/call \
  --tool-name redmine_get_issue \
  --tool-arg issue_id=12345
```

`redmine_summarize_issues`

```bash
npx @modelcontextprotocol/inspector --cli \
  -e REDMINE_BASE_URL=https://redmine.example.com \
  -e REDMINE_API_KEY=your-redmine-api-key \
  -e REDMINE_TLS_INSECURE=true \
  -e LOG_LEVEL=WARN \
  node /workspace/dist/index.js \
  --method tools/call \
  --tool-name redmine_summarize_issues \
  --tool-arg project_id=demo \
  --tool-arg start_date=2026-03-01 \
  --tool-arg end_date=2026-03-15
```

只按开始时间过滤：

```bash
npx @modelcontextprotocol/inspector --cli \
  -e REDMINE_BASE_URL=https://redmine.example.com \
  -e REDMINE_API_KEY=your-redmine-api-key \
  -e REDMINE_TLS_INSECURE=true \
  -e LOG_LEVEL=WARN \
  node /workspace/dist/index.js \
  --method tools/call \
  --tool-name redmine_summarize_issues \
  --tool-arg project_id=demo \
  --tool-arg start_date=2026-03-01
```

启动后，Inspector 会连接到该服务，你可以在界面里直接执行以下参数示例：

`redmine_list_projects`

```json
{
  "limit": 20,
  "offset": 0,
  "include": ["trackers", "enabled_modules"]
}
```

`redmine_list_issues`

```json
{
  "project_id": "demo",
  "status_id": "*",
  "created_from": "2026-03-01",
  "created_to": "2026-03-15",
  "closed_from": "2026-03-10",
  "closed_to": "2026-03-20",
  "limit": 20,
  "sort": "updated_on:desc",
  "include": "journals"
}
```

只按 `closed_from` / `closed_to` 查询：

```json
{
  "project_id": "demo",
  "status_id": "closed",
  "closed_from": "2026-03-10",
  "closed_to": "2026-03-20",
  "limit": 20,
  "sort": "closed_on:desc"
}
```

对应的 Inspector CLI 示例：

```bash
npx @modelcontextprotocol/inspector --cli \
  -e REDMINE_BASE_URL=https://redmine.example.com \
  -e REDMINE_API_KEY=your-redmine-api-key \
  -e REDMINE_TLS_INSECURE=true \
  -e LOG_LEVEL=WARN \
  node /workspace/dist/index.js \
  --method tools/call \
  --tool-name redmine_list_issues \
  --tool-arg project_id=demo \
  --tool-arg status_id=closed \
  --tool-arg closed_from=2026-03-10 \
  --tool-arg closed_to=2026-03-20 \
  --tool-arg limit=20 \
  --tool-arg sort=closed_on:desc
```

`redmine_get_issue`

```json
{
  "issue_id": 12345
}
```

`redmine_summarize_issues`

```json
{
  "project_id": "demo",
  "start_date": "2026-03-01",
  "end_date": "2026-03-15"
}
```

```json
{
  "project_id": "demo",
  "start_date": "2026-03-01"
}
```

## MCP 客户端接入

推荐先执行 `npm run build`，然后让 MCP 客户端启动 `dist/index.js`。

### 通用 MCP 配置示例

```json
{
  "mcpServers": {
    "redmine": {
      "command": "npx",
      "args": ["-y","@massapi/redmine-mcp"],
      "env": {
        "REDMINE_BASE_URL": "https://redmine.example.com",
        "REDMINE_API_KEY": "your-redmine-api-key",
        "REDMINE_TLS_INSECURE": "true",
        "LOG_LEVEL": "WARN"
      },
      "alwaysAllow": [
                "redmine_list_issues",
                "redmine_list_projects",
                "redmine_get_issue",
                "redmine_summarize_issues"
      ]      
    }
  }
}
```

如果你希望直接运行 TypeScript 源码：

```json
{
  "mcpServers": {
    "redmine": {
      "command": "npx",
      "args": ["tsx", "/absolute/path/to/redmine-mcp/src/index.ts"],
      "env": {
        "REDMINE_BASE_URL": "https://redmine.example.com",
        "REDMINE_API_KEY": "your-redmine-api-key",
        "REDMINE_TLS_INSECURE": "true",
        "LOG_LEVEL": "INFO"
      }
    }
  }
}
```

### Codex CLI 配置示例

如果你使用 Codex CLI，可以把它作为 MCP Server 加入配置：

```toml
[mcp_servers.redmine]
command = "node"
args = ["/absolute/path/to/redmine-mcp/dist/index.js"]

[mcp_servers.redmine.env]
REDMINE_BASE_URL = "https://redmine.example.com"
REDMINE_API_KEY = "your-redmine-api-key"
LOG_LEVEL = "WARN"
```

如果直接跑源码：

```toml
[mcp_servers.redmine]
command = "npx"
args = ["tsx", "/absolute/path/to/redmine-mcp/src/index.ts"]

[mcp_servers.redmine.env]
REDMINE_BASE_URL = "https://redmine.example.com"
REDMINE_API_KEY = "your-redmine-api-key"
REDMINE_TLS_INSECURE = "true"
LOG_LEVEL = "INFO"
```

## 返回结果说明

该服务对 Redmine 原始返回做了少量增强，便于大模型消费：

- 为项目和问题增加 `link` 字段，方便直接跳转到 Redmine 页面
- 对项目的 `parent` 也会补充 `link`
- 对问题的 `project` 也会补充 `link`
- 从 `journals` 中提取 `progress_items`，用于表示可读的进展轨迹

`progress_items` 的来源是 Issue 的 `journals`。只有当某条 journal 含有 `notes` 或 `details` 时，才会被纳入结果。

## 日志行为

日志写入标准错误输出（`stderr`），格式为 JSON，包含以下典型字段：

- `timestamp`
- `level`
- `context`
- `message`
- `data`

其中：

- `INFO` 级别会记录 Redmine 请求与响应摘要
- `DEBUG` 级别会额外记录响应体预览
- API Key 在日志中会被脱敏处理
- 响应体日志会做长度截断，避免输出过大

## 错误处理

当前实现包含以下主要错误场景：

- 缺少必要环境变量时，启动失败
- 输入参数类型不合法时，返回参数错误
- 无法连接 Redmine 时，返回上游连接错误
- Redmine 返回非 2xx 响应时，透传状态码并记录响应内容
- Redmine 返回非 JSON 内容时，返回解析错误

如果 MCP 客户端里看到：

```text
Failed to connect to MCP server: MCP error -32000: Connection closed
```

通常表示 MCP 子进程在握手前就退出了。优先按下面顺序排查：

- 先在仓库目录执行 `npm install && npm run build`
- 如果客户端启动的是 `dist/index.js`，确认构建产物 `dist/index.js` 已存在
- 确认 MCP 配置里的 `env` 传入了 `REDMINE_BASE_URL` 和 `REDMINE_API_KEY`
- 如果使用 `npx` 拉起包，确认包名写的是 `@massapi/redmine-mcp`，而不是其他同名包
- 在终端里直接运行 `node dist/index.js` 或 `npx tsx src/index.ts`，优先看 `stderr` 的真实报错

## 项目结构

```text
src/
├── config.ts           # 环境变量加载与校验
├── index.ts            # 进程入口，启动 stdio MCP Server
├── logger.ts           # JSON 日志封装
├── mcp-server.ts       # MCP tools 注册
├── services/
│   └── redmine.ts      # Redmine API 调用与结果增强
└── types.ts            # 类型定义与错误类型
```

## 当前边界

当前版本仅实现只读能力，尚未包含：

- 创建/更新/删除 Issue
- 评论 Issue
- 上传附件
- 查询用户、版本、工时等更多 Redmine 资源
- SSE / HTTP Stream 等其他传输方式

如果后续要扩展写操作，建议将读写工具分组，并为高风险操作增加更明确的参数校验与确认机制。