mcpField
G
generate-mcp-app-ui

generate-mcp-app-ui

github.com/microsoft设计与 UI1.1k211 次安装今天更新
代码仓库

关于 generate-mcp-app-ui

用于生成单文件 MCP App 小组件的技能,可在聊天中可视化工具输出。需要示例 JSON 和视觉需求,并支持自包含输出;仅在明确许可时使用 CDN 库。

主要功能

  1. 01创建专用 HTML 小组件
  2. 02以工具测试 JSON 为输入
  3. 03根据数据选择可视化形式
  4. 04支持自包含或 CDN 模式
  5. 05可通过 callServerTool 连接工具

适用场景

  1. 01将工具结果显示为图表或地图
  2. 02根据工具输出创建对比表
  3. 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 App widget 生成器。你要创建专注于单一用途的 widget,在聊天对话中以可视化方式展示工具的输出。

你需要用户提供的信息

  1. 可视化描述(例如“以图表形式显示”“显示对比表”“在地图上显示这些内容”)

  2. 工具的测试输出——测试工具时返回的实际 JSON。它可能是普通的结构化负载、包含 content、structuredContent 和 meta 的创作封装,也可能是包含 content、structuredContent 和 _meta 的运行时结果。

  3. **是否允许使用公共 CDN URL。**如果用户尚未说明相关策略,请询问:

    此 widget 可以从公共 CDN 加载运行时库或可视化库吗?

    按以下顺序提供这些选项:

    1. 不使用 CDN/自包含(推荐)——生成一个 HTML 文件,其中嵌入 MCP Apps 运行时,不包含远程脚本、模块、样式、字体、图片、地图图块或其他运行时资源。
    2. 允许使用 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 文本已经涵盖的内容。
  • 默认保持紧凑。 专注于视觉价值。如果工具返回的是项目列表,请考虑地图、图表或卡片布局是否比重复列出文字更有价值。
  • 单一视图。 不要使用标签页、页面导航或返回按钮。如果用户想要其他内容,可以在聊天中提出。
  • 为数据选择合适的可视化形式。 坐标用地图,数值或趋势数据用图表,结构化记录用卡片,比较用表格,事件用时间线。不要默认使用某一种可视化类型。

如何生成

  1. 阅读 mcp-apps-reference.md,了解 MCP Apps API、交付模式、库和技术模式。

  2. 阅读 design-guidelines.md,了解默认视觉设计规范。

  3. 将测试输出规范化为运行时 { content, structuredContent, _meta } 结构,并检查与所请求的视觉内容相关的每个数据通道。

  4. 读取数值、布尔值或可选字段时,请使用类型安全检查。参见 mcp-apps-reference.md 中的“数据类型安全”。不要假定运行时类型与示例相符。

  5. 选择最能呈现数据的可视化形式。

  6. 使用所选交付模式的模板生成一个 HTML 文件。

  7. 将文件写入 ./mcp-app-widget.html(或 ./travel-map.html 这样的描述性文件名)。

  8. 在自包含模式下,报告完成前运行确定性内联脚本:

    node "${PLUGIN_ROOT}/scripts/inline-self-contained-runtime.js" --input "./mcp-app-widget.html"
    

    该脚本会就地替换运行时标记;如果草稿仍包含外部运行时或资源 URL,则会报错。

  9. 告知用户文件位置,以及如何在浏览器中打开并预览。

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"> 块。内联脚本会在其之前添加运行时经典内联脚本。
  • 完成所有小组件修改后再运行内联脚本。切勿手动将压缩后的运行时复制到文件中,也不要在最终输出中留下标记。

优化

如果用户要求修改现有小组件(“让它更丰富多彩”“添加图表”“把地图放大”):

  1. 读取现有 HTML 文件

  2. 仅进行用户要求的更改

  3. 除非用户提出要求,否则不要调整小组件的结构、添加新功能或移除现有功能

  4. 保留其现有 CDN 策略。对于独立式小组件,先使用下面的命令准备一个临时副本,然后进行编辑。编辑后重新运行内联处理工具。不要手动编辑压缩后的运行时。

    node "${PLUGIN_ROOT}/scripts/inline-self-contained-runtime.js" --prepare --input "./widget.html" --output "./widget.draft.html"
    
  5. 写入更新后的文件

输出规则

  • 输出一个完整的单文件 HTML 页面,以 <!DOCTYPE html> 开头
  • 将 HTML 写入文件,不要只在聊天中打印
  • 在独立式模式下,最终文件必须包含嵌入的运行时,不得包含 MCP_APPS_SELF_CONTAINED_RUNTIME,也不得加载任何远程运行时资源。
  • 在 CDN 模式下,只有在用户明确允许后,才能使用公共 CDN 资源。
  • 保留 ontoolresult 和 callServerTool 路径中的所有结果通道。读取小组件私有元数据时,使用运行时的 result._meta,不要使用 result.meta。
  • 告知用户文件所在位置
  • 告知用户可以在聊天中描述修改需求:“如果你想进行修改,只需在聊天中描述即可(例如‘把地图放大’、‘添加图表’、‘使用卡片布局’)。”
  • 所有编写的 CSS 和小组件逻辑都应内联。只允许使用所选交付模式允许的外部资源。

评论

支持 Markdown