For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
メインナビゲーション
Theme
概要 ChatGPT と Codex のユーザー向けドキュメント ユースケース

リファレンス

ChatGPT 固有の UI 拡張機能とメタデータのリファレンスです。

まずはオープン標準を使ってください。共通の UI フィールドとブリッジメソッドについては、

MCP Apps 仕様

を参照してください。 OpenAI の拡張機能の利用は任意です。ChatGPT 固有の機能が必要な場合は window.openai から利用できます。

window.openai コンポーネントブリッジ

ChatGPT は、互換性を保つためのエイリアスと ChatGPT 固有のオプションの拡張機能を window.openai で提供しています。新しい UI では、共通仕様に同等の機能があれば MCP Apps ブリッジを使い、ChatGPT 固有の機能に限って window.openai を使ってください。

具体的な実装手順は「ChatGPT UI の構築」を参照してください。

ユーザーの確認が必要なツールでは、初期状態で toolInput がなくても正常です。ChatGPT は、承認が必要な引数を承認前にウィジェットの値に読み込みません。ユーザーが呼び出しを承認すると、ホストが ui/notifications/tool-input を通じて引数を渡します。

利用できる機能

機能の分類 提供される機能 主な用途
状態とデータ window.openai.toolInput ツールの呼び出し時に指定された引数です。承認が必要なツールでは、この値が null のまま、承認後にホストが ui/notifications/tool-input を送信するのを待つ場合があります。
状態とデータ window.openai.toolOutput ツールが返す structuredContent です。モデルは各フィールドの内容をそのまま読むため、簡潔に記述してください。
状態とデータ window.openai.toolResponseMetadata ウィジェットだけに渡される正式なツール結果メタデータです。ChatGPT では status、call_tool_result、mcp_tool_result が含まれ、非公開の _meta を含む MCP 結果の構造全体が保持されます。
状態とデータ window.openai.widgetState レンダリングをまたいで保持される UI 状態のスナップショットです。
状態とデータ window.openai.setWidgetState(state) 新しいスナップショットを同期的に保存します。状態に影響する UI 操作のたびに呼び出してください。
ウィジェットのランタイム API window.openai.callTool(name, args) ウィジェットから別の MCP ツールを呼び出します。モデルからの呼び出しと同様に動作します。
ウィジェットのランタイム API window.openai.sendFollowUpMessage({ prompt, scrollToBottom }) コンポーネントが作成したメッセージを投稿するよう ChatGPT に依頼します。scrollToBottom は任意で、デフォルトは true です。false に設定すると自動スクロールを無効にできます。
ウィジェットのランタイム API window.openai.uploadFile(file, { library?: boolean }) ユーザーが選択したファイルをアップロードし、fileId を受け取ります。{ library: true } を渡すと、ユーザーが ChatGPT のファイルライブラリを利用できる場合は、アップロードしたファイルがそこにも保存されます。
ウィジェットのランタイム API window.openai.selectFiles() ChatGPT のファイルライブラリでファイル選択画面を開き、プラグインからのアクセスが許可されたファイルを { fileId, fileName, mimeType }[] として返します。ユーザーによってはファイルライブラリを利用できないため、このヘルパーの有無を確認してから使ってください。
ウィジェットのランタイム API window.openai.getFileDownloadUrl({ fileId }) ファイルの一時ダウンロード URL を取得します。ウィジェットがアップロードしたファイル、ファイルライブラリから選択したファイル、ファイルパラメーターで渡されたファイル、ツールがファイル参照として返したファイルが対象です。
ウィジェットのランタイム API window.openai.requestDisplayMode(...) ピクチャーインピクチャー(PiP)または全画面での表示をリクエストします。
ウィジェットのランタイム API window.openai.requestModal({ params, template }) ChatGPT が管理するモーダルを開きます。template を省略すると現在のテンプレートが使われます。登録済みのテンプレート URI を渡すと、モーダルの内容を切り替えられます。
ウィジェットのランタイム API window.openai.requestClose() 現在のウィジェットを閉じるよう ChatGPT に依頼します。
ウィジェットのランタイム API window.openai.notifyIntrinsicHeight(...) ウィジェットの高さが変わったときにその値を通知し、スクロール領域の内容が見切れるのを防ぎます。
ウィジェットのランタイム API window.openai.openExternal({ href, redirectUrl }) 確認済みの外部リンクをユーザーのブラウザで開きます。承認済みのリダイレクト先には、ChatGPT がデフォルトで ?redirectUrl=... を追加します。redirectUrl: false を設定すると追加されません。
ウィジェットのランタイム API window.openai.setOpenInAppUrl({ href }) 全画面表示時に示す外部リンク先を任意で変更できます。未設定の場合、ChatGPT はコンポーネントの現在の iframe パスを開きます。
コンテキスト window.openai.theme、window.openai.displayMode、window.openai.maxHeight、window.openai.safeArea、window.openai.view、window.openai.userAgent、window.openai.locale 表示や文言を環境に合わせるための情報です。useOpenAiGlobal を通じて値を読み取ったり、変更を購読したりできます。

