Skip to main content
Glama

ticket-writer MCP

一个用于 MagOneAI 的 MCP 服务器。报告者以自由文本形式提交功能请求;工作流会提出将其变为可执行项所需的几个问题,然后提交一张工单,说明问题、需要做什么、验收标准以及任何架构决策。

Jira、GitHub、GitLab 和 Linear 都出自同一条代码路径。

该服务器从不调用跟踪器。 它渲染创建问题(create-issue)请求并将其交回;工作流自己的 HTTP 节点负责发送。


工作流如何运行

free text  ──▶  check_request  ──"needs_clarification"──▶  ask the reporter ──┐
                     │                                                        │
                     │◀──────────────────── answers ─────────────────────────┘
                  "ready"
                     │
                     ▼
                render_ticket  ──"possible_duplicate"──▶  human confirms ──┐
                     │                                                     │
                     │◀────────── confirm_not_duplicate ───────────────────┘
              "ready_to_send"
                     │
                     ▼
          HTTP node: POST request.url  ◀── the only write in the workflow
                     │
                     ▼
              issue key + url back to the reporter

WORKFLOW.md 包含逐节点的契约:精确的输入和输出 JSON,以及最终出现在工单上的每个字段。


Related MCP server: refine-backlog-mcp

为什么它不直接连接 Jira

第一版草稿中有一个 Jira REST 客户端。那个版本需要在这台机器上保存凭据,产生了一种故障模式——工单渲染正常但 POST 失败——并且将工作流锁定在单一跟踪器上。

改为渲染请求意味着:

  • 此处无凭据 —— 请求头携带 {{PLACEHOLDER}} 名称,由 API 节点从 MagOne 的密钥库中替换

  • 任意跟踪器 —— 新增一个跟踪器就是在 src/targets.py 中添加一个字典条目

  • 复用 MagOne 已有的能力 —— 如果已连接某个跟踪器 MCP,使用 target="generic" 并将其字段传给该跟踪器的创建工具

  • 可以加入审批步骤 —— 在渲染调用完成之前不会写入任何内容

  • 仅用 pytest 即可测试,无需其他依赖 —— 整个仓库中没有网络调用

它也不撰写工单正文。工作流代理是一个语言模型,在将一条冗长的 Slack 消息转化为问题陈述方面,比本仓库中任何规则都更擅长。此服务器负责的是每次运行都必须行为一致的部分:检查清单、问题措辞、正文格式和重复防护。


工具

工具

用途

check_request

这些信息是否足以提交工单?如果不够,该问什么。

render_ticket

创建问题(create-issue)请求,按某个跟踪器的格式渲染。

list_targets

跟踪器、配置键、API 节点需要哪个凭据。

duplicate_search_query

可选。构建用于重复检查的搜索查询。

这四个工具都是只读的。每个响应都携带一个 status,画布根据它进行切换:

status

工作流动作

ready

继续执行 render_ticket

needs_clarification

提出 questions,循环

possible_duplicate

显示 candidates,获取人工答复

ready_to_send

将 request 传给 API 节点

error

读取 hint —— 通常是缺少某个 config 键


什么使工单完整

check_request 会阻塞在四个字段上,按此顺序询问,每轮最多三个:

  1. problem —— 今天什么在造成困扰,谁受影响

  2. goal —— 完成时应该存在什么,以行为描述

  3. acceptance_criteria —— 评审者如何接受或拒绝它

  4. architecture_notes —— 已做的决策、需遵守的约束 (当设计仍开放时,"none known" 是有效的)

affected_users 和 out_of_scope 在用户主动提供时会被收集,但从不阻塞。少于 25 个字符的答案,或只是复述问题的答案,不计为已回答。

要更改检查清单,请编辑 src/ticket.py 中的 SLOTS —— 问题、排序和上限都由此列表决定。


目标

target

config

API 节点提供的凭据

jira

base_url, project_key

{{JIRA_BASIC_AUTH}} —— base64 编码的 email:api_token

github

owner, repo

{{GITHUB_TOKEN}} —— 具有 Issues 读写权限的 PAT

gitlab

project_id, host?

