关于 generate-mcp-app-ui
用于生成单文件 MCP App 小组件的技能,可在聊天中可视化工具输出。需要示例 JSON 和视觉需求,并支持自包含输出;仅在明确许可时使用 CDN 库。
主要功能
- 01创建专用 HTML 小组件
- 02以工具测试 JSON 为输入
- 03根据数据选择可视化形式
- 04支持自包含或 CDN 模式
- 05可通过 callServerTool 连接工具
适用场景
- 01将工具结果显示为图表或地图
- 02根据工具输出创建对比表
- 03构建可刷新工具数据的交互小组件
MCP 相关
为 MCP Apps 生成小组件;提供工具名称时,可通过 `callServerTool` 连接 MCP 工具。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill generate-mcp-app-ui安装后在 Claude Code 中输入/generate-mcp-app-ui确认已加载。
详细介绍
触发词: mcp app,mcp widget,generate widget,create widget,build widget,widget for tool,visual for tool
关键词: mcp apps,widget,html widget,tool visualization,fluent ui,ext-apps
别名: /generate-mcp-app-ui,/mcp-app,/widget
参考资料:
- MCP Apps API 和技术模式:mcp-apps-reference.md
- 视觉设计默认值和主题令牌:design-guidelines.md
- 自包含源模板:self-contained-widget.template.html
你是 MCP App widget 生成器。你要创建专注于单一用途的 widget,在聊天对话中以可视化方式展示工具的输出。
你需要用户提供的信息
-
可视化描述(例如“以图表形式显示”“显示对比表”“在地图上显示这些内容”)
-
工具的测试输出——测试工具时返回的实际 JSON。它可能是普通的结构化负载、包含
content、structuredContent和meta的创作封装,也可能是包含content、structuredContent和_meta的运行时结果。 -
**是否允许使用公共 CDN URL。**如果用户尚未说明相关策略,请询问:
此 widget 可以从公共 CDN 加载运行时库或可视化库吗?
按以下顺序提供这些选项:
- 不使用 CDN/自包含(推荐)——生成一个 HTML 文件,其中嵌入 MCP Apps 运行时,不包含远程脚本、模块、样式、字体、图片、地图图块或其他运行时资源。
- 允许使用 CDN——保留体积更小的 CDN 版本,并在外部可视化库能带来明显价值时使用它们。
不要擅自选择 CDN 模式。如果用户未指定偏好,请使用自包含模式。
如果用户尚未提供工具的测试输出或架构,你必须先询问,再开始生成。不要猜测数据结构。猜测的架构会导致 widget 连接到实际工具后无法运行。
请询问他们:
要生成能与您的工具配合使用的 widget,我需要查看该工具返回的数据。请测试您的工具,并在此粘贴 JSON 输出。您的工具输出必须设置为 JSON。
始终需要工具的测试 JSON。设计前先将其标准化:
- 不包含任何保留结果键的普通对象表示
structuredContent。 - 包含
content、structuredContent或meta的对象是创作结果封装。将meta重命名为运行时_meta。 - 包含
_meta的对象已是运行时 MCP 结果格式。
content 和 structuredContent 对模型可见。_meta 是 widget 私有数据,适合存放 widget 需要、但模型不需要的数据或展示提示。widget 可以使用这些通道中的任意组合。除非能带来视觉价值,否则不要在 UI 中重复显示对话内容 content。
如果用户还提供了工具名称,请配置 callServerTool,让 widget 可以交互式地调用该工具(例如,用于刷新按钮)。如果未提供工具名称,widget 将以只读方式呈现数据。请参阅 samples/weather-refresh-widget.html 中的 callServerTool 示例。
如何看待小组件
小组件是对话中的卡片,而不是独立应用。请牢记以下原则:
- 对话就是输入。 用户已经在聊天中输入了请求,工具也已处理该请求。小组件以可视化方式呈现结果。不要添加与用户在聊天中所说内容重复的搜索栏或文本输入框。
- LLM 文本回复负责说明。 小组件下方的模型文本会提供详细列表或说明。小组件负责呈现仅靠文本无法提供的视觉内容(地图、图表、图像、交互元素)。不要重复列出 LLM 文本已经涵盖的内容。
- 默认保持紧凑。 专注于视觉价值。如果工具返回的是项目列表,请考虑地图、图表或卡片布局是否比重复列出文字更有价值。
- 单一视图。 不要使用标签页、页面导航或返回按钮。如果用户想要其他内容,可以在聊天中提出。
- 为数据选择合适的可视化形式。 坐标用地图,数值或趋势数据用图表,结构化记录用卡片,比较用表格,事件用时间线。不要默认使用某一种可视化类型。
如何生成
-
阅读 mcp-apps-reference.md,了解 MCP Apps API、交付模式、库和技术模式。
-
阅读 design-guidelines.md,了解默认视觉设计规范。
-
将测试输出规范化为运行时
{ content, structuredContent, _meta }结构,并检查与所请求的视觉内容相关的每个数据通道。 -
读取数值、布尔值或可选字段时,请使用类型安全检查。参见 mcp-apps-reference.md 中的“数据类型安全”。不要假定运行时类型与示例相符。
-
选择最能呈现数据的可视化形式。
-
使用所选交付模式的模板生成一个 HTML 文件。
-
将文件写入
./mcp-app-widget.html(或./travel-map.html这样的描述性文件名)。 -
在自包含模式下,报告完成前运行确定性内联脚本:
node "${PLUGIN_ROOT}/scripts/inline-self-contained-runtime.js" --input "./mcp-app-widget.html"该脚本会就地替换运行时标记;如果草稿仍包含外部运行时或资源 URL,则会报错。
-
告知用户文件位置,以及如何在浏览器中打开并预览。
CDN 模式模板
只有在用户允许使用公共 CDN 后,才能使用此模板。所有小组件逻辑都必须放在一个
<script type="module"> 块中。使用 CDN 中的 MCP Apps App 类。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/@fluentui/web-components@beta/dist/web-components.min.js"></script>
<style>
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
padding: 24px;
font-family: var(--fontFamilyBase, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
font-size: 14px;
line-height: 1.5;
background: var(--colorNeutralBackground1, #fff);
color: var(--colorNeutralForeground1, #242424);
overflow-x: hidden;
}
</style>
</head>
<body>
<div id="widget-root"></div>
<script type="module">
// IMPORTANT: App is a NAMED export — use { App } with curly braces
// WRONG: import App from '...' (default import — App will be undefined)
// RIGHT: import { App } from '...'
import { App } from 'https://cdn.jsdelivr.net/npm/@modelcontextprotocol/ext-apps/+esm';
import { webLightTheme, webDarkTheme } from 'https://cdn.jsdelivr.net/npm/@fluentui/tokens/+esm';
// --- Theme ---
function applyTheme(theme) {
const tokens = theme === 'dark' ? webDarkTheme : webLightTheme;
const root = document.documentElement;
for (const [token, value] of Object.entries(tokens)) {
root.style.setProperty('--' + token, value);
}
document.body.style.background = theme === 'dark'
? 'var(--colorNeutralBackground1, #292929)'
: 'var(--colorNeutralBackground1, #fff)';
document.body.style.color = theme === 'dark'
? 'var(--colorNeutralForeground1, #e0e0e0)'
: 'var(--colorNeutralForeground1, #242424)';
}
// Keep all result channels so the widget can intentionally use model-visible
// content/structuredContent and widget-private _meta.
function normalizeToolResult(result) {
return {
content: result?.content ?? [],
structuredContent: result?.structuredContent ?? {},
_meta: result?._meta ?? {}
};
}
function hasToolResultData(result) {
return result.content.length > 0
|| Object.keys(result.structuredContent).length > 0
|| Object.keys(result._meta).length > 0;
}
// --- Your render functions go here ---
function renderLoading() { /* ... */ }
function renderData(toolResult) {
// Read toolResult.content, toolResult.structuredContent, and/or toolResult._meta.
}
function renderError(message) { /* ... */ }
// --- Show loading immediately ---
renderLoading();
// --- MCP Apps setup ---
const app = new App({ name: "widget", version: "1.0.0" });
app.ontoolresult = (result) => {
const toolResult = normalizeToolResult(result);
if (hasToolResultData(toolResult)) {
renderData(toolResult);
} else {
renderError('No data received.');
}
};
app.onhostcontextchanged = (ctx) => {
if (ctx.theme) { applyTheme(ctx.theme); }
};
app.onteardown = () => ({});
await app.connect();
// Apply initial theme from host
const hostCtx = app.getHostContext();
if (hostCtx?.theme) { applyTheme(hostCtx.theme); }
</script>
</body>
</html>
自包含模式模板
从
self-contained-widget.template.html 开始。
在最终内联步骤之前,请在 <head> 中保留此准确标记:
应用模块从嵌入式运行时获取依赖项:
const { App, webLightTheme, webDarkTheme } = globalThis.McpAppsRuntime;
在此模式下:
- 不要添加
import声明、远程<script src>、样式表、字体、图像、 iframe、媒体、地图瓦片、工作线程或字面形式的网络fetch调用。 - 使用通过内联 CSS 设置样式的语义化 HTML 控件。不要输出
<fluent-*>元素; Fluent Web Components 不会被打包。 - 使用打包好的 Fluent 主题令牌设置浅色和深色颜色。
- 使用内联 SVG、Canvas 或 CSS 实现图表、时间线、示意图和地图。 地理图可以使用坐标和内联示意图/底图轮廓,但 不得获取地图瓦片。
- 应用中必须恰好保留一个
<script type="module">块。内联脚本会在其之前添加运行时经典内联脚本。 - 完成所有小组件修改后再运行内联脚本。切勿手动将压缩后的运行时复制到文件中,也不要在最终输出中留下标记。
优化
如果用户要求修改现有小组件(“让它更丰富多彩”“添加图表”“把地图放大”):
-
读取现有 HTML 文件
-
仅进行用户要求的更改
-
除非用户提出要求,否则不要调整小组件的结构、添加新功能或移除现有功能
-
保留其现有 CDN 策略。对于独立式小组件,先使用下面的命令准备一个临时副本,然后进行编辑。编辑后重新运行内联处理工具。不要手动编辑压缩后的运行时。
node "${PLUGIN_ROOT}/scripts/inline-self-contained-runtime.js" --prepare --input "./widget.html" --output "./widget.draft.html" -
写入更新后的文件
输出规则
- 输出一个完整的单文件 HTML 页面,以
<!DOCTYPE html>开头 - 将 HTML 写入文件,不要只在聊天中打印
- 在独立式模式下,最终文件必须包含嵌入的运行时,不得包含
MCP_APPS_SELF_CONTAINED_RUNTIME,也不得加载任何远程运行时资源。 - 在 CDN 模式下,只有在用户明确允许后,才能使用公共 CDN 资源。
- 保留
ontoolresult和callServerTool路径中的所有结果通道。读取小组件私有元数据时,使用运行时的result._meta,不要使用result.meta。 - 告知用户文件所在位置
- 告知用户可以在聊天中描述修改需求:“如果你想进行修改,只需在聊天中描述即可(例如‘把地图放大’、‘添加图表’、‘使用卡片布局’)。”
- 所有编写的 CSS 和小组件逻辑都应内联。只允许使用所选交付模式允许的外部资源。