jira-mcp
A lightweight MCP server for self-hosted Jira Server, exposing REST API v2 tools for search, issue creation/update, transitions, comments, and more to AI clients via MCP protocol.
README
jira-mcp —— 自托管 Jira 的轻量 MCP Server
一个面向 自托管 Jira Server 7.3.6(私有化部署,如 https://jira.gacrnd.com:8443)
的最小 MCP 服务器。官方 Atlassian Rovo MCP 只支持 Cloud(*.atlassian.net),
这个项目填补自托管场景的空缺,直连 Jira REST API v2,通过 MCP 协议把 Jira 能力
暴露给 Claude Desktop / Cursor / Hermes / VS Code Copilot 等 AI 客户端。
本文档既包含快速上手,也包含完整的设计说明,供维护 / 扩展 / 接入使用。
目录
- 特性
- 快速上手
- 工具清单
- 背景与动机
- 设计目标
- 整体架构
- 配置设计
- HTTP 客户端层设计
- 工具 API 设计
- 抗截断设计
- 内部接口专项设计
- 错误处理设计
- 关键设计决策与权衡
- 测试与验证设计
- 已知限制与注意点
- 扩展方向
- 附录:文件清单
特性
- stdio 传输,任何 MCP 客户端都能挂(Claude Desktop / Cursor / Hermes / VS Code Copilot)
- Basic Auth 认证(7.3.6 无 PAT),支持自签证书(默认不校验 SSL)
- 13 个工具:搜索 / 查询 / 建单 / 改单 / 流转 / 评论 / 项目与类型枚举 / 身份校验 / 保存筛选器 / 问题表格
- 服务端做 payload 精简,抗 LLM 工具结果截断
快速上手
环境要求
- Python 3.10+ ,已装
mcp、requests
安装
cd /media/tommy/win_documents/code/mcp/jira-mcp
pip install -r requirements.txt
配置
复制 .env.example 内容到你的客户端配置或 export 环境变量:
export JIRA_URL=https://jira.gacrnd.com:8443
export JIRA_USERNAME=<你的账号>
export JIRA_PASSWORD=<密码>
认证说明:Jira 7.3.6 没有 Personal Access Token(8.14 才引入),只能用 Basic Auth(账号 + 密码)。建议用一个专用服务账号,别用个人主账号。 若账号走 SSO/LDAP,确认该账号能用密码做 REST 认证。
挂载示例
Claude Desktop(claude_desktop_config.json)
{
"mcpServers": {
"jira-gac": {
"command": "python3",
"args": ["/media/tommy/win_documents/code/mcp/jira-mcp/server.py"],
"env": {
"JIRA_URL": "https://jira.gacrnd.com:8443",
"JIRA_USERNAME": "你的账号",
"JIRA_PASSWORD": "你的密码",
"JIRA_VERIFY_SSL": "false"
}
}
}
}
Hermes Agent
见 hermes mcp 相关命令或 hermes-mcp-setup skill,把上面的 command/args/env
加到 MCP 服务器列表即可(stdio 类型)。
快速自测
# 仅测工具注册(不连 Jira)
python3 - <<'PY'
import server
print("tools:", [t for t in dir(server) if t.startswith("jira_")])
PY
工具清单
| 工具 | 说明 |
|---|---|
| jira_get_myself | 校验账号可用(排查认证) |
| jira_search | JQL 搜索 issue |
| jira_list_filters | 列出当前账号的保存筛选器 |
| jira_search_by_filter | 按筛选器(id/名称)搜索 |
| jira_issue_table | 问题导航器表格(页面同款列配置) |
| jira_get_issue | 按 key 查单个 issue 全字段 |
| jira_list_projects | 列出可见项目 |
| jira_get_issue_types | 列项目可用 issue 类型(可能是中文) |
| jira_create_issue | 建单 |
| jira_update_issue | 改单(summary/description/assignee/priority) |
| jira_list_transitions | 列可用的状态流转 |
| jira_transition_issue | 按名称流转状态 |
| jira_add_comment | 加评论 |
背景与动机
官方 Atlassian Rovo MCP Server(github.com/atlassian/atlassian-mcp-server)仅面向
Cloud(*.atlassian.net),其入口 mcp.atlassian.com 无法代理自托管(Server / Data Center)
实例。本项目直连 自托管 Jira Server 7.3.6 的 REST API v2,填补自托管场景的空缺。
关键约束(决定了多项设计选择)
| 约束 | 影响 |
|---|---|
| Jira 7.3.6 < 8.14,无 Personal Access Token(PAT 8.14 才引入) | 认证只能走 Basic Auth(账号 + 密码) |
| 自托管实例使用自签证书(8443 端口) | 默认关闭 SSL 校验,并抑制 InsecureRequestWarning |
社区 sooperset/mcp-atlassian 要求 8.14+ |
不依赖它,自己实现最小 server |
| 账号可能走 SSO/LDAP | Basic Auth 仍按用户目录校验;建议用专用服务账号 |
| AI 客户端工具结果有大小上限(沙箱 ~ 数百 KB) | 搜索结果必须精简摘要,丢弃巨型字段 |
设计目标
- 最小依赖、单文件:
server.py一个文件即可运行,仅依赖mcp+requests。 - stdio 传输:任何 MCP 客户端都能挂,无需网络端口 / 进程常驻管理。
- AI 友好:工具返回值统一
{"ok": bool, ...},永不抛异常,错误信息可读, 让 LLM 客户端能读取错误并继续,而不是被异常打断。 - 抗截断:对搜索 / 表格等大 payload 做结构化精简,只保留对 LLM 有用的字段。
- 可诊断:提供身份校验工具、端到端验证脚本,便于排查认证 / 连通性问题。
整体架构
┌─────────────────────────────────────────────────────────────┐
│ MCP 客户端 (Claude Desktop / Cursor / Hermes / Copilot) │
└───────────────────────────────┬─────────────────────────────┘
│ stdio (JSON-RPC 2.0)
│ initialize / tools/list / tools/call
┌───────────────────────────────▼─────────────────────────────┐
│ FastMCP("jira-gac") ← MCP SDK 层 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 13 个 @mcp.tool() 处理器 │ │
│ │ jira_get_myself / jira_search / jira_list_filters / │ │
│ │ jira_search_by_filter / jira_issue_table / │ │
│ │ jira_get_issue / jira_create_issue / jira_update_issue │ │
│ │ jira_list_transitions / jira_transition_issue / │ │
│ │ jira_add_comment / jira_list_projects / │ │
│ │ jira_get_issue_types │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ JiraClient ← HTTP 客户端层(requests.Session)│ │
│ │ · Basic Auth / Bearer / SSL 开关 │ │
│ │ · request() 统一请求 + 错误规整 │ │
│ │ · post_issue_table() 内部接口专用(CSRF 头) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ JiraError ← 错误规整层 │ │
│ │ · errorMessages / errors → 可读字符串 │ │
│ └─────────────────────────────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────┘
│ HTTPS (verify 可关)
┌───────────────────────────────▼─────────────────────────────┐
│ 自托管 Jira Server 7.3.6 │
│ · REST API v2 : /rest/api/2/... │
│ · 内部接口 : /rest/issueNav/1/issueTable │
└─────────────────────────────────────────────────────────────┘
分层职责:
- 工具层(
@mcp.tool())只做「入参校验 → 调 client → 精简 → 包_ok/_err」,不含 HTTP 细节。 - 客户端层(
JiraClient)只做「拼 URL → 发请求 → 解析 JSON → 抛JiraError」。 - 规整层(
JiraError/_name/_summarize_*)负责「原始 Jira 数据 → LLM 友好输出」。
配置设计
全部通过环境变量注入,避免硬编码凭据,便于客户端配置与 CI 隔离。
| 环境变量 | 默认值 | 说明 |
|---|---|---|
JIRA_URL |
https://jira.gacrnd.com:8443 |
Jira 根地址,末尾 / 会被 rstrip 去掉 |
JIRA_USERNAME |
空 | Basic Auth 用户名(7.3.6 主认证方式) |
JIRA_PASSWORD |
空 | Basic Auth 密码 |
JIRA_PERSONAL_TOKEN |
空 | 可选;Jira 8.14+ 的 PAT,7.3.6 忽略 |
JIRA_VERIFY_SSL |
false |
true/false;自签证书设 false |
JIRA_DEFAULT_PROJECT |
空 | 预留:缺省 project key(当前工具未消费,留作扩展) |
认证优先级(见 JiraClient.__init__):
- 若设
JIRA_PERSONAL_TOKEN→Authorization: Bearer <token>(8.14+); - 否则若设
JIRA_USERNAME→requests的session.auth = (username, password)(HTTP Basic); - 两者皆无 → 匿名请求(工具调用会收到 401,规整为
ok=false错误,不崩溃)。
SSL 处理:
JIRA_VERIFY_SSL = os.environ.get("JIRA_VERIFY_SSL", "false").lower() in ("1", "true", "yes")
if not JIRA_VERIFY_SSL:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
session.verify = JIRA_VERIFY_SSL决定 TLS 证书校验;- 关闭校验时抑制
InsecureRequestWarning,避免 stdio 上刷屏。
安全建议:优先用专用服务账号而非个人主账号;
JIRA_DEFAULT_PROJECT为预留字段, 当前未在工具逻辑中消费,后续可用来简化建单参数。
HTTP 客户端层设计
JiraError — 错误规整
把 Jira 返回的错误体规整成一条可读字符串,供 LLM 直接展示:
class JiraError(Exception):
def __init__(self, status, data):
self.status = status
self.data = data
msgs = []
if isinstance(data, dict):
msgs.extend(data.get("errorMessages") or []) # 顶层错误消息数组
msgs.extend(f"{k}: {v}" for k, v in (data.get("errors") or {}).items()) # 字段级错误
super().__init__("; ".join(msgs) or f"HTTP {status}")
- 兼容 Jira 两种错误体:全局
errorMessages(字符串数组)与字段级errors(dict)。 - 两者皆无时退化为
HTTP <status>,保证总有可读信息。
JiraClient — 请求封装
class JiraClient:
def __init__(self):
self.session = requests.Session() # 连接复用(连接池 / Keep-Alive)
self.session.verify = JIRA_VERIFY_SSL
self.session.headers["Accept"] = "application/json"
# ... 认证设置见「配置设计」
- 使用
requests.Session复用底层连接,避免每次工具调用重新握手(自签 8443 握手开销不小)。 _url(path):JIRA_URL + "/rest/api/2/" + path.lstrip("/"),统一 API v2 前缀。request(method, path, **kwargs):核心统一方法——- 默认
timeout=30; 204 No Content→ 返回None;- 尝试
resp.json(),解析失败则退化为{"raw": resp.text[:2000]}(截断防超大文本); status >= 400→ 抛JiraError。
- 默认
get/post/put为便捷方法。post_issue_table(...):内部接口专用(见「内部接口专项设计」),直接 POST 而非走request(),因为目标/rest/issueNav/1/issueTable不在/rest/api/2/下,且需要form-urlencodedbody + CSRF 免检头 + XHR 头。
工具 API 设计
统一响应规约
每个工具只返回 JSON,不抛异常,两种形态:
def _ok(data): return {"ok": True, "data": data}
def _err(e): return {"ok": False, "error": str(e)}
- 成功:
{"ok": true, "data": <精简后的结构>} - 失败:
{"ok": false, "error": "<可读错误>"}
设计意图:AI 客户端通过
ok判断成败,失败时读error继续推理或重试, 不会被 Python 异常中断整条工具调用链路。
工具清单与数据流
| 工具 | 方法 | Jira 端点 | 写副作用 | 输出精简策略 |
|---|---|---|---|---|
jira_get_myself |
GET | /myself |
无 | 原始(诊断用) |
jira_search |
GET | /search |
无 | _summarize_search |
jira_list_filters |
GET | /filter/favourite → /filter/my |
无 | [{id,name,jql}] |
jira_search_by_filter |
GET | /filter/{id} + /search |
无 | _summarize_search + filter/jql |
jira_issue_table |
POST | /rest/issueNav/1/issueTable |
无 | _summarize_issue_table |
jira_get_issue |
GET | /issue/{key} |
无 | 原始(单条) |
jira_list_projects |
GET | /project |
无 | [{key,name,id,type}] |
jira_get_issue_types |
GET | /issue/createmeta |
无 | [{id,name}] |
jira_create_issue |
POST | /issue |
有 | {key,id,url} |
jira_update_issue |
PUT | /issue/{key} |
有 | {key,updated:[...]} |
jira_list_transitions |
GET | /issue/{key}/transitions |
无 | [{id,name}] |
jira_transition_issue |
POST | /issue/{key}/transitions |
有 | {key,transitioned_to} |
jira_add_comment |
POST | /issue/{key}/comment |
有 | {key,comment_id} |
各工具设计要点
jira_get_myself — 身份诊断入口。返回 client.get("myself") 原始结果,用于排查
认证是否可用。匿名/密码错误时返回 ok=false 且含可读错误,不崩溃。
jira_search(jql, max_results=20, fields=DEFAULT_FIELDS, raw=False)
DEFAULT_FIELDS = "summary,status,priority,issuetype,assignee,reporter,created,updated", 显式指定字段列表,避免拉取全字段(默认会带 avatarUrls/expand 等冗余)。raw=False走精简;raw=True返回 Jira 原始完整结果(调用方明确需要 description/attachments 等大字段时才用)。
jira_list_filters / jira_search_by_filter — 保存筛选器支持
- Jira 保存的筛选器在
GET /filter/favourite(收藏)与/filter/my(我的), 每项含id/name/jql。 _resolve_filter_jql(filter_ref)解析规则:filter_ref是纯数字 → 当作 id 直接GET /filter/{id}读 jql;- 否则在
favourite/my两个端点里按 id 或名称匹配; - 都未命中 → 返回
ok=false,提示可用jira_list_filters查看。
- 结果额外附
filter(名称)与jql(解析出的 JQL),便于调用方继续扩展 JQL。
jira_issue_table — 问题导航器内部接口(详见「内部接口专项设计」)
jira_get_issue(key) — 按 key 查单条,返回原始字段(单条 payload 小,无需精简)。
jira_get_issue_types(project_key) — 建单前的类型枚举
- 调
GET /issue/createmeta?projectKeys=<k>&expand=projects.issuetypes; - 返回
[{id, name}]。name 可能本地化(中文「任务/缺陷」),建单前必须用它确认。
jira_create_issue(project_key, summary, description="", issue_type="Task", assignee="", priority="")
issue_type/priority/assignee均用名称(Server 版语义,非 Cloud 的 accountId):issuetype: {"name": ...}、priority: {"name": ...}、assignee: {"name": ...};- 空值字段不写入
fields,避免误提交;
- 成功返回
{key, id, url},url直接拼成/browse/{key}供用户点击。
jira_update_issue(key, summary/description/assignee/priority)
- 只把非空字段放入
fields(增量更新); - 全空 → 返回
ok=false「没有传入任何要更新的字段」,避免无意义 PUT。
jira_list_transitions / jira_transition_issue
list返回[{id, name}];transition支持按名称或 id定位目标流转(名称本地化,所以支持 id 兜底);- 未命中 → 返回
ok=false并列出所有可用名称,方便客户端纠正。
抗截断设计
MCP 工具结果过大(如一次 raw 搜索 50 条 issue 约 180 KB)会撑爆 LLM 的工具结果沙箱。 本项目在服务端做精简,把体积压到 ~1/10:
_name(obj) — 字段规整
Jira 字段值可能是 dict(如 {"name": ..., "displayName": ..., "key": ...})、str 或
None。统一规整成可读字符串:
def _name(obj):
if isinstance(obj, dict):
return obj.get("name") or obj.get("displayName") or obj.get("key") or ""
if isinstance(obj, str):
return obj
return ""
_summarize_issue — 单条 issue 精简
return {
"key", "summary", "status", "priority", "type",
"assignee", "reporter", "created", "updated",
}
- 丢弃
avatarUrls/iconUrl/expand/ 嵌套对象等冗余字段; - 状态/优先级/类型/经办人/报告人全部 flatten 成字符串。
_summarize_search — 搜索结果精简
保留分页元信息 total / startAt / maxResults + 精简后的 issues 列表。
_summarize_issue_table — 表格结果精简(最激进)
issueTable 接口返回的 table 字段是渲染好的 HTML(几百 KB)。只保留:
total, displayed, page, pageSize, startIndex, sortBy, columnConfig, columns, issueKeys
丢弃 HTML table 字段本身。
内部接口专项设计
jira_issue_table 是对 Jira 问题导航器内部接口的封装,与标准 REST v2 不同:
- 端点:
POST /rest/issueNav/1/issueTable(不在/rest/api/2/前缀下); - 请求体:
application/x-www-form-urlencoded; - 参数:
startIndex/filterId/jql/layoutKey; - 必要请求头:
X-Atlassian-Token: no-check— 跳过 Jira 的 XSRF 校验;X-Requested-With: XMLHttpRequest— 标记为 AJAX 请求。
与 jira_search 的区别:本工具返回 Jira 页面同款的列配置与排序
(columnConfig / sortBy),更适合还原「我在 Jira 页面上看到的那个列表」。
默认 JQL:assignee = currentUser() AND resolution = Unresolved order by updated DESC,
即「当前账号未解决的问题」,配合 jira-unresolved-issues skill 使用。
错误处理设计
| 场景 | 行为 |
|---|---|
| HTTP 4xx/5xx | request() 抛 JiraError,工具捕获后 _err(e) |
| JSON 解析失败 | 退化为 {"raw": text[:2000]},避免异常 |
| 204 无内容 | 返回 None(如 PUT 成功无 body) |
| 筛选器未命中 | _err("找不到筛选器 ...") 附排查提示 |
| 状态流转未命中 | _err(...) 附可用名称列表 |
| 更新无字段 | _err("没有传入任何要更新的字段") |
| 无凭据 | 请求返回 401 → 规整为 ok=false,不崩溃 |
设计原则:所有错误都走 {"ok": false, "error": <可读文本>},错误文本尽量带上
「下一步建议」(如「可用 jira_list_filters 查看」)。
关键设计决策与权衡
| 决策 | 理由 | 权衡 |
|---|---|---|
单文件 server.py |
部署极简,stdio 挂载无路径依赖 | 扩展性靠新增函数,非模块化 |
| 服务端做 payload 精简 | 抗截断,省 LLM token | raw=True 需显式 opt-in 才能拿全量 |
| 认证走 Basic Auth | 7.3.6 无 PAT 的硬约束 | 密码明文走 HTTPS,需专用账号 |
| 默认关 SSL 校验 | 自签证书环境 | 有中间人风险,仅内网可接受 |
| 工具永不抛异常 | LLM 能读错误继续推理 | 客户端需自己判断 ok |
assignee 用 {"name": ...} |
Server 版语义 | Cloud 用 accountId,不可照抄 Cloud 示例 |
| 名称 + id 双通道定位(流转/筛选器) | 名称本地化易错,id 稳定 | 略增解析逻辑 |
测试与验证设计
项目内置三类验证脚本,分层覆盖:
| 脚本 | 层 | 说明 |
|---|---|---|
test_handshake.py |
传输 + 注册 | 以真实 MCP 客户端身份拉起 server,验证握手 + 工具注册 + 一次调用(无凭据 → 应得 ok=false 而非异常) |
verify_server.py |
端到端 | 从 ~/.hermes/config.yaml 读凭据(不落盘密码),握手 + 断言 jira_list_filters/jira_search_by_filter 已注册 + 探测两工具 |
test_readonly.py |
只读工具 | 直接 import server,覆盖 get_issue_types / get_issue / list_transitions(无写副作用)+ 错误分支 |
test_live.py |
实连 | 加载 .env,真实调 get_myself / list_projects / search |
验证原则:不要只 import 模块做 Python 内省 —— 必须走 MCP stdio 传输做
initialize + list_tools + call_tool,才能发现传输层 / 注册层的 bug。
已知坑:stdio server 会把 INFO 日志(Processing request of type ...)打到 stdout,
握手时无害(客户端读 JSON-RPC 帧而非裸 stdout),但 shell 一行式调用时应 2>/dev/null 抑制。
已知限制与注意点
- 建单前先确认类型名:
issue_type/priority名称可能本地化(中文环境「任务/缺陷」), 先用jira_get_issue_types确认精确名称。 description是纯字符串:7.x 上 wiki/plain 渲染按字段配置,纯字符串两者兼容。- 代理会破坏自托管 Jira 的 TLS:
jira.gacrnd.com解析到内网 IP,但 shell 的https_proxy(如 Clash127.0.0.1:7897)仍会命中,因为no_proxy的 CIDR 条目 (172.16.0.0/12)匹配的是主机名而非解析后的 IP。CONNECT 隧道导致 TLS 握手 失败报SSL: UNEXPECTED_EOF_WHILE_READING(非证书错误)。修复:把主机名加入no_proxy(如.gacrnd.com),或对 session 设trust_env=False/ 显式空代理。 - 编辑 server.py 后需重启会话:Hermes 在会话启动时发现 MCP 工具;改动后需
/reset(CLI)//restart(gateway)才生效。 JIRA_DEFAULT_PROJECT尚未消费:预留字段,后续可用于建单默认项目。- 工具永不抛异常:每个工具返回
{"ok": true, "data": ...}或{"ok": false, "error": ...},失败不会抛异常,便于 AI 客户端读取错误继续处理。
扩展方向
- 分页遍历:
jira_search当前单页返回,可加startAt遍历; - 消费
JIRA_DEFAULT_PROJECT:建单时缺省 project key; - 附件 / 字段配置读取:接入
raw=True已有数据,封装成专用工具; - 8.14+ PAT 兼容:
JIRA_PERSONAL_TOKEN已预留,升级实例后自动走 Bearer; - 权限白名单:为写操作(建单/改单/流转/评论)加项目级 / 账号级开关,控制 AI 写权限。
附录:文件清单
| 文件 | 作用 |
|---|---|
server.py |
主程序,全部工具 + 客户端实现 |
requirements.txt |
依赖:mcp>=1.20.0、requests>=2.31.0 |
README.md |
本文档:快速上手 + 详细设计 |
.env.example |
环境变量模板 |
verify_server.py |
端到端握手 + 新工具探测 |
test_handshake.py |
stdio 握手 + 注册自测 |
test_readonly.py |
只读工具覆盖测试 |
test_live.py |
实连冒烟测试 |
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。