useOpenAiGlobal ヘルパー

多くの ChatGPT UI プロジェクトでは、ビューをテストしやすくするため、window.openai へのアクセスを小さなヘルパー関数にまとめています。次のヘルパーはホストの openai:set_globals イベントを監視し、React コンポーネントから個別のグローバル値の変更を購読できるようにします。

import { useSyncExternalStore } from "react";

type WebplusGlobals = {
  widgetState: { selectedId?: string | null; [key: string]: unknown } | null;
  toolInput: Record<string, unknown>;
  toolOutput: Record<string, unknown> | null;
  theme: "light" | "dark";
};
const SET_GLOBALS_EVENT_TYPE = "openai:set_globals";

export function useOpenAiGlobal<K extends keyof WebplusGlobals>(
  key: K
): WebplusGlobals[K] {
  return useSyncExternalStore(
    (onChange) => {
      const handleSetGlobal = (event: Event) => {
        if (!(event instanceof CustomEvent)) return;
        const value = event.detail?.globals?.[key];
        if (value === undefined) {
          return;
        }

        onChange();
      };

      window.addEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal, {
        passive: true,
      });

      return () => {
        window.removeEventListener(SET_GLOBALS_EVENT_TYPE, handleSetGlobal);
      };
    },
    () => {
      const globals = (window as Window & { openai?: WebplusGlobals }).openai;
      if (!globals) throw new Error("This hook requires a ChatGPT app host.");
      return globals[key];
    }
  );
}

UI を閉じるリクエスト

window.openai.requestClose() を呼び出すと、現在の UI を閉じるよう ChatGPT に依頼できます。

表示モードの変更リクエスト

window.openai.requestDisplayMode を使って、インライン、ピクチャーインピクチャー、全画面のいずれかの表示モードをリクエストします。

await window.openai?.requestDisplayMode({ mode: "fullscreen" });
// On mobile, picture-in-picture may be presented as fullscreen.

モーダルの表示

window.openai.requestModal を使うと、ホストが管理するモーダルを開けます。同じ MCP サーバーが登録した別の UI テンプレートの URI を指定してください。template を省略すると、現在のテンプレートが開きます。

await window.openai.requestModal({
  template: "ui://widget/details.html",
});

ファイル API

ChatGPT は、ファイルのアップロードとダウンロードに使えるヘルパーを window.openai のオプションの拡張機能として提供しています。

API 用途 補足
window.openai.uploadFile(file, { library?: boolean }) ユーザーが選択したファイルをアップロードし、fileId を受け取ります。 { library: true } を渡すと、現在のユーザーが ChatGPT のファイルライブラリを利用できる場合は、アップロードしたファイルがそこにも保存されます。
window.openai.selectFiles() ファイルライブラリの選択画面を開き、既存のファイルを選択します。 [{ fileId, fileName, mimeType }] を返します。ユーザーによってはファイルライブラリを利用できないため、このヘルパーの有無を確認してから使ってください。
window.openai.getFileDownloadUrl({ fileId }) ファイルの一時ダウンロード URL をリクエストします。 ウィジェットがアップロードしたファイル、ファイルライブラリから選択したファイル、ファイルパラメーターで渡されたファイル、ツールがファイル参照として返したファイルに使えます。

