takeout-coffee illust 3657

MCP におけるインタラクティブな UI を標準化する拡張機能 MCP Apps の提案

MCP Apps は MCP の拡張機能として、AI エージェントがインタラクティブな UI コンポーネントを返すための標準化された方法を提供します。この記事では MCP Apps の概要と実装方法について解説します。

ChatGPT の Apps SDKMCP-UI のように AI エージェントがチャット形式の対話だけでなく、インタラクティブな UI を通じてユーザーとやり取りできる仕組みが注目されています。AI エージェントが UI を返すことで、会話の流れの中で以下のような体験を提供できます。

  • 「おすすめのランニングシューズを探して」と尋ねると、複数のシューズの画像と価格が表示され、ユーザーは気に入ったものをクリックしてカートに追加できる
  • ホテルの予約を依頼すると、利用可能な部屋のリストが表示され、ユーザーは日付や人数を選択して予約手続きを進められる
  • 希望にあった賃貸物件を探す際に、地図上に物件の位置が表示され、ユーザーは地図を操作して周辺環境を確認できる
  • ユーザーが提示した図形を元に Figma 上でデザイン案を生成し、ユーザーは生成されたデザインを直接編集できる

https://openai.com/ja-JP/index/introducing-apps-in-chatgpt/ より引用。

Apps SDK や MCP-UI はそれぞれ Model Context Protocol (MCP) を基盤としており、MCP の仕組みを拡張して任意の HTML, CSS, JavaScript を含む UI コンポーネントをエージェントが返せるようにしています。しかし、それぞれが独自に MCP を拡張しているため、異なるプラットフォーム間で互換性がなく、同じ UI コンポーネントを複数のエージェントで共有することが困難です。

そこで、MCP 自体にインタラクティブな UI コンポーネントを標準化して扱うための拡張機能 MCP Apps が提案されました。MCP Apps は ui:// URI スキームを使用して UI リソースを宣言し、メタデータを介してツールと関連付けます。これにより MCP の標準的なクライアントとサーバー間の双方向通信を通じて、インタラクティブな UI コンポーネントを配信できるようになります。またセキュリティ上の理由から、MCP Apps ではサンドボックス化された iframe 内で UI コンポーネントを実行することが機能として定義されています。

この記事では、MCP Apps の概要について紹介し、early access SDK を使用して MCP Apps を実装する方法について解説します。

MCP Apps の概要

MCP Apps は MCP の拡張機能として、エージェントがインタラクティブな UI コンポーネントを返すための標準化された方法を提供します。MCP Apps の主な特徴は以下の通りです。

  • ui:// URI スキームを使用して UI リソースを宣言
  • メタデータを介してツールと関連付け
  • JSON-RPC を使用したホストと UI コンポーネント間の双方向通信
  • サンドボックス化された iframe 内で UI コンポーネントを実行

この仕様では HTML コンテンツ(text/html+mcp)のみに焦点を当てており、将来には他のコンテンツタイプもサポートできるように拡張性を考慮して設計されています。また MCP Apps はあくまで MCP の拡張機能であるため、MCP の既存機能に影響を与えることはありません。

UI リソースの宣言

UI リソースは以下のような形式で宣言されます。

{
  // 必ず ui:// スキームを使用する
  "uri": "ui://charts/bar-chart",
  // 人間が理解しやすい名前
  "name": "Bar Chart",
  // 初期の段階では text/html+mcp のみサポート
  // 将来の拡張のために mimeType フィールドを設けている
  "mimeType": "text/html+mcp",
  // UI リソースの説明(任意)
  "description": "A bar chart component for displaying data",
  // UI リソースのメタデータ
  "_meta": {
    "ui": {
      // Content Security Policy (CSP) の設定(任意)
      "csp": {
        // iframe 内で外部ドメインへの接続を許可
        "connect_domains": [],
        // 静的リソースの読み込みを許可
        "resource_domains": []
      }
    },
    // API キーの許可リストやクロスオリジン分離のためのドメインを指定
    "domain": "https://example.com",
    // UI コンポーネントがボーダーを表示するかどうか
    "prefersBorder": true
  }
}