{{GITLAB_TOKEN}} —— 具有 api 范围的令牌

linear

team_id, project_id?

{{LINEAR_API_KEY}}

generic

—

无 —— 将 fields 交给该跟踪器自己的 MCP

渲染器吸收的差异,使代理永远不必处理:

  • Jira 在 description 中需要 ADF,而不是 markdown。传入 markdown 字符串是手写 Jira 节点最常见的失败方式。

  • GitLab 希望标签是逗号字符串;GitHub 和 Jira 希望是列表。

  • GitHub 没有优先级字段,因此优先级变成 priority-* 标签。

  • Linear 是 GraphQL —— 一个端点,变更(mutation)在请求体中。

添加跟踪器:一个 TARGETS 条目,带有一个返回 (url, headers, body) 的 build()。这就是全部改动。


重复防护

此服务器无法搜索,因此由工作流为它提供数据:duplicate_search_query 构建查询,搜索节点执行它,命中的结果作为 existing_issues=[{key, summary, url}] 传回 render_ticket。摘要的 token 重叠度 ≥ 0.6 的会以 possible_duplicate 返回。

没有搜索节点,就没有检查——工单仍然会渲染。这是不拥有每个跟踪器的搜索客户端的代价。


运行它

python -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/python -m pytest -q          # 47 tests, no network, no account

通过 stdio 的本地 MCP,用于 Claude Desktop:

MCP_TRANSPORT=stdio .venv/bin/python -m src.server

部署:

docker build -t ticket-writer .
docker run -p 8000:8000 -e TICKET_WRITER_TOKEN=$(openssl rand -hex 32) ticket-writer
curl localhost:8000/health

在 MagOneAI 中注册 https://your-host/mcp,并携带请求头 Authorization: Bearer $TICKET_WRITER_TOKEN,配置方式与 Outlook MCP 完全相同。TICKET_WRITER_TOKEN 是服务器唯一必需的环境变量;MCP_TRANSPORT、HOST、PORT 和 LOG_LEVEL 都有可用的默认值。将 max_iterations 设置为 12 左右。


提交一张真实工单来验证

scripts/send.py 所做的正是 API 节点所做的——渲染、从同名环境变量中替换占位符、POST:

.venv/bin/python scripts/send.py --target jira \
  --config base_url=https://you.atlassian.net project_key=KAN --dry   # payload only

export JIRA_BASIC_AUTH=$(printf '%s' 'you@mail.com:API_TOKEN' | base64)
.venv/bin/python scripts/send.py --target jira \
  --config base_url=https://you.atlassian.net project_key=KAN         # 201 + issue key

TESTING.md 涵盖每个测试套件证明的内容、针对 Jira Cloud 的实机运行,以及错误表。


安全

  • Bearer 令牌来自环境变量,绝不通过工作流节点传递——经过画布的令牌会出现在运行日志中。

  • 任何跟踪器凭据都不会到达此服务器。 请求头是占位符;config 接受位置而非机密。有一条测试断言了这一点。

  • /health 为平台探针提供未认证访问;其他一切都需要认证。如果未设置令牌,服务器拒绝通过 HTTP 启动。

  • 非 root 容器用户。

  • 报告者文本是数据,不是指令。 它按原样存储在块引用中,从不被解释。工具文档字符串中说明了这一点,因为代理读取的正是这些内容。

  • 重复防护和完整性检查是挡在嘈杂的 Slack 频道和上百张垃圾工单之间的屏障。不要添加跳过这两者的标志。


布局

src/server.py    MCP surface: tool defs, transport, auth
src/ticket.py    pure: rubric, questions, body in markdown + ADF, dup scoring
src/targets.py   pure: what each tracker's API wants — one entry per tracker
tests/           47 tests: the rules, the tool surface, HTTP and auth
scripts/send.py  stands in for the API node, for end-to-end checks
WORKFLOW.md      node-by-node input/output contract
TESTING.md       what is covered, what is not

ticket.py 对任何跟踪器一无所知;targets.py 对什么构成好工单一无所知。如果添加必填字段意味着要编辑 targets.py,那么这个拆分已经泄漏了。

Related MCP Connectors

Related MCP Servers