BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

AI との対話型 UI を拡張する MCP Apps

この記事はBASEアドベントカレンダー 2025 の 23 日目の記事です。

エンジニアの右京です。今年後半になって、主に Web ブラウザ上での AI との対話型 UI の利用シーンに、インタラクティブな UI を提供するという流れが注目されています。 この記事はそれを実現するために現在提案されている MCP Apps について簡単に紹介するものです。

対話型 UI の拡張

今年 10 月に OpenAI が Apps in ChatGPT として Apps SDK を発表しました。これは MCP のレスポンスとして ChatGPT の対話型 UI にアプリケーションを組み込むことができる SDK です。 これにより、ChatGPT の対話中に外部のアプリケーションを呼び出して、よりインタラクティブな体験を提供できることが期待されています。

https://developers.openai.com/images/apps-sdk/overview.png https://developers.openai.com/apps-sdk/concepts/ui-guidelines より引用

developers.openai.com

これまでテキストベースのみで行われていた対話に、HTML、CSS、JavaScript を用いたコンポーネントとしてアプリケーションを提供できるようになり、表現の幅が広がります。 さらに、MCP を用いて提供することで、それに対応するプラットフォームであれば恩恵を受けることができるようになります。

OpenAI の発表以前から MCP UI というものも存在していて、こちらも MCP を用いて対話型 UI を拡張するための仕組みです。 また、技術仕様は大きく異なりますが、同時期に OpenAI と Stripe から発表された Agentic Commerce も購買フローを対話的に完了することができ、対話型 UI を拡張する流れの一つだと言えるでしょう。

mcpui.dev www.agenticcommerce.dev

一方で Apps SDK や MCP UI の仕様はもちろん統一されているわけではなく、プラットフォームごとに互換性があるような状態にはなっていません。 そこで、この仕様を標準化できるようにと現在提案されているのが MCP Apps です。

MCP Apps

blog.modelcontextprotocol.io github.com

ここでは、Apps SDK の Quickstart でサンプルとして登場する Todo アプリを参考に、MCP Apps として実装する場合のイメージを紹介していきます。 サンプルコードと解説は @modelcontextprotocol/sdkzod を用いた MCP サーバーの実装をある程度把握していることを前提に記述しています。 また、 Apps は提案段階で現時点では動作する実装は試験的なものに限られていることに注意してください。あくまで実装のイメージとして捉えていただけると幸いです。もし、なにかしら動くものが欲しい場合は Apps SDK を利用するのが現実的です。

MCP Apps (と Apps SDK / MCP UI) は以下の 3 つの要素を中心に構成されます:

  • ui:// URI Scheme を利用した MCP Server への Resource 定義
  • サンドボックス化された iframe でのコンポーネント実行
  • postMessage を使った JSON-RPC での通信

MCP Apps では、まず MCP サーバーに対して ui:// URI Scheme で Resource を定義します。@modelcontextprotocol/sdk を使う場合は server.registerResource を利用します。

server.registerResource(
  "todo-widget",
  "ui://example/todo",
  {
    title: "Todo Widget",
    mimeType: "text/html;profile=mcp-app",
  },
  async () => {
    return {
      contents: [
        {
          uri: "ui://example/todo",
          text: `<!DOCTYPE html><html>...Todoアプリの実装...</html>`,
          mimeType: "text/html;profile=mcp-app",
        },
      ],
    };
  }
);

mimeTypetext/html;profile=mcp-app となっているのが特徴的ですね。ここで配信される HTML の全文は以下に折りたたんでいますが、全体を見なくとも雰囲気は掴めるかと思います。

Todoアプリの実装

<!DOCTYPE html>
<html>
<body>
  <input id="input" placeholder="Add task">
  <button onclick="addTodo()">Add</button>
  <ul id="list"></ul>

  <script>
    let tasks = [];

    window.addEventListener("message", (event) => {
      if (event.data.method === "ui/tool-result") {
        tasks = event.data.params?.structuredContent?.tasks || [];
        render();
      }
    });

    function addTodo() {
      const title = document.getElementById("input").value;
      window.parent.postMessage({
        jsonrpc: "2.0",
        method: "tools/call",
        params: { name: "add_todo", arguments: { title } },
        id: Date.now()
      }, "*");
    }

    function render() {
      document.getElementById("list").innerHTML =
        tasks.map(t => `<li>${t.title}</li>`).join("");
    }
  </script>