UI リソースのコンテンツは text もしくは blob(base64 エンコード)として提供します。またコンテンツは有効な HTML5 ドキュメントである必要があります。以下は UI リソースのコンテンツ例です。

{
  "contents": [
    {
      "uri": "ui://charts/bar-chart",
      "text": "<!DOCTYPE html><html>...</html>",
      "mimeType": "text/html+mcp"
    }
  ]
}

ツールとの関連付け

UI リソースはツールと関連付けることによりホストに返却されます。ツールにリソースを関連付けるためには、ツールのメタデータに ui/resourceUri フィールドを追加します。以下はツールのメタデータ例です。

{
  "name": "Get Weather Chart",
  "description": "Fetches weather data and displays it in a chart",
  "_meta": {
    "ui/resourceUri": "ui://charts/bar-chart"
  }
}

メタデータに ui/resourceUri フィールドが含まれており、かつホストが MCP Apps をサポートしている場合、ホストは指定された UI リソースを使用して結果をレンダリングします。ホストは UI リソースを取得するために resources/read リクエストを送信します。

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "resources/read",
  "params": {
    "uri": "ui://charts/bar-chart"
  }
}

ホストと iframe 間の双方向通信

MCP Apps ではホストと UI コンポーネント間の双方向通信のために JSON-RPC を使用します。UI コンポーネントは window.parent.postMessage を使用してホストにメッセージを送信し、ホストは iframe.contentWindow.postMessage を使用して UI コンポーネントにメッセージを送信します。概念的には Iframe は MCP クライアントとして動作し、ホストは MCP サーバーとして動作します。

// UI コンポーネントからホストへのメッセージ送信
window.parent.postMessage(
  {
    jsonrpc: "2.0",
    method: "ui/initialize",
    params: {
      /* 初期化パラメータ */
    },
    id: 1,
  },
  "*",
);
 
window.addEventListener("message", (event) => {
  const message = event.data;
  // ホストからのメッセージを処理
  switch (message.method) {
    case "ui/initialize":
      // 初期化処理
      break;
    case "tools/call":
      // MCP ツールの呼び出しを処理
      break;
    // その他のメッセージ処理
  }
});

UI iframe は以下の MCP プロトコルメッセージのサブセットをサポートします。

  • tools/call: ホストが MCP ツールを呼び出すためのリクエスト
  • resources/read: リソースコンテンツを読み取るためのリクエスト
  • notifications/message: ホストへのメッセージをロギングする
  • ui/initializeui/notifications/initialized: MCP のようなハンドシェイク処理
  • ping: ヘルスチェック

UI が ui/initialize リクエストをホストに送信すると、ホストは以下のように UI 固有の情報を含めたレスポンスを返します。

interface HostContext {
  // UI から呼び出すことができるツールの情報
  toolInfo?: {
    id?: RequestId;
    tool: Tool;
  };
  // カラーテーマの設定
  theme?: "light" | "dark" | "system";
  // UI が現在どのように表示されているか
  displayMode?: "inline" | "fullscreen" | "pip" | "carousel";
  // ホストがサポートする表示モードの一覧
  availableDisplayModes?: string[];
  // UI の表示に関する追加情報
  viewport?: {
    width: number;
    height: number;
    maxHeight?: number;
    maxWidth?: number;
  };
  // ユーザーの言語設定 e.g. "ja-JP", "en-US"
  locale?: string;
  // ユーザーのタイムゾーン設定 e.g. "Asia/Tokyo", "America/New_York"
  timeZone?: string;
  userAgent?: string;
  // レスポンシブデザインのためのデバイス情報
  platform?: "web" | "desktop" | "mobile";
  // タッチなどのデバイス機能をサポートしているかどうか
  deviceCapabilities?: {
    touch?: boolean;
    hover?: boolean;
  };
  // セーフエリアのインセット情報(モバイルデバイス向け)
  safeAreaInsets?: {
    top: number;
    right: number;
    bottom: number;
    left: number;
  };
}

