三条通道
让 Agent 调用 Mini App:托管 MCP
每个 MCP 服务器只能选一种生命周期:- 普通插件 MCP:独立运行(比如 stdio),不依赖 Mini App 的 Node 服务。不要把它写进
mcpEndpoints。 - Mini App 托管 MCP:由 Mini App 的 Node 服务实现,只在服务运行时可用。适合和页面共享同一份业务状态。
*.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表示回合可能已经执行过。先核对会话状态,不要自动重试。