Skip to main content

三条通道

让 Agent 调用 Mini App:托管 MCP

每个 MCP 服务器只能选一种生命周期:
  • 普通插件 MCP:独立运行(比如 stdio),不依赖 Mini App 的 Node 服务。不要把它写进 mcpEndpoints。
  • Mini App 托管 MCP:由 Mini App 的 Node 服务实现,只在服务运行时可用。适合和页面共享同一份业务状态。
托管 MCP 先在插件的 *.mcp.json 里声明一个 Streamable HTTP 服务器,url 用这个固定的占位地址,发布后由宿主改写:
servers.mcp.json
再在 miniapp.json 里把它挂到 Node 服务的路径上,并在 plugin.json 的 mcpServers 里引用 servers.mcp.json:
miniapp/miniapp.json
  • Node 入口必须在 start 返回前,在 context.listen 的对应路径上提供 Streamable HTTP MCP。宿主只改写这个服务器的地址,工具发现、排序、调用和错误仍由插件 MCP 运行时负责。
  • 用户发送一条引用了该 Mini App 的消息时,宿主会在请求模型之前启动服务、连好 MCP、确认工具可用。必需的托管 MCP 不可用时,这次请求直接失败,不会让模型在缺少工具时继续执行。
  • tools/list 应只返回工具定义,不在这里导入数据、扫描文档或调用远程服务。业务初始化放到真正需要它的工具调用里。
  • MCP 处理器故障不会影响页面,但在修复并重新发布之前,Agent 无法使用这些工具。

交给输入框

适合「整理好上下文,由用户决定是否发送」的场景,用法见 message.append。如果要让 Agent 修改表格、卡片等业务对象,给它提供 MCP 工具,并在请求和工具里用稳定的业务 ID。用户可以修改或删除输入框里的文字,因此不能把它当作请求和回合之间的关联 ID。

Agent API

Mini App 可以从页面(window.mcode.miniapp.agent)或 Node(context.agent)直接发起 Agent 回合。Agent 拥有完整能力:文件、Shell、Skill 和 MCP,包括这个 Mini App 自己的 MCP。完整签名、模型与问卷类型、会话字段和事件结构见 Agent API 参考,错误处理见错误码说明。

开启

miniapp/miniapp.json
  • 不写 agentAccess 就是禁止。可声明 "session.run"(运行回合)和 "session.read"(读取、观察用户会话),两者互不包含,也可以同时声明。
  • agentConcurrency 可选,用于限制这个 Mini App 同时运行的回合数,取 1 到 32,默认 4。超出的调用按顺序排队,队列最多是并发数的 4 倍。每个会话同一时间只有一个回合。
  • 授权以进程为单位生效:改了 agentAccess 并重新发布,服务会重启。
  • 用户可以在插件详情里分别关闭运行和读取权限,不用重启。关闭运行权限会中止本 Mini App 的在途回合;关闭读取权限会结束相应的观察订阅。接口对象仍在,调用方需要处理权限错误。

会话属于谁

这些会话不出现在用户的会话列表里。每个 Mini App 只能续用自己创建的会话,续用时 agent 和目录不变;另行声明 session.read 后可以读取和观察用户会话,但不能续跑它们。run 和 answer 可以传 model 切换模型,必须是用户在 models() 里能看到的模型。这些会话与聊天的关系见 Mini App 与会话。

方法

会话忙时跟进

续用会话时可以传 whenBusy,必须同时给出 sessionId:
  • queue:当前回合结束后,消息作为独立回合运行,返回该回合的结果。
  • steer:把消息交给正在运行的回合,调整它的工作。消息被读入后返回 status: 'steered',没有独立的 text;回复由原回合的调用接收。若原回合在读入前已经结束,消息会作为下一回合运行。
  • 会话空闲时,两种方式都直接开始新回合。不传 whenBusy 而会话正忙,页面返回 busy,Node 返回 BUSY。
  • 用户停止会话后,已有队列可能暂停,后续跟进会等待用户恢复。排队期间仍占运行名额;需要放弃时取消调用。已经交给运行中回合的 steer 消息不能撤回。

读取和观察用户会话

miniapp/miniapp.json
session.read 可以读取用户会话及其最终回复,应只在产品确实需要这些内容时声明。它不授予运行回合的权限。
  • sessions() 返回 { sessions },最多 50 条最近更新的用户会话;不列出 Mini App、定时任务和子 Agent 会话。
  • session(sessionId) 返回会话状态、项目目录、模型及可用的最后一条助手回复。页面不传 ID 的 session() 读取打开它的聊天;页面不属于聊天时返回 undefined。Node 没有当前聊天,必须显式传 ID。
  • observe({ all: true }, { onEvent }) 接收所有用户会话的 turn.started、最终回复 message.committed 和 turn.finished;指定 sessionId 则观察该会话的进度。审批和问卷不会交给观察者。
  • 观察不是一次运行,不占运行名额;每个 Mini App 最多 4 个订阅。页面用传入的 requestId 配合 cancel(requestId) 结束订阅;Node 用第二个参数的 signal。关闭页面、服务结束或读取权限被关闭也会结束相应订阅。
  • 读取、观察本 Mini App 自己的会话时,session.run 也可授权;不能读取其他 Mini App 的会话或子 Agent 会话。
调用前可用 capabilities?.has('agent.observe') 等检查宿主是否支持接口;这不代表 manifest 已声明或用户仍允许该权限。

审批和问卷

谁能回答审批

宿主先确认有人能回答,再让审批等待;没人能回答就直接拒绝。

  • permission.requested → reply:工具调用等待批准。只有「允许一次」和「拒绝」,没有「始终允许」,Mini App 不会写入用户的全局权限规则。
  • question.requested → answer:回合停在问卷上(ask_user、功能开启或 Plan 审阅)。等暂停的 run 返回之后再调用 answer。
  • 审批只在有人能回答时等待:发起调用时传了 onEvent,或者页面用 open 把会话显示在侧栏里。否则直接拒绝,并告诉模型没人能批准、不用这个工具继续。
  • 等待没有超时限制,调用通过取消、进程退出或重新发布释放。
  • Agent 委派给子会话时,子会话的审批也会通过 onEvent 到达,带着子会话自己的 sessionId。不要按根回合过滤事件,否则调用会一直等下去。

页面示例

Node 示例

在请求处理里调用 context.agent,不要在 start 里调用。服务重启会取消进行中的回合。

事件

下表概述常用事件;完整字段、忙时跟进事件,以及运行和观察事件的区别见 Agent 事件参考。忽略不认识的事件类型,后续可能会增加新类型。

设计建议

  • 把任务、数据范围、需要的交互、成功标准和停止条件都写进 text。页面上的标签、表单状态和宿主聊天的上下文不会自动带进去。
  • deny 只拒绝这一次工具请求,不代表这个文件以后都不能访问。需要跨调用生效的限制,应通过宿主权限或 Hook 规则实施。
  • 根回合 finished 不代表后台子任务都完成了。需要子任务结果时,在业务请求里使用前台委派。
  • AGENT_OUTCOME_UNKNOWN 表示回合可能已经执行过。先核对会话状态,不要自动重试。