以下のような MCP Apps 固有のメッセージも定義されています。

  • ui/open-link: ホストに外部リンクを開くよう指示
  • ui/message: ホストのチャット UI にメッセージを表示
  • ui/notifications/tool-input: iframe の初期化リクエストが完了したら、ホストはツールの引数とともにこの通知を送信
  • ui/notifications/tool-input-partial: ツールの引数が部分的に更新された場合にホストが送信
  • ui/tool-result: ツールの実行結果を iframe に送信。ホストはツールの実行が完了したタイミングで UI に結果を送信する
  • ui/tool-cancelled: ツールの実行がキャンセルされた場合にホストが送信
  • ui/resource-teardown: ホストが UI リソースを破棄される前に通知する
  • ui/size-change: 表示サイズが変更された場合に UI がホストに通知
  • ui/host-context-change: ホストコンテキストが変更された場合にホストが UI に通知
  • ui/sandbox-ready: ホストが iframe のサンドボックス環境が準備できたことを通知
  • ui/sandbox-resource-ready: ホストが iframe のサンドボックス環境でリソースが利用可能になったことを通知

MCP Apps を使ってみる

MCP Apps を構築するための SDK(@modelcontextprotocol/ext-apps)を使用して MCP Apps を実際に実装してみましょう。@modelcontextprotocol/ext-apps パッケージはまだ npm に公開されていないため、GitHub から直接インストールします。

npm install git+https://github.com/modelcontextprotocol/ext-apps.git

その他 MCP サーバーと UI リソースを実装するために必要なパッケージもインストールします。

npm install @modelcontextprotocol/sdk react react-dom express cors zod@3
npm install -D typescript ts-node @types/node @types/express @types/react @types/react-dom @vitejs/plugin-react vite vite-plugin-singlefile tsx

この例では React と Vite を使用して UI コンポーネントを実装します。UI コンポーネントをビルドするために vite.config.ts ファイルを作成します。

vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { viteSingleFile } from "vite-plugin-singlefile";
import { resolve } from "path";
 
const entry = process.env.VITE_ENTRY || "ui-react";
 
export default defineConfig({
  // viteSingleFile プラグインを使用して単一ファイルにバンドル
  plugins: [react(), viteSingleFile()],
  build: {
    rollupOptions: {
      input: resolve(__dirname, `${entry}.html`),
    },
    outDir: `dist`,
    emptyOutDir: false,
  },
});

UI コンポーネントのエントリーポイントとなる ui-react.html ファイルを作成します。

ui-react.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MCP UI Client (React)</title>
</head>
<body>
    <div id="root"></div>
    <script type="module" src="/src/ui-react.tsx"></script>
</body>
</html>

tsconfig.json ファイルを作成します。

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"]
}

ビルドスクリプトを package.json に追加します。

package.json
{
  "scripts": {
    "dev": "vite",
    "build": "rm -rf dist && VITE_ENTRY=ui-react vite build",
    "preview": "vite preview",
    "server": "tsx server.ts",
    "start": "npm run build && npm run server"
  },
}

UI コンポーネントの実装

src/ui-react.tsx ファイルを作成し、MCP Apps SDK を使用して UI コンポーネントを実装しましょう。UI の初期化処理は useApp フックを使用して行います。

src/ui-react.tsx
import { useState } from "react";
import {
  useApp,
  McpUiSizeChangeNotificationSchema,
  McpUiToolResultNotificationSchema,
} from "@modelcontextprotocol/ext-apps/react";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
 
