Skip to main content
Glama

ChatGPT Todo MCPデモ (Apps SDK + React)

ChatGPT用の最小限のTodoアプリです。MCPサーバーがツールとインタラクティブなHTML UIを公開します。React + Viteで構築され、単一ファイルのバンドルとして埋め込まれています。ChatGPTのコネクタウィザードが検出を完了できるように、小規模な開発用OAuthレイヤーが含まれています。

公式リファレンス: Apps SDKクイックスタート。

クイックスタート

npm install
npm start          # builds widget (prestart) then runs server on port 8787 by default
  • MCPエンドポイント: http://localhost:8787/mcp

  • ChatGPT用: HTTPSで公開し(例: ngrok)、https://<your-host>/mcpを指すコネクタを作成します。

  • トンネル経由で検出URLのスキームやホストが正しく表示されない場合は、以下を設定してください:

    export PUBLIC_BASE_URL=https://your-ngrok-host.example

Related MCP server: mcp-todo-demo

プロジェクト構成

パス

役割

server.js

HTTPルーター: OAuth検出 + CORS + /mcpでのMCP StreamableHTTPServerTransport

oauth-dev.js

開発専用のOAuth 2.1検出 + DCR/PKCE(本番環境では実際のIdPに置き換えてください)

widget/

チャット内UI用のVite + Reactソース

dist/todo-widget.html

ビルド済みの単一ファイルHTML(gitignored); 起動時にserver.jsによって読み込まれます


アーキテクチャとコンセプト

一文でのモデル

ChatGPTはMCPクライアントとして機能します。/mcpにあるNodeサーバーとHTTPS経由のMCPで通信します。サーバーはツール(モデルが呼び出せるもの)とリソース(ウィジェット用のHTML)を登録します。ウィジェットはiframe内で実行され、postMessageを介したJSON-RPCブリッジを通じてChatGPTと通信します。同じオリジン上のOAuthメタデータにより、ChatGPTはコネクタを接続できます。これはMCPツールの実行とは別ですが、オンボーディングには必須です。

Model Context Protocol (MCP)

MCPは、ホスト(ChatGPT)がサーバー上のツールを検出・呼び出し、リソースを読み取るための標準的な方法です。このリポジトリでは@modelcontextprotocol/sdkを使用しています。McpServerインスタンスが機能を登録し、MCPメッセージをHTTPにマッピングするトランスポート(StreamableHTTPServerTransport)に接続されます。

Base MCPとApps SDKヘルパー

  • @modelcontextprotocol/sdk: コアとなるMcpServer、スキーマ、トランスポート。

  • @modelcontextprotocol/ext-apps: registerAppToolおよびregisterAppResourceは、UIメタデータ(ツールを表示するためにどのHTMLリソースを使用するか)を正規化し、Apps HTMLのMIMEタイプ(RESOURCE_MIME_TYPE)を設定します。

