
MCP Apps Next.js スターターで始めるGenerative UI
はじめに
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

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で「設定」→「コネクタ」→「カスタムコネクタを追加」

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

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

Claude

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

画像表示の問題
カルーセルの実装後、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にする必要があります。
解決策: ハンドラで、相対パス(/ 始まり)の thumbnailImageUrl に baseURL を自動的にプリペンドする処理を追加しました:
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がうまく実装してくれないことが多いです。その場合は公式リポジトリや下記のコードを参照させると上手くいく確率が高まります。
公式リポジトリ
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)
メインページでは structuredContent の type を判定し、カルーセルデータの場合はカルーセルコンポーネントを表示するようにしました:
{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を実装しましたが、同じアプローチでダッシュボード、フォーム、データ可視化など、さまざまなインタラクティブなウィジェットを構築できます。
実装を通じて得られた知見:
- Vercel Starterが便利: MCPサーバー、ウィジェット、デプロイ設定が一式揃っているため、すぐに開発を始められる
- CSPに注意: iframe内で動作するため、外部リソースの読み込みにはCSP設定が必要
- 画像パスは絶対URLで: iframe内では相対パスが正しく解決されないため、サーバー側で絶対URLに変換する処理が重要
- Claudeでも動作: MCP Appsの仕組みはChatGPTだけでなくClaude.aiやCursorでも利用可能