ChatGPT のファイルライブラリはオプション機能で、ユーザーによっては利用できない場合があります。window.openai.selectFiles() が利用できる場合、このヘルパーが返すファイルには、現在のプラグインからのアクセスがすでに許可されています。返された fileId は、window.openai.getFileDownloadUrl({ fileId }) に渡すか、ファイルパラメーターを使うツールの入力に指定してください。

ユーザーが選択したファイルをアップロードする例です。

const { fileId } = await window.openai.uploadFile(file, {
  library: true,
});

ユーザーが ChatGPT にアップロード済みのファイルを選択する例です。

if (window.openai?.selectFiles) {
  const files = await window.openai.selectFiles();
  // [{ fileId, fileName, mimeType }]
}

window.openai.selectFiles の有無を確認し、ファイルライブラリを利用できない場合は window.openai.uploadFile を使ってください。

一時ダウンロード URL をリクエストする例です。

const { downloadUrl } = await window.openai.getFileDownloadUrl({ fileId });

ファイル入力の定義

ChatGPT からツールにファイルを渡せるようにするには、トップレベルのファイル入力フィールドをすべて _meta["openai/fileParams"] に列挙します。列挙した各フィールドの値は、ファイルオブジェクトまたはその配列である必要があります。

ファイルオブジェクトのスキーマには、サポートされている次の 4 つのプロパティをすべて宣言してください。

プロパティ 型 properties で宣言 required に含める
download_url string はい はい
file_id string はい はい
mime_type string はい いいえ
file_name string はい いいえ

mime_type と file_name の値は任意ですが、プロパティ自体はスキーマで宣言する必要があります。ツールのスキャンのステップでもプラグインの提出時でも、次のいずれかに該当するファイルスキーマは拒否されます。4 つのプロパティのいずれかを省略している場合、download_url と file_id のいずれかが必須になっていない場合、上記の任意プロパティのどちらかを必須にしている場合、または download_url と file_id 以外のプロパティを必須にしている場合です。任意のプロパティは追加で宣言できます。

以下は、必須のファイル入力を 1 つ受け取るツール記述子の完全な例です。

{
  "name": "analyze_file",
  "title": "Analyze file",
  "description": "Analyzes a user-provided file without modifying it.",
  "inputSchema": {
    "type": "object",
    "$defs": {
      "OpenAIFile": {
        "type": "object",
        "properties": {
          "download_url": { "type": "string" },
          "file_id": { "type": "string" },
          "mime_type": { "type": "string" },
          "file_name": { "type": "string" }
        },
        "required": ["download_url", "file_id"],
        "additionalProperties": false
      }
    },
    "properties": {
      "file": { "$ref": "#/$defs/OpenAIFile" }
    },
    "required": ["file"]
  },
  "annotations": {
    "readOnlyHint": true,
    "openWorldHint": false,
    "destructiveHint": false
  },
  "_meta": {
    "openai/fileParams": ["file"]
  }
}

複数のファイルを受け取るには、トップレベルのフィールドを配列として定義し、items に同じファイルオブジェクトのスキーマを指定します。個々のファイルオブジェクト内でどのプロパティを必須とするかとは別に、ツールはトップレベルのファイルフィールド自体を必須にできます。

実行時、ChatGPT はスネークケースのフィールド名を持つオブジェクトとしてファイルの値を渡します。

{
  "download_url": "https://...",
  "file_id": "file_...",
  "mime_type": "image/png",
  "file_name": "input.png"
}

ChatGPT は常に download_url と file_id を含めますが、mime_type と file_name は省略する場合があります。ウィジェットで新しい一時ダウンロード URL が必要になったら、file_id を fileId の値として window.openai.getFileDownloadUrl({ fileId }) に渡してください。

ウィジェットの状態を保存する際、後続のターンでモデルが画像 ID を参照できるようにするには、構造化形式(modelContent、privateContent、imageIds)を使ってください。

ホストと連動するナビゲーション

サンドボックスのランタイムは、iframe 内のナビゲーション履歴を ChatGPT の UI に反映します。React Router などの標準的なルーティング API を使うと、ホストのナビゲーションコントロールが UI と同期します。

