MCP App
一条服务就绪度工作流,只表达一次,却输出为宿主能触达的每一种表面:一个真实的本地 MCP 服务器、一个带 类型的工具、一个交互式 MCP App 资源、一个 Skill、一个 session-start 钩子、一个夹具检查脚本,以及一个 确定性 eval。当你想看清这些表面如何拼在一起、而不是单独研究其中之一时,就该读这个示例。
- 在仓库根目录运行:
pnpm example:mcp-app - 包名:
@agent-bundle-example/mcp-app - 公开依赖:
agent-bundle(workspace:*)、@agent-bundle/runtime、react、zod;浏览器端 App 测试还用到@rstest/browser、@rstest/core与playwright - Target:
portable、codex、claude,每个 target 都带有status服务器及其 App - 凭据: 不需要,eval 与夹具检查只读取签入的数据
- 源码:
examples/mcp-app
它证明什么
-
路由就是注册。 配置里没有写任何服务器、工具、资源或 App。
status服务器之所以存在,是因为src/mcp/status/**下有三个路由模块:tools/show-status.tsx(一个服务的健康状况)、resources/readiness-policy.tsx(位于policy://mcp-app-example/readiness的发布规则),以及apps/status.ts+status.html(工具通过_meta.ui.resourceUri打开的 App,用appResourceUri('status')只拼写一次)。show-status使用defineTool,让 App 绑定、schema 与 处理器留在同一个定义中。没有手写的McpServer、没有registerTool,也不依赖@modelcontextprotocol/server。见MCP 服务器与 MCP App。 -
生成的 stdio 生命周期不由你来写。 构建输出服务器入口及其生命周期外壳:console 重定向到 stderr 的 守卫、信号处理、stdin EOF 退出、有界关停与心跳。
-
MCP App 是编译出来的资源,不是被服务的页面。 App 只编译一次,位于产物根目录的
mcp-apps/status.html,带有稳定的resourceUri,并被内联进生成的status服务器入口,而所有选中 宿主都从同一个根目录启动这个入口。 -
降级证据是一等公民。
src/service-status.ts持有 schema 以及compiler与payments-api健康记录,而payments-api故意返回降级的延迟数据。有意思的输出是一项被如实报告的失败检查,而不是 一张全绿截图。 -
浏览器表面自成一个证明级别。
tests/browser-app/status-panel.browser.test.ts通过agent-bundle/test/browser把生产编译出的 App HTML 挂载到产品桥接之上,也就是 测试中的browser-app级别。 -
App 是桥接的客户端,而不是自带 transport。
src/mcp/status/apps/status.ts从agent-bundle/app创建客户端,通过onToolInput/onToolResult接收打开 App 的show-status输入与结构化结果,并用 注册在tool:status/show-status上的onToolError展示失败或缺少结构化数据的打开结果; 当宿主的 initialize 结果指定show-status为打开工具时,这些路由监听器才会触发;onToolCancelled则独立展示被宿主取消的开场调用。Refresh status 为屏幕上的服务再次调用同一个show-status工具,Read policy 通过request('resources/read', …)读取readiness-policy资源。路由 id、输入与结果由生成的.agent-bundle/routes.d.ts提供类型,因此 App 自己不声明任何契约 表。视图中不再保留 JSON-RPC id、pending map、postMessage('*')或structuredContent解包逻辑。详见 带类型的 App 客户端。 -
web表面装在产物里。 该示例配置了web: { apps: [{ app: 'status/status', tool: 'show-status', allow: ['call-tool'] }] }, 因此即使没有src/cli/**,组合根目录的agent-bundle.manifest.json也会带上web节,并输出bin/mcp-app-example.mjs。从已构建产物运行:它会打印就绪行,并在 loopback 上提供 status App。
agent-bundle dev在/web/status/status提供同一页面。见在浏览器中暴露 App。
编写了什么
在 Workbench 中操作
- 在 Application → Skills 下选中
service-readiness。它的文档默认渲染;用检查器查看源码/生成 输出、资源与 eval 覆盖。 - 在 Application → Events / Hooks 下选中已配置的
sessionStart钩子,并选择 Claude 夹具。运行它 以检视规范输入、返回的上下文,以及 Claude 投影。 - 在 Application → Scripts 下选中
check-service-fixture并运行。输出的检查器会解析自己输出模块 旁边打包好的状态夹具,因此它的成功与 shell 的工作目录无关。 - 在 Application → MCP → status → Apps 下选中
statusApp。工作区会针对已发布的构建打开一个会话, 并把show-status绑定到该 App;选择一个服务并点击 Call tool and preview,面板就会渲染在工具结果 旁边。Advanced → Artifact 显示复合插件根目录,无论选中哪个 target,mcp-apps/status.html都出现在 这里。 - 在存在两次 eval 运行之前,Advanced → Evals → Compare 会刻意显示
At least two recorded runs are needed before a comparison can be aligned.,这是精确的空状态, 不是错误。 - 打开 Advanced → Protocol,选择 portable 与
status服务器,打开会话、列出工具、选择show-status、选中payments-api并调用它。调用历史会显示降级摘要,以及标注了 Availability 与 P95 latency 的检查,其中后者失败。打开 App 预览:渲染出的面板通过 MCP Apps 桥接展示同一条记录, 并带一个以文字标注的琥珀色degraded指示;Refresh status 与 Read policy 调用的是真实的工具 与资源。检视协议轨迹、使用 Restart MCP session,然后关闭、 重置并重新打开会话,以演练整个生命周期。 随后打开 Trace,查看通过会话 id 与 JSON-RPC 请求 id 关联起来的 MCP 请求、响应、通知与会话活动。 - 在 Advanced → Evals → Runs 中选中
mcp-app-status,运行status-is-healthy,查看归属于service-readiness的那次已完成且通过的试次。
如果你修改了某个源文件,请重建并等到 Failed 或 Idle 状态再判断结果;Building 状态仍在进行中。
非交互检查
在仓库级 pnpm build 之后:
pnpm check 在不打开 Workbench 的情况下依次运行校验、构建和类型检查。
在 stdio 上运行服务器
mcp run 会从 portable target 的 MCP 清单解析出生成的入口,并先构建一份临时产物;传入
--artifact artifact 可改为复用 pnpm build 的输出。关闭 stdin 以 0 退出,Ctrl-C 以 130 退出,
每个服务器的状态持久化在 .agent-bundle/mcp-run/portable/status 下。
该命令默认会加载项目根目录的 .env 集合,包括所选 --mode 的变体。启动环境的优先级是清单 env,
然后是 .env 文件,最后是导出的操作者变量。可重复的 --env-file <path> 会替换那些约定文件,
--no-env 跳过它们,而 --plugin-root <path> 只用于复制产物后的演练。完整契约见
运行时环境。