MCP Apps Next.js スターターで始めるGenerative UI

MCP Apps Next.js スターターで始めるGenerative UI

Vercel公式のMCP Appsスターターのご紹介。また、LINEのMessaging APIでおなじみのカルーセルメッセージを表示するツールを実装してみました。
2026.03.09

はじめに

2026年1月にMCP Projectから公開されたMCP Appsは、MCPサーバーがリッチなUIウィジェットをChatGPTやClaudeなどのAIチャットエージェント上に表示できる仕組みです。従来のAIチャットエージェントはテキストの入出力がメインでしたが、MCP Appsを使えばHTML/CSS/JSで構築したウィジェットをAIチャットエージェントのチャット画面内にiframeとしてレンダリングできます。

今回はVercel公式のスターターテンプレートをベースに、LINEのMessaging APIでおなじみのカルーセルメッセージを表示するMCP Appsを実装してみました。

使用した技術スタック

  • Next.js 16.1.6
  • MCP Apps SDK (@modelcontextprotocol/ext-apps)
  • mcp-handler (Next.js用MCPハンドラ)
  • Tailwind CSS 4
  • Vercel (ホスティング)
  • Zod (スキーマバリデーション)

Step 1: Starter テンプレートのデプロイ

MCPサーバーおよびMCP Appsは、mcp-handlerや@modelcontextprotocol/ext-appsを利用して1から作ることも可能ですが、情報が少ないこともありClaude CodeなどのAIコーディングエージェントでの構築はかなり苦戦します(体験談)

そんな中、我らがVercelが2026年3月4日に MCP Apps Next.js Starter の提供を開始。今回はこちらを利用してVercel上にMCP Appsを構築していきました。

使い方は簡単、こちらのサイトの「Deploy」ボタンをクリックするだけです。

https://vercel.com/templates/template/mcp-apps-next-js-starter

スクリーンショット 2026-03-07 15.09.45

GitHubのOrganizationとVercelのTeamを指定すると自動でVercelにデプロイされます。

Step 2: Greetツールの動作確認

まずStarterに含まれている greet ツールが正しく動作することを確認してみましょう。ChatGPTとClaude.aiの両方でMCPサーバーを接続し、ツールを呼び出して挨拶メッセージが表示されることを確認します。

ChatGPTでの設定方法

Web版のChatGPTで「設定」→「アプリ」→「高度な設定」で開発者モードをONにして、「アプリを作成する」するボタンを押下。

  • MCPサーバーのURL:VecelのDomainsのURL + /mcp
  • 認証:認証なし
  • 理解したうえで、続行しますにチェック

Claudeでの設定方法

Web版のClaudeで「設定」→「コネクタ」→「カスタムコネクタを追加」

スクリーンショット 2026-03-07 16.45.16

Greetツールの実行

チャット欄にスラッシュコマンドを入力して、設定画面で指定した名前を指定します。「Greetツールを試したいです。」と続けるとツールが実行されます。以下のような画面が表示されれば成功です。

スクリーンショット 2026-03-09 9.46.34

Step 3: カルーセルツールの実装

greetツールの動作確認が終わったら、ツールの追加実装を進めていきましょう。

Claude Codeで次のような指示をすると追加してくれます。

新しいツールを追加したいです。LINEのMessaging APIにおけるカルーセルテンプレートメッセージのスキーマを参考にして実装してください。
・https://developers.line.biz/ja/docs/messaging-api/message-types/#carousel-template
・1〜10個のカラム(カード)
・各カラムにはサムネイル画像、タイトル、テキスト、1〜3個のアクションボタン

ChatGPTとClaudeでそれぞれ以下のようなUIが表示されました。カードの数やボタンの数を動的にしたので、異なるボタンのUIがGenerativeされていることが分かります。

ChatGPT

スクリーンショット 2026-03-07 16.46.49

Claude

スクリーンショット 2026-03-07 16.44.54

本格的なGenerative UIを実装する場合は、MCPツールのスキーマ定義やコンポーネントカタログの定義などが必要になってきます。コンポーネントカタログは「json-render」が使いやすそうです(これはまたの機会に)

https://github.com/vercel-labs/json-render

問題が起きた時のTips

MCP Apps Next.jsスターターのDeployでエラーが発生する

私が試したときは次のエラーが発生しました。もし同じエラーが発生した方は、ワークアラウンドにはなりますが以下の対応で解消してください。

事象概要

Vercel デプロイ時に app/favicon.ico が Next.js 16 のルートハンドラーとして処理され、Response を返さないためビルドが失敗している。

エラー内容

Error: No response is returned from route handler '[project]/app/favicon--route-entry.js'.

修正手順

app/favicon.ico を public/favicon.ico に移動する

追加したツールが利用できない

Claudeはツールが動的にロードされますが、ChatGPTは動的ロードが無いようです。(2026年3月7日現在)

「設定」→「アプリ」で追加したアプリを選択して、「更新する」ボタンを押下してください。すると、アクションやテンプレートに追加したツールが表示されると思います。ツール修正後、動作に反映されないときも「更新する」ボタンを押下すると反映されます。

スクリーンショット 2026-03-07 15.59.25

画像表示の問題

カルーセルの実装後、ChatGPTで実際にツールを呼び出すと画像が表示されないという問題に2回遭遇しました。

