- 人工智能
- AI Agent
- 多智能体
- 后端
- 前端
【免费下载链接】relay
Relay 是一个多智能体协作平台,通过统一的界面进行协作开发、代码审查、任务管理,将多个 AI Agent 组织成一个真正的团队。
本文是 Relay 多智能体协作平台中 MCP Tool Description 规范 的完整技术解读与实践指南。它规定了平台上每一个
office_claw_*MCP 工具(协作、记忆、调度、富消息等)在注册时必须遵守的 description、inputSchema 与错误返回标准,直接决定了智能体能否"用对"工具而不是"自己 DIY"导致消息丢失。读完本文,你将掌握五要素 description 模板、zod schema 的书写约束、三段式错误消息规范,以及如何对照审查清单与注册测试(tool-registration.test.js)完成一次合格的 MCP 工具上线。
为什么 description 会成为多智能体平台的"生死线"
Relay 将多个 AI Agent 组织成一个真正的团队,它们通过packages/mcp-server暴露的 MCP 工具与平台交互。MCP 规范只定义了工具的外壳(name/description/inputSchema/ handler),而工具的正确用法完全靠 description 文本传达给模型。
在 Relay 中,description 不是给人看的注释,而是被模型直接消费的路由信号。一次失败的调用会沿着 callback → 出站 pipeline → IM 的链路传播,最终表现为"飞书收不到文件"这类用户可见故障。文档中记录的历史事故正说明了这一点:某工具只写了"生成文档并保存为 RichBlock",没说"什么时候用"、没说"不要手动拼create_rich_block"、没说"产物会自动投递到 IM",结果模型看不到正确用法 → 自己 DIY → 平台侧收不到产物。
因此文档开头就给出了明确态度:写新 MCP tool 或优化现有 tool 时必读,description 五要素"缺一个就是不合格"。
Description 五要素与模板:让模型"知道怎么用"
五要素定义
每个工具 description 必须回答五个问题:
| 要素 | 回答的问题 | 作用 |
|---|---|---|
| 1. 做什么 | 一句话能力描述 | 让模型判断"这个工具是干嘛的" |
| 2. 什么时候用 | 触发关键词 / 用户常见表述 | 让模型把用户意图路由到正确工具 |
| 3. 不做什么 / 不适用场景 | 排除错误路由 | 防止模型把 A 工具当 B 工具用 |
| 4. 产物 | 调用后用户会看到什么 | 管理模型对副作用的预期 |
| 5. GOTCHA | 常见陷阱 / 和相似工具的区别 | 阻断已知的错误用法 |
标准模板
{一句话:做什么 + 对谁有用}. Use when: {用户说"生成报告""导出 PDF""帮我写份文档"等}. NOT for: {不适用场景,和相似 tool 的区别}. Output: {产物描述:文件类型 / 附着方式 / 副作用}. GOTCHA: {陷阱提醒,例如"不要用 create_rich_block 手动拼"}.好 vs 差的 description 对照
文档给出了同一工具(Markdown 生成文档并投递到 IM)的两版 description:
差(OpenCode 踩坑的根因):
Generate a document (PDF/DOCX/MD) from Markdown content using Pandoc. The generated file is automatically saved and attached to the current message as a file RichBlock.问题逐一对照:没说"什么时候用"、没说"不要用 create_rich_block 手动拼"、没说产物会自动投递到 IM。结果就是 OpenCode 看不到正确用法 → 自己 DIY → 飞书收不到文件。
好:
Generate a document (PDF/DOCX/MD) from Markdown and deliver to IM platforms. Use when: user asks to "生成报告/导出文档/发PDF/写份文档给我". NOT for: sending existing files (use create_rich_block with kind:"file" + url). NOT for: generating images (use image generation tools instead). Output: DOCX/PDF/MD file saved to /uploads/, attached as file RichBlock, automatically delivered to Feishu and other IM platforms via outbound pipeline. GOTCHA: Do NOT manually call pandoc + create_rich_block — that skips IM delivery. Always use this tool for document generation.好版本同时覆盖了正路由(Use when)、反路由(NOT for)、产物与副作用(Output)、陷阱(GOTCHA),并把"兜底结论"(Always use this tool)写死,防止模型在多个相似工具间犹豫。
仓库中的落地范例
规范不是空谈,仓库里几乎所有office_claw_*工具都按这个结构编写。以 callback-tools.ts 中的office_claw_post_message为例:
Post a proactive async message to the OfficeClaw chat mid-task in the CURRENT thread (e.g. progress updates, sharing results). To simply @mention another agent at the end of your response, use @agent-name in your reply text instead — it is free and never expires. GOTCHA: This tool uses callback credentials that expire — if it fails with 401, fall back to inline @mention in your response text. GOTCHA: Do NOT use this for routine replies — only for mid-task proactive messages when you need to share something before your response completes.再看 rich-block-rules-tool.ts 的office_claw_get_rich_block_rules,它把"渐进式披露"策略与 GOTCHA 结合得很典型:
Get the full rich block usage rules (card/diff/checklist/media_gallery/audio/interactive). Call this BEFORE creating your first rich block in a session — it returns the full schema and constraints. You only need to call this once per session; the rules do not change within a session. GOTCHA: Without loading these rules first, you will likely produce invalid block JSON (wrong field names, missing required fields).这里"什么时候用"被写成Call this BEFORE ... first rich block,"GOTCHA"明确点出跳过后最可能的失败模式。而 schedule-tools.ts 中的office_claw_set_scheduled_task_enabled则示范了"和相似工具的区别"类 GOTCHA——它反复强调"暂停任务用 enabled=false,永久删除才用 remove_scheduled_task",直接把两个极易混淆的工具边界钉死。
inputSchema 规范:把参数约束写进 schema,而不是留给模型猜
六条核心原则
| 原则 | 做法 | 原因 |
|---|---|---|
| 能枚举就别自由文本 | format: enum(['pdf','docx','md']) | 自由字符串模型极易写错,枚举直接消灭一类错误 |
| 字段描述带单位/格式 | "fileSize in bytes"、"ISO 8601 timestamp" | 消除单位歧义,模型不必猜 |
| 禁止额外字段 | additionalProperties: false | 防止模型发明不存在的参数 |
| 字段名不同义词 | start_index或offset,只选一个 | 同义词并存会分裂模型记忆 |
| 必填标清楚 | required: ['markdown', 'format', 'baseName'] | 让模型一次调用就带全必填项 |
| describe 写清约束 | .describe('Display name without extension, e.g. "调研报告"') | 在参数层面给出示例 |
仓库中的 zod schema 实践
Relay 的 MCP 工具用zod定义 inputSchema,每个字段都通过.describe()携带约束与示例。看 callback-tools.ts 中office_claw_post_message的 schema:
export const postMessageInputSchema = { content: z.string().min(1).describe('The message content to post'), replyTo: z.string().optional().describe('Optional message ID to reply to'), clientMessageId: z .string() .min(1) .max(200) .optional() .describe('Optional idempotency key for at-least-once delivery de-duplication'), targetAgents: z .array(z.string().min(1)) .optional() .describe( 'Optional explicit target agent IDs (e.g. ["codex","gpt52"]). Merged with @mentions parsed from content. Used for direction rendering in frontend.', ), };这里每一个 describe 都写清了语义(做什么用)和格式(长度上限、数组元素、示例值)。再看 schedule-tools.ts 的office_claw_register_scheduled_task,它把"枚举 + 单位 + 倾向建议"三件事一次讲透:
trigger: z .string() .describe( 'Trigger config as JSON string. Choose type by intent: ' + 'interval — "every N hours/minutes" repeating from now, e.g. {"type":"interval","ms":7200000}. interval.ms must be >= 10000 (10s); ' + 'cron — specific wall-clock times/days, e.g. {"type":"cron","expression":"0 9 * * *","timezone":"Asia/Shanghai"}; ' + 'once — fire once after delay or at exact time. PREFER delayMs (relative) over fireAt (absolute) because model clocks may drift, e.g. {"type":"once","delayMs":120000}. delayMs must be >= 1000 (1s); fireAt must be a future epoch ms. ' + 'PREFER interval over cron when user says "every N hours/minutes".', ),这条 describe 同时体现了三条规范:单位明确(ms、>= 10000)、示例具体(给出了三种 type 的完整 JSON 示例)、最佳实践内置("模型时钟可能漂移,优先相对时间")。
枚举约束也有大量实例,例如任务状态status: z.enum(['todo', 'doing', 'blocked', 'done'])(callback-tools.ts)、workflow 阶段stage: z.enum(['kickoff', 'impl', 'quality_gate', 'review', 'merge', 'completion'])(callback-tools.ts)、检索模式mode: z.enum(['lexical', 'semantic', 'hybrid'])(evidence-tools.ts)。能枚举就别自由文本在 Relay 中已被严格执行。
describe 与运行时校验的配合
schema 不只是给模型看的说明书,它还直接参与运行时校验。office_claw_post_message在 handler 里用postMessageRuntimeInputSchema.safeParse()对归一化后的输入做二次校验,失败时把 zod 的每个 issue 拼成错误返回(callback-tools.ts):
const parsedInput = postMessageRuntimeInputSchema.safeParse(normalizedInput); if (!parsedInput.success) { const issues = parsedInput.error.issues .map((issue) => `${issue.path.join('.') || 'input'}: ${issue.message}`) .join('; '); return errorResult(`Invalid input for office_claw_post_message: ${issues}`); }这套"schema 定义 + describe 说明 + safeParse 运行时校验"的闭环,正是规范落地到代码的完整形态。
错误返回规范:三个信息缺一不可
规范要求错误消息必须包含:
- 哪个字段错了
- 期望的类型/范围
- 一个正确示例
文档中的对照示例:
// 差 return errorResult('Invalid format'); // 好 return errorResult('Invalid format "xlsx". Expected one of: pdf, docx, md. Example: format="docx"');坏消息只有"Invalid format",模型完全无法自救;好消息同时给出实际值(xlsx)、合法集合(pdf/docx/md)、修复示例(format="docx"),模型拿到后可以直接改正重试。
仓库里几乎每个 errorResult 都遵循"实际值 + 期望 + 示例"的结构。看 schedule-tools.ts 的validateTriggerConfig——对非法triggerJSON 逐项给出可操作提示:
Invalid trigger JSON — interval trigger ms must be a finite number >= 10000 Invalid trigger JSON — once trigger requires either delayMs or fireAt Invalid trigger JSON — unsupported trigger type: ${type}file-tools.ts 定义的errorResult()统一以isError: true标记返回,供上层判断失败;而 handler 内联的错误分支(如Access denied: ${filePath} is not within allowed directories)同样遵循"字段/路径 + 期望边界"的结构。规范的另一条隐性要求也在代码中体现:错误要可路由、可重试——模型必须能从报错里推断出下一步。
审查检查清单:上线前的九项硬检查
写完或改完 MCP tool 后,逐项对照:
- description 有"做什么"
- description 有"什么时候用"(含用户常见表述 / 中英文关键词)
- description 有"不做什么 / 不适用"
- description 有"产物 / 副作用"
- description 有 GOTCHA(和相似 tool 的区别)
- inputSchema 参数都有
.describe() - 枚举值用
enum不用自由文本 tool-registration.test.js已更新(EXPECTED_TOOLS / EXPECTED_COLLAB_TOOLS)- SystemPromptBuilder MCP_TOOLS_SECTION 已更新
前七项是描述与 schema 本身的品质检查,后两项是注册与提示词层的联动,在 Relay 中有明确的源码落点。
注册测试:EXPECTED_TOOLS 是回归守门员
tool-registration.test.js 定义了EXPECTED_TOOLS全量清单与EXPECTED_COLLAB_TOOLS(协作面)清单,测试通过createServer()后比对server._registeredTools,既有"每个预期工具都已注册"的正向断言,也有"不允许出现预期之外工具"的反向断言。文件注释记录了这条规则的由来:request_permission/check_permission_status的 handler 和 schema 早已存在,但createServer()漏了server.tool()注册——正是靠这个"守住注册层"的回归测试,修复前 Red、修复后 Green。所以规范要求新增工具必须同步更新这两个清单,否则全量测试会直接失败。
另一个值得注意的联动是 server-toolsets.ts:其中维护了COMPACT_DESCRIPTIONS紧凑版描述表,供 GLM-5 196K 这类窄上下文模型使用——因为 OpenAI ChatCompletions 会把工具定义序列化进prompt_length,完整五要素描述会浪费大量 token,compact 模式只保留每个 description 的第一句。这说明五要素是"完整形态"而非唯一形态,规范还要考虑不同模型的上下文预算。
SystemPromptBuilder:MCP_TOOLS_SECTION 的单一真相源
SystemPromptBuilder.ts 中的MCP_TOOLS_SECTION是注入每个智能体系统提示词的 MCP 工具摘要,按"记忆工具(先搜后问)→ 记忆 drill-down → 协作工具 → 共享 Skills"分块列出,并标注关键路由规则(如office_claw_multi_mention必须先搜后问、需 searchEvidenceRefs 或 overrideReason)。新增工具后不更新这里,智能体的系统提示词里就看不到它——这正是清单第九项的深层原因:工具注册(MCP 层)与提示词可见性(SystemPrompt 层)必须同步,否则工具"存在但不可用"。
规范在 Relay 全链路中的位置
把本规范放在 Relay 的运行链路中看,它的价值会非常清晰:
- schema 定义阶段:按五要素 + zod
.describe()写全描述与约束; - 注册阶段:
server-toolsets.ts通过registerTools()调用server.tool(name, description, inputSchema, handler)注册(server-toolsets.ts),环境变量OFFICE_CLAW_MCP_EXCLUDED_TOOLS支持按名屏蔽工具; - 模型推理阶段:description 作为系统提示词的一部分进入上下文,模型据此路由;
- 调用与校验阶段:zod
safeParse做运行时校验,errorResult返回三段式错误; - 回归守门阶段:
tool-registration.test.js确认注册完整、无多余工具,SystemPromptBuilder 保持提示词可见性同步。
任何一个环节断裂,都会演变成文档开头描述的那类故障。因此这份规范实际上定义的是 Relay 中 MCP 工具的最低可交付标准——它不是给"写出优雅描述"的建议,而是保证多智能体系统不出错的过程约束。实践中建议把五要素模板作为所有新工具的起点,把审查清单作为合并前的强制关卡,并让注册测试成为"未更新即红"的自动化护栏。
- 人工智能
- AI Agent
- 多智能体
- 后端
- 前端
【免费下载链接】relay
Relay 是一个多智能体协作平台,通过统一的界面进行协作开发、代码审查、任务管理,将多个 AI Agent 组织成一个真正的团队。
相关推荐
QuickRecorder macOS 录屏快速上手:30秒录下全屏、窗口与系统声音
QuickRecorder macOS 录屏快速上手:30秒录下全屏、窗口与系统声音 QuickRecorder 是一款基于 ScreenCapture Kit
桌面应用音视频屏幕录制编写 Lore How-To 指南:模板结构、规范约束与发布检查清单
编写 Lore How To 指南:模板结构、规范约束与发布检查清单 导读 :How To 是 Lore 文档体系中面向"目标明确、需要配方"型读者的实操文档类
版本控制后端OpenSSF Scorecard 错误处理实践指南:公共错误、内部错误与标准返回模式
OpenSSF Scorecard 错误处理实践指南:公共错误、内部错误与标准返回模式 本指南围绕 OpenSSF Scorecard 仓库中的错误处理设计文档
供应链安全开源治理
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考