export function UiReact() {
  const [toolResults, setToolResults] = useState<CallToolResult[]>([]);
  const { app, isConnected, error } = useApp({
    appInfo: {
      name: "ui-react",
      version: "0.1.0",
    },
    capabilities: {},
    onAppCreated: (app) => {
      app.setNotificationHandler(
        McpUiToolResultNotificationSchema,
        async (notification) => {
          setToolResults((prev) => [...prev, notification.params]);
        }
      );
      app.setNotificationHandler(
        McpUiSizeChangeNotificationSchema,
        async (notification) => {
          document.body.style.width = `${notification.params.width}px`;
          document.body.style.height = `${notification.params.height}px`;
        }
      );
    },
  });
 
  if (error) {
    return <div>Error: {error.message}</div>;
  }
 
  if (!isConnected) {
    return <div>Connecting to host application...</div>;
  }
 
  return (
    <div>
      <h1>UI React App</h1>
 
      {toolResults.map((result, index) => (
        <div
          key={index}
          style={{ border: "1px solid black", margin: "10px", padding: "10px" }}
        >
          <h2>Tool Result {index + 1}</h2>
          {result.structuredContent ? (
            <pre>{JSON.stringify(result.structuredContent, null, 2)}</pre>
          ) : (
            <p>No structured content</p>
          )}
        </div>
      ))}
    </div>
  );
}

ここでは onAppCreated コールバック内で ui/tool-resultui/size-change 通知のハンドラを登録しています。ui/tool-result 通知が受信されると、ツールの実行結果が状態に追加され、ui/size-change 通知が受信されると、UI の表示サイズが更新されます。そのほか、UI の接続状態やエラー情報も取得できます。

MCP ホストとやり取りする場合には useApp フックから返される app オブジェクトを使用することになります。app オブジェクトを介して、MCP ツールの呼び出しや外部リンクのオープンの要求などが可能です。

src/ui-react.tsx
export function UiReact() {
  const [messages, setMessages] = useState<string[]>([]);
  const { app, isConnected, error } = useApp({ /* ... */ });
 
  const handleGetWeatherTool = useCallback(async () => {
    if (!app) return;
    try {
      const result = await app.callServerTool({
        name: "get-weather",
        arguments: { location: "New York" },
      });
      setMessages((prev) => [
        ...prev,
        `Weather tool result: ${JSON.stringify(result)}`,
      ]);
    } catch (error) {
      setMessages((prev) => [...prev, `Error calling weather tool: ${error}`]);
    }
  }, [app]);
 
  const handleOpenLink = useCallback(async () => {
    if (!app) return;
    try {
      const result = await app.sendOpenLink({
        url: "https://www.example.com",
      });
      setMessages((prev) => [
        ...prev,
        `Open link result: ${JSON.stringify(result)}`,
      ]);
    } catch (error) {
      setMessages((prev) => [...prev, `Error opening link: ${error}`]);
    }
  }, [app]);
 
  if (error) {
    return <div>Error: {error.message}</div>;
  }
 
  // ...省略...
 
  return (
    <div>
      <h1>UI React App</h1>
      <button onClick={handleGetWeatherTool}>Get Weather</button>
      <button onClick={handleOpenLink}>Open Example.com</button>
 
      <div>
        <h2>Messages</h2>
        {messages.map((msg, index) => (
          <p key={index}>{msg}</p>
        ))}
      </div>
    </div>
 
    { /* ...省略... */ }
  );
}

最後に React の createRoot を使用して UiReact コンポーネントをレンダリングします。

src/ui-react.tsx
import { createRoot } from "react-dom/client";
 
window.addEventListener("load", () => {
  const root = document.getElementById("root");
  if (!root) {
    throw new Error("Root element not found");
  }
 
  createRoot(root).render(<UiReact />);
});

npm run build コマンドを実行して UI コンポーネントをビルドします。ビルドが成功すると、dist ディレクトリに ui-react.html ファイルが生成されます。

$ npm run build
 
> [email protected] build
> rm -rf dist && VITE_ENTRY=ui-react vite build
 
vite v7.2.4 building client environment for production...
 33 modules transformed.
[plugin vite:singlefile]
 
[plugin vite:singlefile] Inlining: ui-react-qBT9I1qt.js
dist/ui-react.html  287.03 kB gzip: 82.72 kB
 built in 496ms

MCP サーバーの実装

server.ts ファイルを作成し、MCP サーバーを実装します。基本的な構造は従来の MCP サーバーの実装と同様ですが、UI リソースの登録とツールのメタデータに ui/resourceUri フィールドを追加する点が異なります。初めに dist ディレクトリから UI リソースのコンテンツを読み込む loadHtml 関数を用意しておきましょう。