問題1: CSP(Content Security Policy)によるブロック

MCP Appsのウィジェットはiframe内で動作するため、CSPが適用されます。外部ドメイン(例: placekitten.com)の画像はCSPでブロックされてしまいます。

解決策: サンプル画像を public/images/carousel/ に配置し、自ドメインから配信するようにしました。CSP設定で resourceDomains に自ドメインの baseURL を指定しています:

_meta: {
  ui: {
    csp: {
      connectDomains: [baseURL],
      resourceDomains: [baseURL],
    },
  },
},

問題2: iframe内での相対パス問題

画像を自ドメインに配置しても、LLMが thumbnailImageUrl/images/carousel/cat-1.jpg のような相対パスを渡す場合、iframe内ではこのパスがアプリのドメインに正しく解決されません。httpから始まるURLにする必要があります。

解決策: ハンドラで、相対パス(/ 始まり)の thumbnailImageUrlbaseURL を自動的にプリペンドする処理を追加しました:

async ({ altText, columns, imageAspectRatio, imageSize }) => {
  const resolvedColumns = columns.map((c) => ({
    ...c,
    thumbnailImageUrl: c.thumbnailImageUrl?.startsWith("/")
      ? `${baseURL}${c.thumbnailImageUrl}`
      : c.thumbnailImageUrl,
  }));

  return {
    structuredContent: {
      type: "carousel",
      altText,
      imageAspectRatio,
      imageSize,
      columns: resolvedColumns, // 絶対URLに変換済み
    },
  };
};

baseURL はVercel環境では VERCEL_PROJECT_PRODUCTION_URL などの環境変数から自動解決されるため、デプロイ環境に応じた正しいURLが使われます。

MCP Appsの実装がうまくいかない

MCP Appsは新しい技術なのでClaude Codeがうまく実装してくれないことが多いです。その場合は公式リポジトリや下記のコードを参照させると上手くいく確率が高まります。

公式リポジトリ

https://github.com/modelcontextprotocol/ext-apps

Claude Codeをご利用の場合は /add-dir で上記リポジトリを参照させると良きです

MCPツール定義(app/mcp/route.ts

Zodでスキーマを定義し、registerAppTool でツールを登録しました:

const carouselActionSchema = z.object({
  type: z.enum(["uri", "message", "postback"]),
  label: z.string(),
  uri: z.string().optional(),
  text: z.string().optional(),
  data: z.string().optional(),
});

const carouselColumnSchema = z.object({
  thumbnailImageUrl: z.string().optional(),
  title: z.string().optional(),
  text: z.string(),
  defaultAction: carouselActionSchema.optional(),
  actions: z.array(carouselActionSchema).min(1).max(3),
});

registerAppTool(
  server,
  "carousel",
  {
    title: "Carousel",
    description:
      "Display a LINE-style carousel template message in the widget.",
    inputSchema: {
      altText: z.string().describe("Alternative text for accessibility"),
      columns: z.array(carouselColumnSchema).min(1).max(10),
      imageAspectRatio: z.enum(["rectangle", "square"]).optional(),
      imageSize: z.enum(["cover", "contain"]).optional(),
    },
  },
  async ({ altText, columns, imageAspectRatio, imageSize }) => {
    // ...ハンドラ実装
  },
);

ウィジェットUI(app/components/carousel.tsx

Reactコンポーネントとして、LINE風のカルーセルUIを実装しました。Tailwind CSSで横スクロール可能なカードリストを構成しています:

export function Carousel({ data }: { data: CarouselData }) {
  return (
    <div className="flex gap-3 overflow-x-auto snap-x snap-mandatory py-2">
      {data.columns.map((col, i) => (
        <CarouselCard
          key={i}
          column={col}
          imageAspectRatio={data.imageAspectRatio}
          imageSize={data.imageSize}
        />
      ))}
    </div>
  );
}

各カードは角丸のカードUI、サムネイル画像、タイトル・テキスト、LINEカラー(#06C755)のアクションボタンで構成されます。

ページ側の統合(app/page.tsx

メインページでは structuredContenttype を判定し、カルーセルデータの場合はカルーセルコンポーネントを表示するようにしました:

{data && isCarouselData(data) ? (
  <Carousel data={data} />
) : (
  <pre>{data ? JSON.stringify(data, null, 2) : "Waiting for tool call..."}</pre>
)}

まとめ

MCP Appsを使うことで、ChatGPTやClaudeのチャット画面内にリッチなUIを表示できるようになります。今回はLINE風のカルーセルUIを実装しましたが、同じアプローチでダッシュボード、フォーム、データ可視化など、さまざまなインタラクティブなウィジェットを構築できます。

実装を通じて得られた知見:

  1. Vercel Starterが便利: MCPサーバー、ウィジェット、デプロイ設定が一式揃っているため、すぐに開発を始められる
  2. CSPに注意: iframe内で動作するため、外部リソースの読み込みにはCSP設定が必要
  3. 画像パスは絶対URLで: iframe内では相対パスが正しく解決されないため、サーバー側で絶対URLに変換する処理が重要
  4. Claudeでも動作: MCP Appsの仕組みはChatGPTだけでなくClaude.aiやCursorでも利用可能

この記事をシェアする

関連記事