用途
这是一个用于在本地测试或调试智能体 UI(Agent UI)的技能,核心场景是排查 MCP 应用(如 ui:// 资源、iframe 组件)在宿主中不显示、渲染异常或行为不符预期的问题。它强调“四个层级、按需攀爬”,即只在 bug 需要时才上升到更高的验证层级。
主要能力
1. 协议层 —— MCP Inspector
在任何渲染问题之前,先确认服务器发出的结构是否正确。使用命令:
npx @modelcontextprotocol/inspector
按顺序检查:
resources/list中包含你的ui://…URI。- 该资源的
mimeType必须精确为text/html;profile=mcp-app。 - 资源
contents[0]._meta.ui.csp存在且范围收窄。 tools/list中,渲染工具带有_meta.ui.resourceUri,而数据工具不带。- 调用工具时同时返回
content(文本)和structuredContent。
大多数“组件不显示”的问题都在这一层就被发现。如果 Inspector 中没有显示 _meta.ui.resourceUri,任何宿主都不会渲染。
2. 渲染层 —— basic-host
ext-apps 仓库提供了一个最小宿主,能挂载 iframe 并说真实的 bridge 协议。
# 你的服务器
npm run build && npm run serve # → http://localhost:3001/mcp
# 宿主,从 ext-apps 检出目录运行
cd examples/basic-host && npm install
SERVERS='["http://localhost:3001/mcp"]' npm start # → http://localhost:8080
Windows PowerShell:
$env:SERVERS='["http://localhost:3001/mcp"]'; npm start
从下拉菜单中选择你的工具,调用它,观察 View 是否挂载。这一层可以捕获 bridge bug、CSP 拦截和生命周期顺序错误。
每次修改 UI 后都要重新构建——服务器从磁盘读取 dist/mcp-app.html,过期的 bundle 会静默地提供旧 View。
3. 真实宿主 —— 隧道接入
npx cloudflared tunnel --url http://localhost:3001
拿到生成的 https://<name>.trycloudflare.com URL 后进行注册:
- Claude —— Settings → Connectors → Add custom connector。需要付费计划(Pro、Max 或 Team)。
- ChatGPT —— 在开发者模式下连接 MCP 服务器。
- M365 Copilot —— VS Code 中的 Agents Toolkit(6.12.0+),然后访问
https://m365.cloud.microsoft/chat。需要先添加 CORS 与重定向 URI 白名单,参见 m365-copilot-packaging。
只有真实宿主才能暴露宿主特有的缺口,例如 window.openai API 未定义、frameDomains 条目被忽略、显示模式未被授予等。
4. 埋点
iframe 的控制台往往不在你正在看的位置,在某些宿主中甚至完全不可达。
app.sendLog({ level: "info", data: { phase: "tool-result", keys: Object.keys(sc) } });
sendLog 在 M365 Copilot 中不受支持,需要保留回退:
function log(level: string, data: unknown) {
try { app.sendLog?.({ level, data }); } catch { /* noop */ }
if (new URLSearchParams(location.search).has("debug")) console.log(level, data);
}
一个 ?debug 查询参数可以显示屏幕上的日志面板,在无法使用 devtools 的宿主中调试时非常值得。
值得自动化的断言
无需宿主即可进行单元测试:
- 服务器结构:直接调用工具处理函数,断言
structuredContent符合其 schema,且content[0].text非空(纯文本回退)。 - 资源结构:断言
mimeType、ui://协议,且csp只列出预期来源。 - 工具/资源接线:断言只有渲染工具带有
resourceUri,且每个被引用的 URI 都解析到已注册资源。 - 载荷校验:向 View 的解析器输入畸形和超大
structuredContent,断言它渲染错误状态而非崩溃。 - 能力降级:把
window.openai打桩为{},断言不渲染任何依赖宿主的交互元素。
本插件中的 scripts/validate-mcp-app.mjs 会对服务器源码树静态执行前三项,可在 CI 中运行。
桥接层集成测试:驱动无头 iframe,向它发送 ui/notifications/tool-result,断言 DOM。当组件超过一个状态后就值得做。
排查对照表
| 症状 | 可能原因 | 检查项 |
|---|---|---|
| 完全没有组件 | 工具缺少 _meta.ui.resourceUri |
Inspector tools/list |
| 没有组件但工具带该字段 | URI 不匹配任何已注册资源 | Inspector resources/list |
| iframe 空白 | 资源未内联,或不在 resourceDomains |
Network 面板 / CSP 违规 |
| 能加载但没数据 | 处理函数在 connect() 之后注册 |
把处理函数移到 connect() 之前 |
| 所有请求失败 | CSP 写在配置参数里,而非 contents 的 _meta |
在 Inspector 中读取资源 |
| 组件不断重挂载 | 数据工具带有 resourceUri |
解耦数据/渲染工具 |
| 本地可用,Copilot 中空白 | 组件宿主 URL 未被 CORS 允许 | Widget Host URL Generator |
| 按钮无反应 | 宿主 API 未定义 | getHostCapabilities() / 真值守卫 |
| 全屏被忽略 | 模式不在 availableDisplayModes 中 |
从被授予的模式渲染 |
| 活跃会话中仍是旧 UI | 破坏性变更后复用了资源 URI | 发布新的 ui:// URI |
| serve 时 import 语法错误 | 缺少 "type": "module" |
package.json |
| 嵌套嵌入空白 | 宿主不支持 frameDomains |
内联或降级为链接 |
相关内容
- mcp-apps-sdk —— 构建配置与失败模式清单。
- host-capability-matrix —— 各宿主的预期能力。
- m365-copilot-packaging —— Copilot 的前置条件。
适用场景与限制
该技能适用于在本地调试 MCP 应用 UI、排查组件不显示或渲染异常、验证服务器协议结构、以及在真实宿主中定位宿主特有缺口。限制在于:需要已构建的 MCP 服务器与相应工具链;部分宿主(如 Claude 添加自定义连接器)需要付费计划;M365 Copilot 需要特定版本的工具包并配置 CORS 与重定向 URI 白名单;sendLog 在 M365 Copilot 中不可用。