ウィジェットは論理URI(例: ui://widget/todo.html)のリソースとして登録されます。そのURIは公開Web URLである必要はありません。ホストはMCPのresources/readを介して解決します。各ツールの_meta.ui.resourceUriは同じURIを指しているため、ChatGPTはどのUIサーフェスがどのツールに属しているかを認識できます。

HTTPフロントドア (server.js)

1つのNode http.Serverが複数のサーフェスを処理します:

  1. OAuth / 検出 (oauth-dev.js) — ChatGPTが期待する既知のURLとトークンエンドポイント。

  2. CORS /mcp用のOPTIONS。

  3. ヘルスチェック GET /。

  4. MCP ストリーミング可能なHTTPトランスポートを介した/mcpへのPOST / GET / DELETE。

  5. 404 不明なパス用。

つまり、1つのプロセスで複数の論理HTTP API(OAuth HTTP + MCP HTTP)を扱います。

ストリーミング可能なHTTPとサーバーのライフタイム

トランスポートは受信したMCPリクエストごとに作成され、sessionIdGenerator: undefined(このデモではステートレスモード)が使用されます。リクエストごとに新しいMcpServerが構築され、レスポンスが閉じられると破棄されます。

重要: インメモリのTodo状態(server.js内のtodos)は、McpServerインスタンス内ではなく、モジュールスコープに存在します。そのため、各リクエストが新しいMCPサーバーオブジェクトを取得しても、Nodeプロセスのライフタイム中は状態が保持されます。

ツールとUIの契約

ツール(add_todo、complete_todo)は入力スキーマ(Zod)を宣言し、ホストが引数を検証できるようにします。

ツールの結果には以下が含まれます:

  • content: モデル/会話用の通常のMCPコンテンツ(テキストなど)。

  • structuredContent: ウィジェットが消費するJSON(ここでは{ tasks: [...] })。

すべての変更に対して同じstructuredContentの形状を使用することで、呼び出しがウィジェット内のユーザーによってトリガーされたか、チャット内のモデルによってトリガーされたかに関わらず、React UIを同期させることができます。

OAuth (oauth-dev.js)

ChatGPTのコネクタフローは、OAuth保護リソースメタデータと認可サーバーメタデータを取得します(Apps SDK認証を参照)。これらのルートがないと、セットアップ時に「OAuth設定の取得エラー」が発生する可能性があります。

このリポジトリには、ChatGPTのリダイレクトURLにスコープされた開発専用の認可サーバー(検出、動的クライアント登録、認可リダイレクト、PKCEトークン交換)が含まれています。本番環境でそのまま使用しないでください。Auth0、Stytch、Cognitoなどに置き換え、MCPリクエストでトークンを検証してください。

PUBLIC_BASE_URLは、プロキシやngrokがHost / X-Forwarded-Protoを期待通りに設定しない場合に、メタデータ内の公開https://オリジンを強制します。

ウィジェットブリッジ (widget/src/bridge.ts)

ビルドされたHTMLはChatGPTのiframe内で実行されます。通常のSPAのように/mcp URLを呼び出すのではなく、MCP Apps UIブリッジを使用します:

  1. ui/initialize その後に ui/notifications/initialized — ホストとのハンドシェイク。

  2. tools/call — 名前付きMCPツールを引数付きで実行するようホストに依頼(モデルが使用するものと同じツール)。

  3. ui/notifications/tool-result — モデルがツールを実行した際、ホストが結果をプッシュすることで、tools/callからの直接の戻りパスなしでUIを更新できます。

つまり、2つの更新パスがあります。UIが開始した呼び出しに対するRPCレスポンスと、モデルが開始した呼び出しに対する通知です。

なぜ単一ファイルHTMLなのか (Vite + vite-plugin-singlefile)

ChatGPTはウィジェットを「あなたのサイト + 個別のJSチャンク」としてではなく、MCPリソース読み取りからの埋め込みHTMLとして受け取ります。相対的なチャンクURLは、その埋め込みモデルでは機能しません。ビルドによって、JS/CSSがインライン化された1つのdist/todo-widget.htmlが生成されます。server.jsは起動時にそれをtodoHtmlに読み込みます。

Reactは開発者の人間工学のためのレイヤーであり、デプロイ可能な成果物は静的なHTMLです。

エンドツーエンドのフロー

ChatGPTのユーザー: メッセージ → モデルがツールを選択 → ChatGPTがあなたの/mcpにPOST → ツールが実行 → structuredContent.tasksを返す → ホストがウィジェットを表示/更新。

ウィジェットのユーザー: React → postMessage経由でtools/call → ホストがMCPに転送 → 同じハンドラー → RPC結果が状態を更新。

コネクタ設定: ChatGPTがオリジンの/.well-known/...にアクセス → 必要に応じてOAuthリンク → その後の/mcpへのMCP呼び出しにはAuthorization: Bearer ...が含まれる場合があります(すべてのツールでこれを強制するのが本番ステップです)。

今後のステップ

エリア

方向性

状態

データベースにTodoを永続化し、アクセストークンから取得した認証済みユーザーIDでスコープ化する。

認証

oauth-dev.jsを実際のIdPに置き換え、各MCPリクエストで発行者、オーディエンス、スコープを検証する。

MCPセッション

異なるストリーミングやライフサイクルセマンティクスが必要な場合は、ステートフルなセッションを検討する。

ツール

より詳細な説明/スキーマ、オプションのoutputSchema、モデルルーティングのためのより明確な名前付け。

ウィジェット

同じブリッジを使用し、UX、エラー処理、読み込み状態を改善する。

スクリプト

スクリプト

説明

npm run build

widget/からdist/todo-widget.htmlをビルド

npm start

npm run buildを実行してからnode server.js

npm run build:widget

Viteビルドのみ

デフォルトポート: 8787 (PORT環境変数で上書き可能)。

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A minimal MCP server demonstrating how to build ChatGPT-compatible applications using Next.js with widget rendering capabilities. Provides a starter template for integrating Next.js applications with the ChatGPT Apps SDK through the Model Context Protocol.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    A minimal MCP server that provides an interactive to-do list with checkboxes in chat, demonstrating MCP Apps UI resource integration and tool-based state updates.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    A minimal Next.js application demonstrating how to build an OpenAI Apps SDK compatible MCP server with widget rendering in ChatGPT.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    A Model Context Protocol server with a built-in OAuth 2.1 authorization server and a Next.js todo app, enabling authenticated task management (create, read, update, delete tasks) via natural language through an MCP client.
    7 npm
    ISC