server.ts
import path from "node:path";
import { fileURLToPath } from "node:url";
import fs from "node:fs/promises";
 
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
 
// Load both UI HTML files from dist/
const distDir = path.join(__dirname, "dist");
const loadHtml = async (name: string) => {
  const htmlPath = path.join(distDir, `${name}.html`);
  return fs.readFile(htmlPath, "utf-8");
};

MCP サーバーのセットアップを行います。server.registerResource メソッドを使用して UI リソースを登録します。

server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
 
const server = new McpServer({
  name: "example-server",
  version: "0.1.0",
});
 
server.registerResource(
  // リソース名
  "ui-react",
  // リソースのURI
  "ui://example/ui-react",
  {
    // 人間が読むためのタイトル
    title: "UI React Example",
    // MIMEタイプは必ず text/html+mcp
    mimeType: "text/html+mcp",
  },
  // UI リソースの内容を返す関数
  async (): Promise<ReadResourceResult> => {
    const contentUiReact = await loadHtml("ui-react");
    return {
      contents: [
        {
          uri: "ui://example/ui-react",
          text: contentUiReact,
          mimeType: "text/html+mcp",
        },
      ],
    };
  }
);

次に、server.registerTool メソッドを使用して UI リソースを使用するツールを登録します。ツールのメタデータに ui/resourceUri フィールドを追加して、関連付けを行います。

server.ts
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
 
server.registerTool(
  "create-react-ui",
  {
    title: "Create React UI",
    description: "Returns a React-based UI",
    inputSchema: {},
    outputSchema: {
      message: z.string().describe("Message to display"),
    },
    // registerResourceで登録したUIリソースを返す
    _meta: {
      "ui/resourceUri": "ui://example/ui-react",
    },
  },
  // ツールの処理内容を実装する関数
  async (): Promise<CallToolResult> => {
    const message = "This is a React-based UI!";
    return {
      content: [{ type: "text", text: JSON.stringify({ message }) }],
      structuredContent: { message },
    };
  },
);

最後に Express サーバーをセットアップして MCP サーバーの仕様に従いリクエストを処理します。

server.ts
import express, { Request, Response } from "express";
import { randomUUID } from "node:crypto";
import cors from "cors";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { InMemoryEventStore } from "@modelcontextprotocol/sdk/examples/shared/inMemoryEventStore.js";
 
const app = express();
app.use(express.json());
app.use(
  cors({
    origin: "*",
    exposedHeaders: ["Mcp-Session-Id"],
  })
);
 
// Map to store transports by session ID
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
 
const mcpPostHandler = async (req: Request, res: Response) => {
  const sessionId = req.headers["mcp-session-id"] as string | undefined;
 
  try {
    let transport: StreamableHTTPServerTransport;
    if (sessionId && transports[sessionId]) {
      transport = transports[sessionId];
    } else if (!sessionId && isInitializeRequest(req.body)) {
      const eventStore = new InMemoryEventStore();
      transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: () => randomUUID(),
        eventStore,
        onsessioninitialized: (sessionId) => {
          console.log(`Session initialized: ${sessionId}`);
          transports[sessionId] = transport;
        },
      });
 
      transport.onclose = () => {
        const sid = transport.sessionId;
        if (sid && transports[sid]) {
          console.log(`Session closed: ${sid}`);
          delete transports[sid];
        }
      };
 
      await server.connect(transport);
      await transport.handleRequest(req, res, req.body);
      return;
    } else {
      res.status(400).json({
        jsonrpc: "2.0",
        error: { code: -32000, message: "Bad Request: No valid session ID" },
        id: null,
      });
      return;
    }
 
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    console.error("Error handling MCP request:", error);
    if (!res.headersSent) {
      res.status(500).json({
        jsonrpc: "2.0",
        error: { code: -32603, message: "Internal server error" },
        id: null,
      });
    }
  }
};
 
app.post("/mcp", mcpPostHandler);
 
