> ## Documentation Index
> Fetch the complete documentation index at: https://mcp.developerdoc.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# 构建 MCP App

> 使用 MCP Apps 构建交互式 UI 应用的入门指南

## 前置条件

你需要安装 [Node.js](https://nodejs.org/en/download) 18 或更高版本。建议熟悉 [MCP 工具](/specification/latest/server/tools) 和 [资源](/specification/latest/server/resources)，因为 MCP Apps 会组合使用这两种原语。如果你有 [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) 使用经验，会更容易理解服务器端模式。

## 开始使用

创建 MCP App 最快的方式，是使用带有 MCP Apps 技能的 AI 编码智能体。如果你更想手动搭建项目，可以跳到[手动设置](#manual-setup)。

### 使用 AI 编码智能体

支持技能的 AI 编码智能体可以为你脚手架生成完整的 MCP App 项目。技能是包含指令和资源的文件夹，智能体会在相关场景下加载它们。它们会教 AI 如何执行创建 MCP Apps 等专门任务。

`create-mcp-app` 技能包含架构指导、最佳实践和可运行示例，智能体会用它们生成你的项目。

<Steps>
  <Step title="安装技能">
    如果你使用 Claude Code，可以直接用以下命令安装该技能：

    ```
    /plugin marketplace add modelcontextprotocol/ext-apps
    /plugin install mcp-apps@modelcontextprotocol-ext-apps
    ```

    你也可以使用 [Vercel Skills CLI](https://skills.sh/) 在不同 AI 编码智能体中安装技能：

    ```bash theme={null}
    npx skills add modelcontextprotocol/ext-apps
    ```

    也可以通过克隆 ext-apps 仓库来手动安装该技能：

    ```bash theme={null}
    git clone https://github.com/modelcontextprotocol/ext-apps.git
    ```

    然后将该技能复制到你的智能体对应位置：

    | 智能体                                                                                                                                                                        | 技能目录 (macOS/Linux)        | 技能目录 (Windows)                        |
    | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------- |
    | [Claude Code](https://docs.anthropic.com/en/docs/claude-code/skills)                                                                                                       | `~/.claude/skills/`       | `%USERPROFILE%\.claude\skills\`       |
    | [VS Code](https://code.visualstudio.com/docs/copilot/customization/agent-skills) 和 [GitHub Copilot](https://docs.github.com/en/copilot/concepts/agents/about-agent-skills) | `~/.copilot/skills/`      | `%USERPROFILE%\.copilot\skills\`      |
    | [Gemini CLI](https://geminicli.com/docs/cli/skills/)                                                                                                                       | `~/.gemini/skills/`       | `%USERPROFILE%\.gemini\skills\`       |
    | [Cline](https://cline.bot/blog/cline-3-48-0-skills-and-websearch-make-cline-smarter)                                                                                       | `~/.cline/skills/`        | `%USERPROFILE%\.cline\skills\`        |
    | [Goose](https://block.github.io/goose/docs/guides/context-engineering/using-skills/)                                                                                       | `~/.config/goose/skills/` | `%USERPROFILE%\.config\goose\skills\` |
    | [Codex](https://developers.openai.com/codex/skills/)                                                                                                                       | `~/.codex/skills/`        | `%USERPROFILE%\.codex\skills\`        |
    | [Cursor](https://cursor.com/docs/context/skills)                                                                                                                           | `~/.cursor/skills/`       | `%USERPROFILE%\.cursor\skills\`       |

    <Note>
      该列表并不完整。其他智能体可能在不同位置支持技能，请查看你的智能体文档。
    </Note>

    例如，使用 Claude Code 时，可以全局安装该技能（所有项目都可用）：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      cp -r ext-apps/plugins/mcp-apps/skills/create-mcp-app ~/.claude/skills/create-mcp-app
      ```

      ```powershell Windows theme={null}
      Copy-Item -Recurse ext-apps\plugins\mcp-apps\skills\create-mcp-app $env:USERPROFILE\.claude\skills\create-mcp-app
      ```
    </CodeGroup>

    也可以只为单个项目安装，将它复制到项目目录下的 `.claude/skills/`：

    <CodeGroup>
      ```bash macOS/Linux theme={null}
      mkdir -p .claude/skills && cp -r ext-apps/plugins/mcp-apps/skills/create-mcp-app .claude/skills/create-mcp-app
      ```

      ```powershell Windows theme={null}
      New-Item -ItemType Directory -Force -Path .claude\skills | Out-Null; Copy-Item -Recurse ext-apps\plugins\mcp-apps\skills\create-mcp-app .claude\skills\create-mcp-app
      ```
    </CodeGroup>

    要确认技能已安装，可以询问智能体“你可以访问哪些技能？”，你应该能看到 `create-mcp-app` 是可用技能之一。
  </Step>

  <Step title="创建应用">
    让你的 AI 编码智能体构建它：

    ```
    创建一个显示取色器的 MCP App
    ```

    智能体会识别出 `create-mcp-app` 技能与任务相关，加载其中指令，然后脚手架生成包含服务器、UI 和配置文件的完整项目。

    <Frame caption="使用 Claude Code 创建新的 MCP App">
      <img src="https://mintcdn.com/mcp-af858c62/jSqzMzM_desZfWyn/images/quickstart-apps/create-mcp-app-skill.gif?s=6290b9a8df980cb6c541580928ef84dd" alt="使用 Claude Code 创建新的 MCP App" width="800" height="563" data-path="images/quickstart-apps/create-mcp-app-skill.gif" />
    </Frame>
  </Step>

  <Step title="运行应用">
    <CodeGroup>
      ```bash macOS/Linux theme={null}
      npm install && npm run build && npm run serve
      ```

      ```powershell Windows theme={null}
      npm install; npm run build; npm run serve
      ```
    </CodeGroup>

    <Tip>
      运行上述命令前，可能需要先确认自己位于**应用文件夹**中。
    </Tip>
  </Step>

  <Step title="测试应用">
    按照下方[测试应用](#testing-your-app)中的说明操作。对于取色器示例，可以启动一个新聊天，并让 Claude 提供一个取色器。

    <Frame caption="在 Claude 中测试取色器">
      <img src="https://mintcdn.com/mcp-af858c62/jSqzMzM_desZfWyn/images/quickstart-apps/test-color-picker.gif?s=6573e59e8666214104780e9364ac7fc2" alt="在 Claude 中测试取色器" width="800" height="544" data-path="images/quickstart-apps/test-color-picker.gif" />
    </Frame>
  </Step>
</Steps>

<a id="manual-setup" />

### 手动设置

如果你不使用 AI 编码智能体，或想了解设置过程，可以按以下步骤操作。

<Steps>
  <Step title="创建项目结构">
    典型 MCP App 项目会将服务器代码和 UI 代码分开：

    <Tree>
      <Tree.Folder name="my-mcp-app" defaultOpen>
        <Tree.File name="package.json" />

        <Tree.File name="tsconfig.json" />

        <Tree.File name="vite.config.ts" />

        <Tree.File name="server.ts" comment="带工具和资源的 MCP 服务器" />

        <Tree.File name="mcp-app.html" comment="UI 入口" />

        <Tree.Folder name="src" defaultOpen>
          <Tree.File name="mcp-app.ts" comment="UI 逻辑" />
        </Tree.Folder>
      </Tree.Folder>
    </Tree>

    服务器会注册工具并提供 UI 资源。UI 资源最终会在安全 iframe 中渲染，并使用默认拒绝的 CSP 配置。如果应用包含 CSS 和 JS 资产，需要[配置 CSP](https://apps.extensions.modelcontextprotocol.io/api/documents/Patterns.html#configuring-csp-and-cors)，也可以使用 `vite-plugin-singlefile` 这样的工具将资产打包进 HTML，本教程会采用这种方式。
  </Step>

  <Step title="安装依赖">
    ```bash theme={null}
    npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk
    npm install -D typescript vite vite-plugin-singlefile express cors @types/express @types/cors tsx
    ```

    `ext-apps` 包同时为服务器端（注册工具和资源）与客户端（用于 UI 到主机通信的 `App` 类）提供辅助工具。这里使用 Vite 和 `vite-plugin-singlefile` 插件将 UI 和资产打包成单个 HTML 文件，便于演示；但这是可选的，只要[配置 CSP](https://apps.extensions.modelcontextprotocol.io/api/documents/Patterns.html#configuring-csp-and-cors)，你也可以使用任意打包器，或直接提供未打包文件。
  </Step>

  <Step title="配置项目">
    <Tabs>
      <Tab title="package.json">
        `"type": "module"` 设置会启用 ES module 语法。`build` 脚本使用 `INPUT` 环境变量告诉 Vite 要打包哪个 HTML 文件。`serve` 脚本使用 `tsx` 运行 TypeScript 服务器。

        ```json theme={null}
        {
          "type": "module",
          "scripts": {
            "build": "INPUT=mcp-app.html vite build",
            "serve": "npx tsx server.ts"
          }
        }
        ```
      </Tab>

      <Tab title="tsconfig.json">
        TypeScript 配置面向现代 JavaScript（`ES2022`），并使用带 bundler resolution 的 ESNext modules，这与 Vite 配合良好。`include` 数组同时覆盖根目录中的服务器代码和 `src/` 中的 UI 代码。

        ```json theme={null}
        {
          "compilerOptions": {
            "target": "ES2022",
            "module": "ESNext",
            "moduleResolution": "bundler",
            "strict": true,
            "esModuleInterop": true,
            "skipLibCheck": true,
            "outDir": "dist"
          },
          "include": ["*.ts", "src/**/*.ts"]
        }
        ```
      </Tab>

      <Tab title="vite.config.ts">
        ```typescript theme={null}
        import { defineConfig } from "vite";
        import { viteSingleFile } from "vite-plugin-singlefile";

        export default defineConfig({
          plugins: [viteSingleFile()],
          build: {
            outDir: "dist",
            rollupOptions: {
              input: process.env.INPUT,
            },
          },
        });
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="构建项目">
    项目结构和配置就绪后，继续阅读下方[构建 MCP App](#building-an-mcp-app)，实现服务器和 UI。
  </Step>
</Steps>

<a id="building-an-mcp-app" />

## 构建 MCP App

下面构建一个显示当前服务器时间的简单应用。这个示例展示了完整模式：注册带 UI 元数据的工具，将打包后的 HTML 作为资源提供，并构建能与服务器通信的 UI。

### 服务器实现

服务器需要做两件事：注册一个包含 `_meta.ui.resourceUri` 字段的工具，并注册一个资源处理器来提供打包后的 HTML。下面是完整服务器文件：

```typescript theme={null}
// server.ts
console.log("Starting MCP App server...");

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import {
  registerAppTool,
  registerAppResource,
  RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import cors from "cors";
import express from "express";
import fs from "node:fs/promises";
import path from "node:path";

const server = new McpServer({
  name: "My MCP App Server",
  version: "1.0.0",
});

// ui:// scheme 会告诉 hosts 这是一个 MCP App resource。
// 路径结构是任意的；按适合你的 app 的方式组织即可。
const resourceUri = "ui://get-time/mcp-app.html";

// 注册返回当前时间的 tool
registerAppTool(
  server,
  "get-time",
  {
    title: "Get Time",
    description: "Returns the current server time.",
    inputSchema: {},
    _meta: { ui: { resourceUri } },
  },
  async () => {
    const time = new Date().toISOString();
    return {
      content: [{ type: "text", text: time }],
    };
  },
);

// 注册用于提供打包后 HTML 的 resource
registerAppResource(
  server,
  resourceUri,
  resourceUri,
  { mimeType: RESOURCE_MIME_TYPE },
  async () => {
    const html = await fs.readFile(
      path.join(import.meta.dirname, "dist", "mcp-app.html"),
      "utf-8",
    );
    return {
      contents: [
        { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
      ],
    };
  },
);

// 通过 HTTP 暴露 MCP server
const expressApp = express();
expressApp.use(cors());
expressApp.use(express.json());

expressApp.post("/mcp", async (req, res) => {
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
    enableJsonResponse: true,
  });
  res.on("close", () => transport.close());
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

expressApp.listen(3001, (err) => {
  if (err) {
    console.error("Error starting server:", err);
    process.exit(1);
  }
  console.log("Server listening on http://localhost:3001/mcp");
});
```

关键部分如下：

* **`resourceUri`**：`ui://` scheme 会告诉主机这是一个 MCP App 资源。路径结构是任意的。
* **`registerAppTool`**：注册一个带 `_meta.ui.resourceUri` 字段的工具。当主机调用该工具时，会获取并渲染 UI，并在工具结果到达后传给 UI。
* **`registerAppResource`**：当主机请求 UI 资源时，提供打包后的 HTML。
* **Express 服务器**：通过端口 3001 上的 HTTP 暴露 MCP 服务器。

### UI 实现

UI 由一个 HTML 页面和一个使用 `App` 类与主机通信的 TypeScript 模块组成。下面是 HTML：

```html theme={null}
<!-- mcp-app.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Get Time App</title>
  </head>
  <body>
    <p>
      <strong>Server Time:</strong>
      <code id="server-time">Loading...</code>
    </p>
    <button id="get-time-btn">Get Server Time</button>
    <script type="module" src="/src/mcp-app.ts"></script>
  </body>
</html>
```

以及 TypeScript 模块：

```typescript theme={null}
// src/mcp-app.ts
import { App } from "@modelcontextprotocol/ext-apps";

const serverTimeEl = document.getElementById("server-time")!;
const getTimeBtn = document.getElementById("get-time-btn")!;

const app = new App({ name: "Get Time App", version: "1.0.0" });

// 与 host 建立通信
app.connect();

// 处理 host 推送的初始 tool 结果
app.ontoolresult = (result) => {
  const time = result.content?.find((c) => c.type === "text")?.text;
  serverTimeEl.textContent = time ?? "[ERROR]";
};

// 用户与 UI 交互时主动调用 tools
getTimeBtn.addEventListener("click", async () => {
  const result = await app.callServerTool({
    name: "get-time",
    arguments: {},
  });
  const time = result.content?.find((c) => c.type === "text")?.text;
  serverTimeEl.textContent = time ?? "[ERROR]";
});
```

关键部分如下：

* **`app.connect()`**：与主机建立通信。应用初始化时调用一次。
* **`app.ontoolresult`**：当主机向应用推送工具结果时触发的回调（例如首次调用工具并渲染 UI 时）。
* **`app.callServerTool()`**：让应用主动调用服务器上的工具。请注意，每次调用都会与服务器往返一次，因此 UI 设计应能妥善处理延迟。

`App` 类还提供用于记录日志、打开 URL，以及使用应用中的结构化数据更新模型上下文的额外方法。请查看完整 [API 文档](https://apps.extensions.modelcontextprotocol.io/api/)。

<a id="testing-your-app" />

## 测试应用

要测试 MCP App，请构建 UI 并启动本地服务器：

<CodeGroup>
  ```bash macOS/Linux theme={null}
  npm run build && npm run serve
  ```

  ```powershell Windows theme={null}
  npm run build; npm run serve
  ```
</CodeGroup>

在默认配置下，服务器可通过 `http://localhost:3001/mcp` 访问。不过，要看到应用渲染，需要使用支持 MCP Apps 的 MCP 主机。你有几种选择。

### 使用 Claude 测试

[Claude](https://claude.ai) (web) 和 [Claude Desktop](https://claude.ai/download)
支持 MCP Apps。对于本地开发，需要将服务器暴露到互联网。你可以在本地运行 MCP 服务器，并使用 `cloudflared` 等工具通过隧道转发流量。

在另一个终端中运行：

```bash theme={null}
npx cloudflared tunnel --url http://localhost:3001
```

复制生成的 URL（例如 `https://random-name.trycloudflare.com`），并在 Claude 中将其添加为[自定义连接器](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp)：点击个人资料，进入 **Settings**、**Connectors**，最后点击 **Add custom connector**。

<Note>
  自定义连接器可在 Claude 付费计划（Pro、Max 或 Team）中使用。
</Note>

<Frame caption="在 Claude 中添加 custom connector">
  <img src="https://mintcdn.com/mcp-af858c62/jSqzMzM_desZfWyn/images/quickstart-apps/add-custom-connector.gif?s=ebbc27c12b4b83c3937772f7c677edfb" alt="在 Claude 中添加 custom connector" width="800" height="543" data-path="images/quickstart-apps/add-custom-connector.gif" />
</Frame>

### 使用 basic-host 测试

`ext-apps` 仓库包含一个用于开发的测试主机。克隆仓库并安装依赖：

<CodeGroup>
  ```bash macOS/Linux theme={null}
  git clone https://github.com/modelcontextprotocol/ext-apps.git
  cd ext-apps/examples/basic-host
  npm install
  ```

  ```powershell Windows theme={null}
  git clone https://github.com/modelcontextprotocol/ext-apps.git
  cd ext-apps\examples\basic-host
  npm install
  ```
</CodeGroup>

在 `ext-apps/examples/basic-host/` 中运行 `npm start` 会启动 basic-host 测试界面。要将它连接到特定服务器（例如你正在开发的服务器），请以内联方式传入 `SERVERS` 环境变量：

<CodeGroup>
  ```bash macOS/Linux theme={null}
  SERVERS='["http://localhost:3001/mcp"]' npm start
  ```

  ```powershell Windows theme={null}
  $env:SERVERS='["http://localhost:3001/mcp"]'; npm start
  ```
</CodeGroup>

访问 `http://localhost:8080`。你会看到一个简单界面，可以选择工具并调用它。当你调用工具时，主机会获取 UI 资源，并在沙箱 iframe 中渲染它。之后你可以与应用交互，并验证工具调用是否正常工作。

<Frame caption="使用 basic host 测试二维码 MCP App">
  <img src="https://mintcdn.com/mcp-af858c62/jSqzMzM_desZfWyn/images/quickstart-apps/qr-code-server.gif?s=09bcf6269440c1f8841de22798ac8f5d" alt="二维码 MCP App 在 basic host 中运行的示例" width="800" height="596" data-path="images/quickstart-apps/qr-code-server.gif" />
</Frame>

## 了解更多

<CardGroup cols={2}>
  <Card title="API 文档" icon="book" href="https://apps.extensions.modelcontextprotocol.io/api/">
    完整 SDK 参考和 API 详情
  </Card>

  <Card title="GitHub 仓库" icon="github" href="https://github.com/modelcontextprotocol/ext-apps">
    源代码、示例和议题跟踪器
  </Card>

  <Card title="规范" icon="file-lines" href="https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx">
    面向实现者的技术规范
  </Card>
</CardGroup>

## 反馈

MCP Apps 正在积极开发中。如果遇到问题或有改进想法，请在 [GitHub 仓库](https://github.com/modelcontextprotocol/ext-apps/issues)中提交 issue。关于该扩展方向的更广泛讨论，可以参与 [GitHub Discussions](https://github.com/modelcontextprotocol/ext-apps/discussions)。
