--- name: zentao-shared description: "禅道 MCP 共享基础:payload 包装约定、路径/查询参数规则、ID 层级关系、状态字段规则、确认约定。所有 zentao-* 技能必须先读本文件。" --- # 禅道 MCP 共享规则 所有 `zentao-*` 技能的**必读前置**。 --- ## 一条最重要的约定:参数以工具自身的 inputSchema 为准 **本文件与各技能文档都不重画完整字段表。** 每个工具的字段名、必填项、取值范围以它在 MCP 里注册的 `inputSchema`/`description` 为唯一真相;技能只描述**调用顺序、ID 如何传递、 payload 怎么包、哪里必须停下来等用户确认**。 > 上一代其他插件(寰汐)踩过这个坑:技能文档手画参数表,字段名/枚举值/必填项跟后端 > docstring 逐渐漂移,用户侧表现为频繁的「参数缺失」报错。工具签名变了而文档没跟上, > 是必然发生而非可能发生的事——禅道这边直接不画表,从源头避免。 --- ## 工具命名 Claude Code 里工具名带前缀:`mcp__zentao__post_bugs`;其他平台通常是裸名 `post_bugs`。 本文档统一写**裸名**,实际调用时按你所在平台的约定加前缀。 命名规律是 `{method}_{路径按 / 拆分拼接}`,路径参数去掉冒号: ``` GET /bugs/:bugID → get_bugs_bugID POST /bugs → post_bugs PUT /tasks/:taskID/start → put_tasks_taskID_start DELETE /stories/:storyID → delete_stories_storyID GET /products/:productID/stories → get_products_productID_stories ``` --- ## 路径参数完全不出现在 inputSchema 里(最隐蔽的坑) **`inputSchema` 里只有 `payload`(有请求体的话)和 query 参数,路径参数(`bugID`/ `productID`/`taskID`……)不会被声明为任何属性**——用真实工具现场验证过: `get_bugs_bugID`/`delete_bugs_bugID`/`get_products_productID` 的 `inputSchema.properties` 都是空的。想传路径参数,**必须自己从工具名最后一段解析出参数名**(`get_bugs_bugID` → 要传 `bugID`),schema 本身看不出这个要求。 **更麻烦的是不传或传错不会报错**:现场测试 `get_bugs_bugID({})`(故意不传 `bugID`) 返回的是 `HTTP 200`、内容为空字符串,跟正常"这个 ID 查不到东西"长得一样,非常容易被 误判成"这条数据不存在"而不是"参数传漏了"。调用返回空结果时,**先检查是不是路径参数 传漏了或传错了名字**,再怀疑数据本身。 **命名还有例外,不能全靠"猜工具名去掉动词就是参数名"**:`get_epics_storyID` / `get_requirements_storyID`(业务需求/用户需求详情)的路径参数**字面就叫 `storyID`**, 不是更直觉的 `epicID`/`requirementID`——这是禅道官方 API 本身的历史命名,照抄字面 参数名,不要自己"纠正"。 --- ## 参数结构:payload 包装约定 请求体字段也**不会摊平在 inputSchema 顶层**,而是统一包在一个 `payload` 对象里; 路径参数(上一节说的,虽然不在 schema 里但仍要传)和 query 参数才是顶层字段。三种形态: ``` POST(新建,无路径参数): post_bugs({ payload: { productID: 2, title: "...", openedBuild: ["trunk"] } }) PUT(改,带路径参数): put_bugs_bugID({ bugID: 123, payload: { title: "..." } }) put_tasks_taskID_start({ taskID: 456, payload: { realStarted: "2026-08-25" } }) GET(查,query 参数摊平在顶层,没有 payload——这类工具可选参数多,其余技能文档统一用 "函数签名速查"写法举例,不是真的按位置传参,仍然是每个字段按名字传): get_products_productID_stories(productID: 2, browseType: "allstory", recPerPage: "50", pageID: "1") DELETE(删,通常只有路径参数): delete_bugs_bugID({ bugID: 123 }) ``` **把 body 字段错误地摊平到顶层(不包 payload)是最常见的调用失败原因**,报错通常表现为 "参数缺失"——先检查是不是漏包了 `payload`。 --- ## 分页 **不是所有 GET 都分页**,列表类接口(约一半)才有,详情类没有。有的话固定是这两个参数: - `recPerPage`:每页数量,不超过 1000 - `pageID`:页码,从第 1 页开始 **没有 `limit` 这个参数名**——传了不存在的参数名会被静默丢弃(不报错、不生效),日志里 `upstream_query_params` 会是空的,看起来"调用成功但没起作用",容易误判。默认页大小以 具体工具的 inputSchema 说明为准,不要凭经验假设。 --- ## ID 层级关系 ``` program 项目集(可选,product 不强制挂靠) └─ product 产品(创建只需 name 必填,program/其他都可选) ├─ project 项目(创建必填 name/model/begin/end/workflowGroup;model 取值受限: │ scrum 敏捷 | waterfall 瀑布 | kanban 看板 | agileplus 融合敏捷 | waterfallplus 融合瀑布) │ └─ execution 执行/迭代(创建必填 project,即 execution 必须先有 project 才能建, │ 不能直接挂在 product 下) ├─ story 用户故事 / epic 业务需求 / requirement 用户需求(三条需求线,见 zentao-story) ├─ bug ├─ testcase 测试用例 / testtask 测试单 ├─ productplan 产品计划 / build 版本 / release 发布 ├─ feedback 反馈 / ticket 工单 / system 应用 └─ task 任务(挂在 execution 下) ``` 创建下级实体前,先查上级列表拿到 ID 展示给用户确认,不要凭名字猜 ID(`get_products` → `get_products_productID_stories` 这类"先列表后详情/子资源"的两步调用是常态)。 **没有 `GET /projects/:projectID` 单条项目详情接口**——只有 `get_projects` 列表,要看 某个项目的信息,从列表里按 ID 过滤,不要尝试拼一个不存在的详情工具名。 --- ## 状态字段 **禅道各实体的状态是模块内固定的字符串常量**,不是可配置的两层模型——具体取值以对应 工具的字段说明为准(比如 story 是 `draft/active/closed/change`,bug 是否 resolved/closed 各有专门的 `put_*_*ID_resolve` / `put_*_*ID_close` 端点)。**不要凭直觉写状态字符串**, 这些字段大多有 DB 级约束,写错直接报错,具体状态机在各自的 zentao-story/zentao-bug/ zentao-task 里有说明。 --- ## 鉴权 MCP 连接用的 Token 在禅道网页「头像下拉菜单 → 获取凭证」自助生成,**14 天有效期**,到期 需要重新生成并更新插件配置里的 Token。如果调用突然全部 401,先怀疑 Token 过期。 --- ## 全局确认约定 1. **状态流转类操作必须先确认**:关闭(close)、解决(resolve)、激活(activate)、 变更(change)这类端点,执行前把要提交的内容/目标状态展示给用户,等到明确确认 ("确认"、"关闭它"、"好的")再调。不要因为用户说了"这个 bug 修完了"就顺手把 resolve 也做了——修复和标记解决是两个决定。 2. **删除是不可逆操作,必须先确认**:所有 `delete_*` 工具删的都是真实数据,没有回收站, 执行前明确告知会删除什么。 3. **先解析 ID 再操作**:需要 `productID`/`executionID`/`taskID` 这类 ID 的操作,先用 对应的 `get_*` 列表工具查出来给用户看,不要凭名字或印象猜 ID。 4. **附件目前只支持改名**:`put_files_fileID` 只能改附件文件名(`fileName` 字段), 上传和删除附件不在这套 MCP 工具里,需要引导用户去网页端操作。