</body>
</html>

次にこの Resource をserver.registerTool_meta.ui.resourceUri として Tool に関連付けします。

server.registerTool(
  "add_todo",
  {
    title: "Add todo",
    description: "Creates a todo item with the given title.",
    inputSchema: {
        title: z.string().min(1)
    },
    _meta: {
      ui: {
        resourceUri: "ui://example/todo",
      }
    },
  },
  async (args) => {
    todos = [...todos, { id: uuid(), title: args.title, completed: false }];
    return {
      content: [{ type: "text", text: `Added ${args.title}` }],
      structuredContent: { tasks: todos },
    };
  },
);

これで add_todo が呼び出されると、MCP は ui:// URI Scheme で定義されたリソースを取得し、iframe へロードします。この iframe がチャット UI の中に表示されることで、インタラクティブな UI が提供されることになります。そして、Tool の実行結果が ui/tool-result として postMessage で JSON-RPC 形式で iframe 内に送信されます。

window.addEventListener("message", (event) => {
  if (event.data.method === "ui/tool-result") {
    tasks = event.data.params?.structuredContent?.tasks || [];
    render();
  }
});

この場合は add_todo で返された tasks がそのままメッセージに含まれているので、それを受け取って UI を更新しています。これで UI とチャットの内容が同期されるということですね。

アプリ側から Tool を呼び出すようなことも可能で、以下はタスクを追加するためのボタンをクリックすると Tool を呼び出す例です。

function addTodo() {
  const title = document.getElementById("input").value;
  window.parent.postMessage({
    jsonrpc: "2.0",
    method: "tools/call",
    params: { name: "add_todo", arguments: { title } },
    id: Date.now()
  }, "*");
}

method には専用のものがいくつか定義されており、リンクを開くよう要求したり、チャット UI へメッセージを返すことができるようです。 より詳しい現状の仕様は以下で確認できます:

github.com

また、今回は素の HTML でアプリを作成していますが、 React コンポーネントとして実装できるような仕組みも提供されています。 この辺りだったり、より詳しい日本語の情報として azukiazusa さんの記事が参考になるかと思います。この記事を書く際にも大変参考にさせていただきました。

azukiazusa.dev

所感

来年以降でこの辺りの整備が一気に進み、実用化されることでビジネスサイドからの注目度もより高まるのではないかと考えています。個人的には(少なくともCoding Agentの文脈の) MCP についてはコンテキストの圧迫やその費用対効果から少し懐疑的なのですが、チャット UI へのサービスの統合は一定のビジネスインパクトがあるだろうなとも感じています。特に Agentic Commerce は EC 領域において大きな影響を与えそうです。MCP が登場した当初から、「これからはチャットでタスクが完結するのでサービスごとのフロントエンドは不要(意訳)」というような意見もありましたが、それが少し現実味を帯びてきたのかもしれません。

ここまでに紹介したように MCP Apps や Apps in GPT は既存の Web 技術の上に成り立っています。これが将来的にどうなるかはわかりませんが、少なくとも数年の間は新しい Web フロントエンドの形として付き合っていく必要がありそうです。これまで一つのドメインに対して一貫性のあるデザインや操作を提供することが中心だった Web フロントエンド開発ですが、MCP Apps のような仕組みが普及することで、サービスの機能をコンポーネントとして切り出していく方向へシフトするかもしれません。サービスの利用方法の一つとして Remote MCP Server が提供されているのが当たり前になり、さらに複数の形で同一のサービスを提供することが一般的になる可能性もあります。

そのためには BFF のような役割を持つレイヤーの重要性が増しそうです。一方で UI Component や Design Token に関してはどの提供方法でも統一したかったりと、全体のアーキテクチャやコード管理の方法にも影響がありそうですし、API を整えたりということも必要でしょう。さらに未来に登場するであろう汎用プラットフォームに向けても対応できるような備えを、なんもわからんなりに考えておきたいですね!

おわりに

ざっくりとした紹介になってしまいましたが、MCP Apps のイメージが伝われば幸いです。 今回のアドベントカレンダーでは前半でも記事を書いているのでよかったらそちらもご覧ください!

devblog.thebase.in devblog.thebase.in

明日は @UedaHayato です!お楽しみに!