状态:已实施(2026-10-29),五个阶段全部完成,实施记录见 §9。决策见 ADR-0025。面向 MCP 服务开发者的说明见《MCP 接入指南》(
docs/guides/mcp-guide.md)。
第三方 MCP 服务经 MCP Gateway 转接给两个引擎(ADR-0021)。此前只用到了 MCP 的"工具"部分,本次按 MCP 2025-11-25 规范补齐:
| 阶段 | 内容 |
|---|---|
| A | 工具提示(annotations)、结构化输出(structuredContent / outputSchema)、工具列表变化通知(list_changed) |
| B | 长任务(Tasks),与后台调用合并 |
| C | 向用户询问信息(Elicitation) |
| D | OAuth 授权(按服务共享) |
| E | 服务端的资源(Resources)与提示模板(Prompts) |
不做:Sampling(服务借用平台的模型)——花平台的 token、外部服务可以借此驱动模型,安全与费用都难以控制。
原则:只实现规范中定义的机制,不自定义协议字段;规范中的可选项按本文选定的方式使用,写进《MCP 接入指南》(docs/guides/mcp-guide.md)。
服务在工具定义中声明的提示(规范中均为"提示",不保证可信):
| 提示 | 规范默认值 | 平台的用法 |
|---|---|---|
readOnlyHint |
false | 只读:默认直接调用;只读模式下只提供只读工具(原有) |
destructiveHint |
true(仅对非只读工具有意义) | 明确声明为 true 的工具默认"总是审批":自动模式下也要审批 |
idempotentHint |
false | 只读或幂等的工具遇到网络错误时重新连接后重试一次;其他工具不重试,避免重复执行 |
openWorldHint |
true | 后台工具列表中标出;审计记录中带上 |
title |
— | 后台显示名 |
destructiveHint 不能直接用来强制审批,否则所有没写提示的工具在自动模式下都要审批;所以只认明确声明的 true。confirm):不论权限模式都询问(只读模式下非只读工具仍然不提供)。明确声明破坏性的工具默认为这一档,管理员可以改。toolHints),覆盖值优先。structuredContent 时:文本中已经包含同样的 JSON(规范建议服务这样做)就不重复;否则把 JSON 附在文本后面交给模型。只有 structuredContent 时转为文本(原有)。超过长度上限的保存为工作区文件,交给模型路径。outputSchema 时,按 schema 校验 structuredContent(规范要求客户端校验)。不合格时:结果照常交给模型,审计记录中带上校验错误。outputSchema 原样转给引擎(引擎的 MCP 客户端会据此严格校验,网关改写过的结果可能通不过),而是在工具说明末尾附上返回结构的简述(截断到 1500 字),让模型事先知道返回什么。notifications/tools/list_changed;收到后(合并 2 秒内的多次通知)重新列出工具并更新缓存。toolsUpdatedAt:它是 Codex 会话的工具版本之一,变化会让会话在下一轮分叉、重新获取工具列表(ADR-0020、ADR-0021),没有变化时不应触发。后台调用(docs/design/15-background-calls.md)增加"等待方式":
| 等待方式 | 条件 | 平台怎么等 | 服务重启 |
|---|---|---|---|
wait(原有) |
工具不支持任务 | 保持连接,等工具返回 | 标记失败,通知发起方 |
task(新增) |
服务声明 tasks.requests.tools.call,工具的 execution.taskSupport 为 required 或 optional |
保存任务 id,按 pollInterval 调用 tasks/get |
继续查询 |
记录表、唤醒消息、取消、顶部的后台调用栏、后台"后台调用"页面、工作组子任务等待都共用。
tools/call 带 task: { ttl },ttl 为后台调用的最长时间),立即得到任务 id。backgroundCalls.autoAfterSec(默认 180 秒):期间完成的,取回结果直接返回给模型,和普通调用一样。_meta["io.modelcontextprotocol/model-immediate-response"] 中提供的说明一并交给模型。pollInterval(最短 5 秒、最长 5 分钟,未提供时 30 秒)查询;到达终态(completed / failed / cancelled)后用 tasks/result 取回结果,经过与普通调用相同的结果处理,作为一条新消息唤醒发起方。input_required 时调用 tasks/result,服务借此发出询问(阶段 C),用户回答后任务继续。tasks/cancel(尽力而为),记录为已取消。maxPerTarget)时:在前台查询直到完成,最多 10 分钟(与普通调用相同)。相关代码集中在 mcp-gateway/tasks.ts:Tasks 仍是规范中的实验性功能,以后版本调整时只改这一处。
连接上游服务时声明 elicitation: { form: {}, url: {} }。
| 模式 | 平台的处理 |
|---|---|
表单(form) |
按 requestedSchema 生成表单卡片:文本(含 email / uri / date / date-time 格式)、数字、是否、单选、多选,带默认值、必填、长度与范围限制;提交时服务端再校验一次 |
网址(url) |
链接卡片:显示说明和完整域名,用户点开后自行完成,再点"已完成"或"拒绝";平台不自动打开网址 |
用户的选择对应规范的三种结果:提交(accept,带内容)、拒绝(decline)、关闭(cancel)。
form(InteractionFormRequest),走现有的交互流程:会话事件 interaction.required、Agent API / Bot API 的回复接口、超时(limits.interactionTimeoutSec)按"关闭"处理。{ action: 'accept', content }、{ action: 'decline' }、{ action: 'cancel' }。规范中询问请求本身不带"由哪次调用发起"的信息,而上游连接是多个会话共用的。为了不把一个会话的询问显示到另一个会话:
decline),审计中记录原因。io.modelcontextprotocol/related-task,按任务 id 找到发起方;后台查询使用的是不属于任何会话的连接,只认任务 id。runId 为空),照常显示和回复。<后台地址>/admin/v1/mcp/oauth/callback,平台用授权码换取令牌。AGENT_SERVER_TRUST_PROXY)。| 用法 | 说明 | token 消耗 |
|---|---|---|
| 模型按需读取 | Profile 勾选的服务中有声明资源能力的,网关提供两个工具:mcp_resources(server?, cursor?) 列出资源和资源模板(分页,只有目录信息);mcp_resource_read(server, uri) 读取 |
只在读取时,只有读到的部分 |
| 用户附加 | 会话输入框"附加资料":选择服务和资源,平台读取后保存到会话的 uploads/,作为附件发出 |
这一轮附件的篇幅 |
.agent/mcp/ 并返回开头和路径;二进制一律保存为文件。docs://{product}/{topic}):列出时给出模板和说明,模型填好参数后用 mcp_resource_read 读取。resources/subscribe)、Profile 固定附加资源。需要时再加。prompts/get,把得到的内容放进输入框,用户可以修改后发送。GET /v1/sessions/:id/mcp/prompts、POST /v1/sessions/:id/mcp/prompts/get;资源:GET /v1/sessions/:id/mcp/resources、POST /v1/sessions/:id/mcp/resources/attach。examples/mcp-reference-server/:用官方 SDK 写的示例服务(虚构的订单系统),实现本文的工具提示、结构化输出、长任务(含任务中询问)、表单与网址询问、资源与模板、提示模板、工具列表变化。用于线上端到端验收,也作为接入指南的示例代码;test/mcp-reference.test.ts 启动它并经网关逐项检查,保证示例与平台的支持一致。
OAuth 不在参考服务中(需要一个授权服务器):由 test/mcp-oauth.test.ts 中的模拟授权服务器覆盖(元数据发现、动态注册、PKCE、刷新令牌)。
mcp_servers:toolHints(工具名 → 覆盖的提示)、authType(none / headers / oauth)、oauth(客户端信息、令牌、PKCE 校验码、状态;不出现在 DTO 中)。McpCachedTool:增加 title、annotations、outputSchema、taskSupport。background_calls:mode(wait / task)、taskId、pollAfter、workspaceDir、pollErrorSince。interactions:runId 可为空;kind 增加 form。McpToolPolicy 增加 confirm。| 阶段 | 代码 | 测试 |
|---|---|---|
| A 工具提示、结构化输出、列表变化 | services/mcp-servers.ts(生效提示、confirm 策略、toolHints 覆盖)、mcp-gateway/results.ts(结果处理、按 outputSchema 校验)、mcp-gateway/upstream.ts(list_changed) |
mcp-hints.test.ts |
| B 长任务 | mcp-gateway/tasks.ts、mcp-gateway/background.ts(mode = task) |
mcp-tasks.test.ts(前台完成、转后台、失败、取消、重启后继续) |
| C 询问用户 | mcp-gateway/elicitation.ts、services/interactions.ts(form 交互、runId 可为空、内容校验)、chat-ui/InteractionCard.vue |
mcp-elicitation.test.ts |
| D OAuth | services/mcp-oauth.ts、回调 GET /admin/v1/mcp/oauth/callback(不需要后台登录,凭一次性 state) |
mcp-oauth.test.ts |
| E 资源与提示模板 | services/mcp-content.ts、/v1/sessions/:id/mcp/*、chat-ui/McpPicker.vue(输入框的"资料"、"快捷指令") |
mcp-resources.test.ts |
实施中的调整:
client.callTool:SDK 的这个方法在返回结构与 outputSchema 不符时直接报错,改为 client.request 后自行校验,不合格时照常返回、记录在审计中(§2.2)。tools 能力,列出工具时直接返回空列表,不再当作连接失败。runId 为空),不改变 Run 状态,会话删除时取消。tool_hints)、0015(后台调用的任务字段)、0016(interactions.run_id 可为空、kind 增加 form)、0017(oauth)、0018(features)。