app.get("/mcp", async (req: Request, res: Response) => {
  const sessionId = req.headers["mcp-session-id"] as string | undefined;
  if (!sessionId || !transports[sessionId]) {
    res.status(400).send("Invalid or missing session ID");
    return;
  }
  const transport = transports[sessionId];
  await transport.handleRequest(req, res);
});
 
app.delete("/mcp", async (req: Request, res: Response) => {
  const sessionId = req.headers["mcp-session-id"] as string | undefined;
  if (!sessionId || !transports[sessionId]) {
    res.status(400).send("Invalid or missing session ID");
    return;
  }
  try {
    const transport = transports[sessionId];
    await transport.handleRequest(req, res);
  } catch (error) {
    console.error("Error handling session termination:", error);
    if (!res.headersSent) {
      res.status(500).send("Error processing session termination");
    }
  }
});
 
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000;
 
app.listen(PORT, () => {
  console.log(`MCP Server listening on http://localhost:${PORT}/mcp`);
});

npm run server コマンドを実行して MCP サーバーを起動します。

$ npm run start
 
> [email protected] server
> tsx server.ts
 
MCP Server listening on http://localhost:3000/mcp

@modelcontextprotocol/inspector を使用して MCP サーバーの実装をテストします。

npx @modelcontextprotocol/inspector

リソースタブで ui://example/ui-react リソースが登録されていることを、ツールタブで create-react-ui ツールが登録されていることを確認します。

2025-11-22 時点では MCP Apps をサポートするホスト実装はまだ存在しないため UI コンポーネントを実際に表示できませんが MCP-UI では暫定的な実装が行われているようです。

まとめ

  • ChatGPT の Apps SDK や MCP-UI のように、AI エージェントがインタラクティブな UI を返す仕組みが注目されている
  • MCP Apps は MCP の拡張機能として、ui:// URI スキームを使用した UI リソースの宣言とツールとの関連付けを標準化する
  • JSON-RPC を使用したホストと iframe 間の双方向通信により、UI コンポーネントがツールの呼び出しや外部リンクのオープンなどを行える
  • @modelcontextprotocol/ext-apps SDK を使用することで、React などのフレームワークを用いた UI コンポーネントの実装が可能
  • 2025-11-22 時点では MCP Apps をサポートするホスト実装はまだ存在しないが、今後の普及が期待される

参考

記事の理解度チェック

以下の問題に答えて、記事の理解を深めましょう。

MCP Apps で UI リソースを宣言する際に使用する URI スキームは何ですか?

  • mcp://

    もう一度考えてみましょう

  • http://

    もう一度考えてみましょう

    http:// は一般的な Web リソースの URI スキームであり、MCP Apps の UI リソース宣言には使用しません。

  • app://

    もう一度考えてみましょう

  • ui://

    正解!

    MCP Apps では `ui://` URI スキームを使用して UI リソースを宣言します。例えば `ui://charts/bar-chart` のように使用します。

MCP Apps で UI リソースをツールと関連付けるために使用するメタデータフィールドは何ですか?

  • ui/resourceUri

    正解!

    ツールのメタデータに `ui/resourceUri` フィールドを追加することで、UI リソースとツールを関連付けます。

  • ui/resource

    もう一度考えてみましょう

    正しいフィールド名は `ui/resourceUri` です。

  • resourceUri

    もう一度考えてみましょう

  • ui/componentUri

    もう一度考えてみましょう

MCP Apps で UI コンポーネントのコンテンツタイプとして初期サポートされている MIME タイプは何ですか?

  • text/html

    もう一度考えてみましょう

    MCP Apps 固有の MIME タイプとして `text/html+mcp` が使用されます。

  • app/html

    もう一度考えてみましょう

  • text/html+mcp

    正解!

    MCP Apps の初期仕様では HTML コンテンツ(`text/html+mcp`)のみに焦点を当てており、将来には他のコンテンツタイプもサポートできるように設計されています。

  • text/html+skybridge

    もう一度考えてみましょう

    text/html+skybridge は ChatGPT の Apps SDK で使用される MIME タイプであり、MCP Apps では使用されません。