内容
# 精简JIRA MCP
一款适用于JIRA Cloud的精简、代理优先的MCP服务器。参见 [`DESIGN.md`](./DESIGN.md) 了解设计理念和决策。
与其将REST API 1:1映射,不如暴露10个意图型工具,这些工具读取和写入markdown(不是原始ADF),在服务器端解析人类引用(电子邮件、“我”、状态名称),隐藏内部ID和分页机制,并为每个响应(成功或错误)添加恢复提示。
## 工具和资源
**工具:** `jira_search`, `jira_get_issue`, `jira_create_issue`, `jira_update_issue`, `jira_transition`, `jira_comment`, `jira_get_comments`, `jira_describe_project`, `jira_read_attachment`, `jira_add_attachment`.
**资源:** `jira://projects` (项目目录),`jira://jql-fields` (JQL字段目录,包括自定义字段),`jira://link-types`.
## 要求
- 一个JIRA Cloud站点 + 一个**经典、无范围**的 [API令牌](https://id.atlassian.com/manage-profile/security/api-tokens) (参见下文)
- 以下之一:一个可以通过`npx`运行已发布的二进制文件的宿主(Node ≥ 18),一键式`.mcpb`包,或用于本地开发的 [Bun](https://bun.sh) ≥ 1.3
## 配置
所有配置在启动时从环境中读取 - **凭证从不作为工具参数传递**。缺少或空白的必填值会快速失败,并显示明确的启动错误,指出变量名称。
| 变量 | 必需 | 默认值 | 描述 |
| --- | --- | --- | --- |
| `JIRA_BASE_URL` | 是 | - | 您的JIRA Cloud站点,例如 `https://your-domain.atlassian.net` |
| `JIRA_EMAIL` | 是 | - | Atlassian账户电子邮件(HTTP基本身份验证) |
| `JIRA_API_TOKEN` | 是 | - | **经典、无范围**的API令牌(参见下文) |
| `JIRA_PROJECT_DEFAULT` | 否 | - | `jira_create_issue`未指定项目时使用的项目密钥(例如 `PROJ`) |
| `JIRA_MCP_COMMENT_MARKER` | 否 | `:claude:` | 代理发布的评论的前缀可见标记 |
| `JIRA_FIELD_STORY_POINTS` | 否 | 自动检测 | Story Points的自定义字段ID(显式覆盖) |
| `JIRA_FIELD_SPRINT` | 否 | 自动检测 | Sprint的自定义字段ID(显式覆盖) |
| `JIRA_FIELD_EPIC_LINK` | 否 | 自动检测 | Epic Link的自定义字段ID(显式覆盖) |
| `JIRA_MCP_ATTACHMENT_MAX_BYTES` | 否 | `26214400`(25 MB) | `jira_read_attachment`的硬字节上限;较大的文件在下载前被拒绝(注意 + URL返回) |
| `JIRA_ATTACHMENT_ROOT` | 否 | 当前工作目录 + 临时目录 | `jira_add_attachment`可以上传的路径分隔符(`:`/`;`)根目录;路径在所有根目录之外(在`..`/符号链接解析后)被拒绝,阻止例如`~/.ssh`等敏感信息的外泄。默认值仅覆盖代理生成的目录;持有个人文件的目录(例如`~/Downloads`)是故意选择加入的 |
| `JIRA_MCP_ATTACHMENT_UPLOAD_MAX_BYTES` | 否 | `26214400`(25 MB) | `jira_add_attachment`的大小上限;较大的文件被拒绝,并附带提示而不是原始413错误 |
三个`JIRA_FIELD_*` ID支持`jira_get_issue`显示的精选敏捷字段。服务器在启动时自动检测每个ID(通过其稳定的JIRA Software模式密钥检测Sprint和Epic Link,通过字段名称检测Story Points),因此仅当检测在您的实例上丢失时,才需要显式覆盖 - 在 `/rest/api/3/field` 上查找ID并设置匹配的变量。如果未检测到字段且未设置覆盖,服务器会记录警告,指出它并将其从`jira_get_issue`中省略,而不是进行猜测。
### API令牌必须无范围
在
[id.atlassian.com → API令牌](https://id.atlassian.com/manage-profile/security/api-tokens)
使用 **"创建API令牌"** 创建令牌 - 而不是"创建具有范围的API令牌"。
**有范围** 的令牌(具有诸如 `read:jira-work`、`write:jira-work`、`read:jira-user`、`read:me` 等粒度范围) **与HTTP基本身份验证不兼容**。JIRA Cloud不会直接拒绝它 - 而是会静默地将请求视为 **匿名**,因此数据端点返回 `200` 且结果 **为空**,而 `/myself` 返回 `401`。症状是"每次搜索都返回0个问题"。
服务器对此进行了防范:在第一次工具调用时,它会预先检测 `/rest/api/3/myself`(已缓存)。如果凭证被拒绝,工具会返回一个带有提示的 `authentication_failed` 错误,而不是误导性的空结果。
快速检查令牌是否有效:
```sh
curl -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
https://your-domain.atlassian.net/rest/api/3/myself
# → 您的用户JSON(有效) | 401 "客户端必须经过身份验证"(令牌无效)
```
## 安装
### Claude 代码
```sh
claude mcp add jira \
-e JIRA_BASE_URL=https://your-domain.atlassian.net \
-e JIRA_EMAIL=you@example.com \
-e JIRA_API_TOKEN=your-api-token \
-- npx -y lean-jira-mcp
```
默认范围是 `local`(仅限此项目);在 `--` 之前添加 `-s user` 以使其在所有项目中可用。
### Codex CLI
```sh
codex mcp add jira \
--env JIRA_BASE_URL=https://your-domain.atlassian.net \
--env JIRA_EMAIL=you@example.com \
--env JIRA_API_TOKEN=your-api-token \
-- npx -y lean-jira-mcp
```
或直接编辑 `~/.codex/config.toml`:
```toml
[mcp_servers.jira]
command = "npx"
args = ["-y", "lean-jira-mcp"]
[mcp_servers.jira.env]
JIRA_BASE_URL = "https://your-domain.atlassian.net"
JIRA_EMAIL = "you@example.com"
JIRA_API_TOKEN = "your-api-token"
```
### 任意其他MCP客户端 - `npx`
无需手动构建:已发布的包附带一个自包含的、Node可运行的二进制文件。将您的客户端的MCP配置指向它,并通过`env`提供凭证:
```jsonc
{
"mcpServers": {
"jira": {
"command": "npx",
"args": ["-y", "lean-jira-mcp"],
"env": {
"JIRA_BASE_URL": "https://your-domain.atlassian.net",
"JIRA_EMAIL": "you@example.com",
"JIRA_API_TOKEN": "your-unscoped-api-token",
"JIRA_PROJECT_DEFAULT": "PROJ",
"JIRA_MCP_COMMENT_MARKER": ":claude:"
}
}
}
}
```
(在Claude Desktop中,这位于 `claude_desktop_config.json`。只有前三个环境变量是必需的;其余是可选的。)
### Claude Desktop - `.mcpb` 包(一键式)
构建包,然后在Claude Desktop中打开它(设置 → 扩展,或双击 `.mcpb` 文件)。Claude Desktop会提示输入上述配置值,并安全地存储API令牌。
```sh
bun install
bun run build:mcpb # → dist/lean-jira-mcp.mcpb
```
该包是一个自包含的Node有效载荷(无 `node_modules` 可供运输)以及一个 [`manifest.json`](./manifest.json),其配置提示与上表中的环境变量一一对应。要验证清单是否符合官方规范:
```sh
npx -y @anthropic-ai/mcpb validate manifest.json
```
## 已知限制
- **评论反应不可读。** Emoji反应(👍等)未被Jira Cloud REST/令牌API公开 - 没有 `reactions` 字段、扩展或Jira表达式路由,并且内部反应端点在Cloud上缺失(或仅限cookie/会话身份验证)。唯一的诚实离线路由是通过呈现的UI(会话身份验证)进行 **浏览器自动化** - 一个不同的机制和一个不同的工具, **明确超出此REST/令牌身份验证服务器的范围**。将反应视为一个盲点:永远不要根据它们推断决策、批准或共识 - 此服务器仅根据API可见的工件(文本评论、标签、状态)进行密钥交换。
## 本地开发
```sh
bun install
cp .env.example .env # 填写 JIRA_BASE_URL、JIRA_EMAIL、JIRA_API_TOKEN
bun run start # 通过stdio提供服务
bun test # 完整的离线测试套件(无网络)
bun run typecheck # tsc --noEmit
bun run build # 捆绑Node二进制文件 → dist/index.js
```
### Bun `.env` 自动加载陷阱(配置可能被忽略)
Bun **自动从工作目录加载 `.env`**。当MCP客户端使用 `bun run src/index.ts` 启动服务器且cwd = 此存储库时,一个流氓存储库 `.env` **会静默地填充客户端未在其自己的配置中设置的任何环境变量**。这可能会:
- **掩盖快速失败契约** - 缺少必需的变量看起来已设置,因为`.env`填充了它,所以服务器启动而不是大声失败;并且
- **使 `JIRA_PROJECT_DEFAULT` 看起来被忽略** - 存储库 `.env`的值胜过您认为客户端正在传递(或不传递)的内容。
已发布的 **Node二进制文件不自动加载 `.env`** - 这仅在通过Bun进行本地开发/测试时才会发生。要忠实地测试配置和快速失败行为,请从 **干净的工作目录** (或暂时将存储库 `.env` 移到一边)运行,以便只有您实际传递的变量在范围内:
```sh
cd "$(mktemp -d)" && JIRA_BASE_URL=… JIRA_EMAIL=… JIRA_API_TOKEN=… bun run /path/to/repo/src/index.ts
```
### 架构
- `src/config/env.ts` - 单一配置读取路径:加载 + 验证环境,快速失败。
- `src/lib/jira-client.ts` - 单一的JIRA HTTP边界(身份验证、重试/回退)。唯一的外部测试缝;工具处理程序依赖于 `IJiraClient` 接口。
- `src/lib/response-shaper.ts` - 统一的成功/错误信封,印有提示。
- `src/tools/` - 每个工具一个文件 + 中央注册表(`tools/index.ts`)。
- `src/resources/` - 静态参考资源 + 注册表。
- `src/core/server.ts` - 依赖注入的 `McpServer` 构造。
- `test/helpers/fake-jira-client.ts` - 每个工具重用的伪造 `JiraClient` 固定装置。
### 手动/实时烟雾测试
测试套件完全离线运行,针对伪造的客户端。要使用真实的JIRA路径,请在 `.env` 中设置凭证,并将MCP Inspector指向服务器:
```sh
bunx @modelcontextprotocol/inspector bun run src/index.ts
```
然后使用真实的 `jql` 调用 `jira_search`(例如 `project = ABC ORDER BY updated DESC`)。