React Router の BrowserRouter を使ったルーターの設定例です。

import {
  BrowserRouter,
  Routes,
  Route,
  Link,
  useParams,
} from "react-router-dom";
function PizzaListPlugin() {
  const { placeId } = useParams();
  return (
    <main>
      <Link to="/place/pizzeria">Example Pizzeria</Link>
      <p>{placeId ?? "Choose a place"}</p>
    </main>
  );
}

export default function PizzaListRouter() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<PizzaListPlugin />}>
          <Route path="place/:placeId" element={<PizzaListPlugin />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

コードから画面を遷移させる例です。

import { createElement } from "react";
import { BrowserRouter, useNavigate } from "react-router-dom";
function NavigationExample() {
  const navigate = useNavigate();

  function openDetails(placeId: string) {
    navigate(`place/${placeId}`, { replace: false });
  }

  function closeDetails() {
    navigate("..", { replace: true });
  }

  return createElement(
    "div",
    null,
    createElement(
      "button",
      { onClick: () => openDetails("pizzeria") },
      "Open details"
    ),
    createElement("button", { onClick: closeDetails }, "Close details")
  );
}

ツール記述子のパラメーター

原則として、ツールの記述には MCP 仕様に記載されているフィールドを含めてください。

outputSchema は、structuredContent を返すすべてのツールで宣言してください。ツールが返すオブジェクトの構造を正確に記述すると、クライアントが結果を検証でき、モデルが後続のツール呼び出しを判断できます。

ツール記述子の _meta フィールド

ツール記述子には次の _meta フィールドを使います。ツールを UI テンプレートに関連付けるには、MCP Apps の標準キー _meta.ui.resourceUri を優先してください。ChatGPT は、互換性の維持とオプションの拡張機能のために OpenAI 固有のメタデータをサポートしています。

キー 設定箇所 型 制約 用途
_meta["securitySchemes"] ツール記述子 array なし _meta だけを読み取るクライアントとの後方互換性を保つため、セキュリティスキームをここにも記載します。
_meta.ui.resourceUri ツール記述子 string(URI) なし UI テンプレートの標準リソース URI です。
_meta.ui.visibility ツール記述子 string[] デフォルトは ["model", "app"] ツールをモデル、UI、またはその両方のどこから利用できるかを指定します。app は、MCP Apps プロトコルで UI を表す識別子です。
_meta["openai/outputTemplate"] ツール記述子 string(URI) なし ChatGPT の _meta.ui.resourceUri に対応する OpenAI 固有のエイリアスです。互換性のために任意で使えます。
_meta["openai/profile"] ツール記述子 boolean 任意。true の場合のみプロフィールツールとして指定 現在のプロフィールを返す、認証付きの読み取り専用ツールを識別します。実装すると、ユーザーが接続済みの複数のアカウントを区別して管理しやすくなります。このツールがなくても、複数のアカウントを接続できます。「複数アカウントのサポート」を参照してください。
_meta["openai/widgetAccessible"] ツール記述子 boolean デフォルトは false 既存の UI 統合で使われている OpenAI 固有の互換フィールドです。_meta.ui.visibility と tools/call の組み合わせを優先してください。
_meta["openai/visibility"] ツール記述子 string public(デフォルト)または private 既存の UI 統合で使われている OpenAI 固有の互換フィールドです。_meta.ui.visibility を優先してください。
_meta["openai/toolInvocation/invoking"] ツール記述子 string 64 文字以内 ツールの実行中に表示する短いステータスメッセージです。
_meta["openai/toolInvocation/invoked"] ツール記述子 string 64 文字以内 ツールの実行完了後に表示する短いステータスメッセージです。
_meta["openai/fileParams"] ツール記述子 string[] なし ファイルを表すトップレベルの入力フィールドを列挙します。各フィールドは { download_url, file_id, mime_type?, file_name? } を受け取ります。

例を示します。

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({ name: "docs-example", version: "1.0.0" });

// Local example data; replace this with your application data.
async function performSearch(query: string) {
  const documents = [
    {
      id: "intro",
      title: "Introduction to Acme",
      url: "https://example.com/intro",
    },
  ];
  return documents.filter((document) =>
    document.title.toLowerCase().includes(query.toLowerCase())
  );
}

import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
import { z } from "zod";

registerAppTool(
  server,
  "search",
  {
    title: "Public Search",
    description: "Search public documents.",
    inputSchema: { q: z.string() },
    outputSchema: {
      results: z.array(
        z.object({
          id: z.string(),
          title: z.string(),
          url: z.string(),
        })
      ),
    },
    _meta: {
      securitySchemes: [
        { type: "noauth" },
        { type: "oauth2", scopes: ["search.read"] },
      ],
      ui: { resourceUri: "ui://widget/story.html" },
      // Optional compatibility alias (ChatGPT only):
      // "openai/outputTemplate": "ui://widget/story.html",
      "openai/toolInvocation/invoking": "Searching…",
      "openai/toolInvocation/invoked": "Results ready",
    },
  },
  async ({ q }) => {
    const results = await performSearch(q);

    return {
      structuredContent: { results },
      content: [{ type: "text", text: `Found ${results.length} results.` }],
    };
  }
);

アノテーション

ツールを「読み取り専用」として示すには、ツール記述子に次の ToolAnnotations のフィールドを設定します。

キー 型 必須かどうか 補足
readOnlyHint boolean 必須 ツールが情報の取得または計算だけを行い、会話の外でデータを作成、更新、削除したり、会話の外に送信したりしないことを示します。
destructiveHint boolean 必須 ツールがユーザーデータを削除または上書きする可能性があることを示します。これにより、ホストは実行前に明示的な承認を求める必要があると判断できます。
openWorldHint boolean 必須 ツールが公開インターネットや対象範囲の限定されていない外部リソースにアクセスすることを示します。ウェブ検索などの読み取り専用操作も対象です。アクセス範囲が限定された非公開アカウントやワークスペースは、外部でホストされているという理由だけでこの分類に該当するわけではありません。
idempotentHint boolean 任意 同じ引数でツールを繰り返し呼び出しても、環境に追加の影響がないことを示します。

これらのヒントが影響するのは、ChatGPT や Codex がユーザーにツール呼び出しをどう説明するかだけです。サーバー側では独自の認可ロジックを適用する必要があります。

例を示します。

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({ name: "docs-example", version: "1.0.0" });

// Local example data; replace this with your application data.
async function fetchSavedRecipes() {
  return [{ id: "pasta", title: "Tomato pasta" }];
}

import { z } from "zod";

server.registerTool(
  "list_saved_recipes",
  {
    title: "List saved recipes",
    description: "Returns the user’s saved recipes without modifying them.",
    inputSchema: {},
    outputSchema: {
      recipes: z.array(
        z.object({
          id: z.string(),
          title: z.string(),
        })
      ),
    },
    annotations: { readOnlyHint: true },
  },
  async () => ({
    content: [],
    structuredContent: { recipes: await fetchSavedRecipes() },
  })
);

コンポーネントリソースの _meta フィールド

コンポーネントを配信するリソーステンプレート(registerResource)に、次のキーを設定します。ChatGPT はこの情報を使って、他のクライアントにメタデータを公開せずに、レンダリングした iframe の内容を説明し、表示の仕方を調整できます。

キー 設定箇所 型 用途
_meta.ui.prefersBorder リソースの内容 boolean 対応している場合は、コンポーネントを枠線付きのカード内に表示するよう促すヒントです。
_meta.ui.csp リソースの内容 object ウィジェットの標準 CSP フィールドである connectDomains、resourceDomains、および任意の frameDomains の推奨設定箇所です。
_meta.ui.domain リソースの内容 string(オリジン) ホストされるコンポーネント専用のオリジンです。UI を持つプラグインの提出時には必須で、プラグインごとに一意である必要があります。デフォルトは https://web-sandbox.oaiusercontent.com です。
_meta["openai/ui"].availableDisplayModes リソースの内容 string[] サポートする表示モードは inline、fullscreen、pip から指定します。ChatGPT はコンポーネントを読み込む前に表示モードを選択できます。
_meta["openai/widgetDescription"] リソースの内容 string コンポーネントの読み込み時にモデルに渡す、人が読んで理解できる要約です。アシスタントによる重複した説明を減らせます。
_meta["openai/widgetPrefersBorder"] リソースの内容 boolean ChatGPT の _meta.ui.prefersBorder に対応する OpenAI 固有の互換エイリアスです。
_meta["openai/widgetCSP"] リソースの内容 object ウィジェットの CSP メタデータに使う従来の ChatGPT 互換キーです。標準 CSP フィールドの設定先は _meta.ui.csp に移行しましたが、redirect_domains は、信頼済みの openExternal リンク先を指定するために引き続き必要です。
_meta["openai/widgetDomain"] リソースの内容 string(オリジン) ChatGPT の _meta.ui.domain に対応する OpenAI 固有の互換エイリアスです。

ChatGPT は従来の互換キー _meta["openai/widgetCSP"] もサポートしています。このキーでは、次のスネークケースのフィールド名を使います。

  • connect_domains: string[]
  • resource_domains: string[]
  • frame_domains?: string[]
  • redirect_domains?: string[]。window.openai.openExternal のリダイレクト先を指定する ChatGPT 拡張機能

新しい UI では通常、標準の _meta.ui.csp オブジェクトが推奨されます。次のフィールドをサポートしています。

  • connectDomains: string[]。ウィジェットが fetch/XHR で接続できるドメイン
  • resourceDomains: string[]。静的アセット(画像、フォント、スクリプト、スタイル)を読み込めるドメイン
  • frameDomains?: string[]。iframe の埋め込みを許可するオリジンのリストです。指定は任意です。デフォルトでは、ウィジェット内にサブフレームを表示できません。iframe ポリシーに従えば、プラグインは自身のドメインのページを埋め込むことができ、既存のエディターや管理画面も利用できます。提出時には埋め込みが必要な理由を説明してください。iframe を使うと追加の審査が必要になったり、承認に時間がかかったりする場合があります。

ただし、_meta.ui.csp では、redirect_domains を使って window.openai.openExternal(...) のリダイレクト先を指定できません。リダイレクト先を許可リストに追加するには、引き続き _meta["openai/widgetCSP"].redirect_domains を設定する必要があります。

UI 読み込み前の表示モードの宣言

サーバーが返すリソースの内容に _meta["openai/ui"].availableDisplayModes を設定します。全画面表示専用のコンポーネントであれば、ChatGPT はインラインで読み込み中の表示を出さずに、最初から全画面で開けます。

次のオブジェクトをリソースの contents 配列に含めて返します。html にはコンポーネントの HTML を設定してください。

const resource = {
  uri: "ui://widget/viewer.html",
  mimeType: "text/html;profile=mcp-app",
  text: html,
  _meta: {
    "openai/ui": {
      availableDisplayModes: ["fullscreen"],
    },
  },
};

["inline", "fullscreen"] のように、コンポーネントがサポートするすべてのモードを列挙してください。availableDisplayModes は MCP Apps の初期化時にも引き続き宣言します。初期化時の宣言は動的な表示モードにも対応しており、サーバー側の宣言はそれを補うものです。

ツール結果

ツール結果には、MCP 仕様のフィールドを含めることができます。主なフィールドは次のとおりです。

キー 型 必須かどうか 補足
structuredContent object 任意 モデルとコンポーネントの両方に渡されます。outputSchema を宣言した場合は、そのスキーマに準拠する必要があります。
content string または Content[] 任意 モデルとコンポーネントの両方に渡されます。
_meta object 任意 コンポーネントだけに渡されます。モデルからは見えません。

会話の記録に含まれるのは structuredContent と content だけです。ホストは _meta をコンポーネントに転送するため、モデルにデータを公開せずに UI に値を反映できます。

ホストが提供するツール結果メタデータは次のとおりです。

キー 設定箇所 型 用途
_meta["openai/widgetSessionId"] ツール結果の _meta(ホストが提供) string 現在マウントされているウィジェットインスタンスの ID です。アンマウントされるまで変わらないため、ログとツール呼び出しの対応付けに使えます。

例を示します。

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({ name: "docs-example", version: "1.0.0" });

// Local example data; replace this with your application data.
function generateZooAnimals(count: number) {
  return Array.from({ length: count }, (_, index) => ({
    id: `animal-${index + 1}`,
    name: `Penguin ${index + 1}`,
    species: "Penguin",
  }));
}

import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
import { z } from "zod";

registerAppTool(
  server,
  "get_zoo_animals",
  {
    title: "get_zoo_animals",
    inputSchema: { count: z.number().int().min(1).max(20).optional() },
    outputSchema: {
      animals: z.array(
        z.object({
          id: z.string(),
          name: z.string(),
          species: z.string(),
        })
      ),
    },
    _meta: { ui: { resourceUri: "ui://widget/widget.html" } },
  },
  async ({ count = 10 }) => {
    const animals = generateZooAnimals(count);

    return {
      structuredContent: { animals },
      content: [{ type: "text", text: `Here are ${animals.length} animals.` }],
      _meta: {
        allAnimalsById: Object.fromEntries(
          animals.map((animal) => [animal.id, animal])
        ),
      },
    };
  }
);

ツール結果でのエラーの通知

ツール結果でエラーを返すには、次の _meta キーを使います。

キー 用途 型 補足
_meta["mcp/www_authenticate"] エラー結果 string または string[] RFC 7235 の WWW-Authenticate チャレンジを使って OAuth を開始します。

クライアントが提供する _meta フィールド

キー 提供されるタイミング 型 用途
_meta["openai/locale"] 初期化時とツール呼び出し時 string(BCP 47) リクエストされたロケールです。旧クライアントは _meta["webplus/i18n"] を送信する場合があります。
_meta["openai/userAgent"] ツール呼び出し時 string 分析や表示形式の調整に使えるユーザーエージェントのヒントです。取得できた場合にのみ提供されます。
_meta["openai/userLocation"] ツール呼び出し時 object おおまかな位置情報のヒントです(city、region、country、timezone、longitude、latitude)。
_meta["openai/subject"] ツール呼び出し時 string レート制限とユーザー識別のために MCP サーバーに送信される、匿名化されたユーザー ID です。
_meta["openai/session"] ツール呼び出し時 string 同じ ChatGPT セッション内のツール呼び出しを対応付けるための、匿名化された会話 ID です。
_meta["openai/organization"] ツール呼び出し時 string 現在の ChatGPT 組織に関連付けられた、匿名化された組織 ID です。取得できる場合に提供されます。

操作時に渡される _meta["openai/userAgent"] と _meta["openai/userLocation"] は、あくまでヒントです。サーバーはこれらの値を認可の根拠にせず、値がなくても動作するようにしてください。_meta["openai/userAgent"] は取得できた場合にのみ提供される任意のメタデータです。サーバーを呼び出しているホストのインターフェースを確実に識別する手段としては使えません。

例を示します。

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({ name: "docs-example", version: "1.0.0" });

// Local example data; replace this with your application data.
async function findNearbyCafes(city: unknown) {
  return city === "San Francisco"
    ? [{ name: "Example Cafe", address: "123 Example Street, San Francisco" }]
    : [];
}

function formatIntro(locale: unknown, city: unknown) {
  const location = typeof city === "string" ? city : "your area";
  return locale === "es" ? `Cafés en ${location}` : `Cafes in ${location}`;
}

import { z } from "zod";

server.registerTool(
  "recommend_cafe",
  {
    title: "Recommend a cafe",
    inputSchema: {},
    outputSchema: {
      cafes: z.array(
        z.object({
          name: z.string(),
          address: z.string(),
        })
      ),
    },
  },
  async (_args, { _meta }) => {
    const locale = _meta?.["openai/locale"] ?? "en";
    const userLocation = _meta?.["openai/userLocation"];
    const location =
      typeof userLocation === "object" &&
      userLocation !== null &&
      "city" in userLocation
        ? userLocation.city
        : undefined;
    const cafes = await findNearbyCafes(location);

    return {
      content: [{ type: "text", text: formatIntro(locale, location) }],
      structuredContent: { cafes },
    };
  }
);