内容
# 红矿MCP服务器
生产就绪的[模型上下文协议](https://modelcontextprotocol.io/)服务器,通过**HTTP + SSE**将红矿问题管理暴露给AI客户端(Cursor,Claude Desktop等),使整个团队可以远程连接。
## 功能
- **18种红矿工具**:问题,搜索,项目,状态,创建/更新,时间跟踪,维基页面
- **SSE传输**用于远程团队访问(`GET /sse`,`POST /messages`)
- **团队门户**通过`x-api-key`(服务器上的`TEAM_SECRET`)
- **每个用户红矿访问**通过每个同事的Cursor `mcp.json`中的`x-redmine-api-key`
- **Docker**和**docker-compose**用于分发/生产
- **nginx**示例配置,包含SSE友好代理设置
- **健康检查端点**用于监控
## 项目结构
```
redmine-mcp/
├── index.js # MCP服务器 + Express应用
├── package.json
├── .env # 本地机密(不提交)
├── .env.example
├── .gitignore
├── Dockerfile
├── docker-compose.yml
├── nginx.conf # 反向代理模板
└── README.md
```
## 本地设置
### 先决条件
- Node.js 18+
- 启用了REST API的红矿实例
- 红矿API密钥(我的账户 → API访问密钥)
### 安装
```bash
cd redmine-mcp
cp .env.example .env
# 编辑.env文件,填写红矿URL和团队密钥(服务器上不存储个人API密钥)
npm install
```
### 配置`.env`
| 变量 | 描述 |
|----------|-------------|
| `REDMINE_URL` | 共享的红矿基准URL,用于整个团队 |
| `TEAM_SECRET` | 共享的门户密钥;客户端发送为`x-api-key` |
| `PORT` | HTTP端口(默认为`3456`) |
每个用户的**个人红矿API密钥**不存储在服务器上。他们在Cursor MCP头中将其设置为`x-redmine-api-key`。
### 本地运行
```bash
npm start
```
验证:
```bash
curl -s http://localhost:3456/health
curl -s -H "x-api-key: YOUR_TEAM_SECRET" http://localhost:3456/health
```
## MCP工具
| 工具 | 描述 |
|------|-------------|
| `get_my_issues` | 指派给API用户的的问题(id,主题,状态,优先级,项目,描述,截止日期,预计工时) |
| `get_issue` | 通过ID获取完整的问题,包括子任务/子问题(如果可用) |
| `get_subtasks` | 父问题的子问题(`parent_issue_id`) |
| `search_issues` | 按主题关键词搜索;可选`project_id` |
| `get_project_issues` | 通过标识符slug获取项目的所有问题 |
| `get_projects` | 列出所有项目 |
| `get_issue_statuses` | 列出工作流状态 |
| `update_issue` | 更新备注,`done_ratio`,`status_id` |
| `create_issue` | 创建问题(`project_id`,`subject`必填;可选描述,优先级,分配人) |
| `log_time` | 记录问题上的工时;设置可计费工时CF(默认为`hours`);`activity_id`默认为5 |
| `get_time_entries` | 问题的时间记录 |
| `get_my_time_entries` | 当前用户的时间记录(可选日期范围) |
| `get_time_activities` | 列出时间活动类型和ID用于`log_time` |
| `get_project_time_entries` | 项目时间记录,带有按用户汇总的小时数 |
| `get_wiki_pages` | 项目的维基页面列表 |
| `get_wiki_page` | 通过项目和标题获取维基页面 |
| `create_wiki_page` | 在项目中创建维基页面 |
| `update_wiki_page` | 更新现有的维基页面 |
## 项目问题工具
### `get_project_issues`
**目的:** 通过项目标识符slug获取项目的所有问题。
| 参数 | 类型 | 必填 | 描述 |
|-----------|------|----------|-------------|
| `project_slug` | 字符串 | 是 | 红矿项目标识符slug |
| `status_id` | 数字或字符串 | 否 | 状态筛选(默认为`*`,所有状态) |
| `include_subprojects` | 布尔值 | 否 | 包括子项目问题(默认为false) |
此工具遵循红矿分页规则,返回所有匹配的问题,而不仅仅是第一页。
**示例提示:**
- “显示项目redmine的所有问题”
- “获取项目my-project-slug的所有开放问题”
- “列出项目support中包括子项目的所有问题”
## 时间跟踪工具
使用这些工具记录工作小时数和查看红矿中花费的时间。调用 **`get_time_activities`** 以获取有效的`activity_id`值用于 **`log_time`**。
### `log_time`
**目的:** 记录红矿问题上花费的时间。
| 参数 | 类型 | 必填 | 描述 |
|-----------|------|----------|-------------|
| `issue_id` | 数字 | 是 | 记录时间的問題 |
| `hours` | 数字 | 是 | 花费的小时数(必须 > 0),例如 `1.5` |
| `activity_id` | 数字 | 否 | 活动类型ID(默认为`5` = 开发) |
| `comments` | 字符串 | 否 | 工作日志备注 |
| `spent_on` | 字符串 | 否 | 日期 `YYYY-MM-DD`(默认为今天) |
| `billable_hours` | 数字 | 否 | 可计费小时自定义字段(默认为`hours`) |
可计费小时作为红矿自定义字段`1`发送(如果实例使用其他ID,则`.env`中的`BILLABLE_HOURS_CF_ID`)。
**示例提示:**
- “在问题#142上记录2.5小时,用于开发活动”
- “在问题98上记录1小时,活动ID 9,备注:代码审查”
- “在问题50上记录3小时,2小时可计费”
### `get_time_entries`
**目的:** 获取特定问题的所有时间记录。
| 参数 | 类型 | 必填 | 描述 |
|-----------|------|----------|-------------|
| `issue_id` | 数字 | 是 | 问题ID |
| `limit` | 数字 | 否 | 最大结果数(默认为`25`) |
返回:`id`,`issue_id`,`project`,`user`,`activity`,`hours`,`comments`,`spent_on`,`created_on`。
**示例提示:**
- “显示问题#98的所有时间记录”
- “问题142上记录了多少时间?”
### `get_my_time_entries`
**目的:** 获取连接的红矿用户记录的时间记录。
| 参数 | 类型 | 必填 | 描述 |
|-----------|------|----------|-------------|
| `from_date` | 字符串 | 否 | 开始日期 `YYYY-MM-DD` |
| `to_date` | 字符串 | 否 | 结束日期 `YYYY-MM-DD` |
| `limit` | 数字 | 否 | 最大结果数(默认为`50`) |
**示例提示:**
- “我本周记录了什么?”
- “显示我的时间记录,从2026-05-12到2026-05-18”
### `get_time_activities`
**目的:** 列出可用的时间活动类型(开发,设计,测试等)和它们的ID。
无参数。
**示例提示:**
- “有哪些时间记录活动类型可用?”
- “列出红矿时间记录活动,以便我可以记录时间”
### `get_project_time_entries`
**目的:** 获取整个项目的時間記錄,并按用戶分组汇总小时数。
| 参数 | 类型 | 必填 | 描述 |
|-----------|------|----------|-------------|
| `project_id` | 数字或字符串 | 是 | 项目数字ID或标识符slug |
| `from_date` | 字符串 | 否 | 开始日期 `YYYY-MM-DD` |
| `to_date` | 字符串 | 否 | 结束日期 `YYYY-MM-DD` |
| `limit` | 数字 | 否 | 最大结果数(默认为`50`) |
**示例提示:**
- “显示项目redmine slug的所有时间记录”
- “汇总项目5的用户记录小时数,本月”
## 维基工具
使用这些工具读取和管理红矿项目维基页面。`project_id`可以是数字项目ID或项目标识符slug。
### `get_wiki_pages`
**目的:** 列出项目的维基页面。
| 参数 | 类型 | 必填 | 描述 |
|-----------|------|----------|-------------|
| `project_id` | 数字或字符串 | 是 | 项目数字ID或标识符slug |
**示例提示:**
- “列出项目redmine的维基页面”
- “显示项目5的所有维基页面”
### `get_wiki_page`
**目的:** 通过项目和标题获取维基页面。
| 参数 | 类型 | 必填 | 描述 |
|-----------|------|----------|-------------|
| `project_id` | 数字或字符串 | 是 | 项目数字ID或标识符slug |
| `title` | 字符串 | 是 | 维基页面标题 |
| `include_attachments` | 布尔值 | 否 | 包含附件(如果可用) |
**示例提示:**
- “显示项目redmine的安装维基页面”
- “获取API文档维基页面,包含附件”
### `create_wiki_page`
**目的:** 在项目中创建维基页面。
| 参数 | 类型 | 必填 | 描述 |
|-----------|------|----------|-------------|
| `project_id` | 数字或字符串 | 是 | 项目数字ID或标识符slug |
| `title` | 字符串 | 是 | 维基页面标题 |
| `text` | 字符串 | 是 | 维基页面内容 |
| `comments` | 字符串 | 否 | 版本备注 |
| `parent_title` | 字符串 | 否 | 父维基页面标题 |
**示例提示:**
- “在项目redmine中创建一个名为部署说明的维基页面”
- “在安装下创建一个子维基页面,包含这些设置步骤”
### `update_wiki_page`
**目的:** 更新现有的维基页面。
| 参数 | 类型 | 必填 | 描述 |
|-----------|------|----------|-------------|
| `project_id` | 数字或字符串 | 是 | 项目数字ID或标识符slug |
| `title` | 字符串 | 是 | 维基页面标题 |
| `text` | 字符串 | 是 | 替换维基页面内容 |
| `comments` | 字符串 | 否 | 版本备注 |
| `parent_title` | 字符串 | 否 | 父维基页面标题 |
**示例提示:**
- “更新项目redmine中的部署说明维基页面”
- “替换API文档维基页面内容并添加版本备注”
## Cursor `mcp.json`(团队成员)
添加到 `~/.cursor/mcp.json`(或项目 `.cursor/mcp.json`)。
- **`x-api-key`** — 相同的`TEAM_SECRET`(来自管理员)。
- **`x-redmine-api-key`** — 每个人的红矿API密钥(红矿 → 我的账户 → API访问密钥)。
```json
{
"mcpServers": {
"redmine": {
"url": "https://mcp.yourcompany.com/sse",
"headers": {
"x-api-key": "shared_team_secret_from_admin",
"x-redmine-api-key": "your_personal_redmine_api_key"
}
}
}
}
```
本地开发:
```json
{
"mcpServers": {
"redmine-local": {
"url": "http://localhost:3456/sse",
"headers": {
"x-api-key": "shared_team_secret_from_admin",
"x-redmine-api-key": "your_personal_redmine_api_key"
}
}
}
}
```
`get_my_issues`和其他工具使用**连接的用户**的红矿身份,而不是服务器上的管理员密钥。
## 健康检查
```bash
curl -s http://localhost:3456/health | jq
```
返回服务器名称,版本,红矿URL(配置),活动SSE会话数和运行时间。 `/health`上不需要API密钥。
## 分发部署
### 选项A — Docker(推荐)
```bash
cp .env.example .env
# 填写生产值
docker compose up -d --build
docker compose logs -f
```
### 选项B — 主机上的PM2
```bash
npm install --omit=dev
npm install -g pm2
pm2 start index.js --name redmine-mcp
pm2 save
pm2 startup
```
### nginx反向代理
1. 复制`nginx.conf`到`/etc/nginx/sites-available/redmine-mcp`
2. 替换`mcp.yourcompany.com`为您自己的域名
3. 启用站点并重新加载nginx
4. 运行certbot获取TLS:`certbot --nginx -d mcp.yourcompany.com`
确保SSE位置保持`proxy_buffering off`和较长的`proxy_read_timeout`。
## API端点
| 方法 | 路径 | 认证 | 目的 |
|--------|------|------|---------|
| GET | `/sse` | `x-api-key` + `x-redmine-api-key` | 建立MCP SSE流(用户密钥绑定到会话) |
| POST | `/messages?sessionId=...` | `x-api-key` | 客户端JSON-RPC消息(使用会话的红矿密钥) |
| GET | `/health` | 无 | 存活/状态 |
## 安全说明
- 如果`TEAM_SECRET`泄露,请更换;仅通过团队秘密管理器共享
- 在nginx上终止TLS;不要在没有代理的情况下公开暴露端口3456
- 每个同事在Cursor中使用自己的红矿API密钥;永远不要将密钥提交到git
- 如果`TEAM_SECRET`泄露,请更换;如果个人红矿密钥被泄露,请更换
## 许可证
ISC