window.openai 元件橋接介面
ChatGPT 透過 window.openai 提供相容性別名與選用的
ChatGPT 擴充功能。開發新的 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,並保留完整的 MCP 結果封裝,包含隱藏的 _meta。 |
| 狀態與資料 | 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(...) | 要求使用子母畫面/全螢幕模式。 |
| 小工具執行階段 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 元件訂閱
單一全域值:
export function useOpenAiGlobal<K extends keyof WebplusGlobals>(
key: K
): WebplusGlobals[K] {
return useSyncExternalStore(
(onChange) => {
const handleSetGlobal = (event: SetGlobalsEvent) => {
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);
};
},
() => window.openai[key]
);
}
關閉 UI
呼叫 window.openai.requestClose(),要求 ChatGPT 關閉目前的 UI。
要求使用其他呈現模式
使用 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/checkout.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"] 中列出每個頂層檔案輸入。列出的每個欄位都必須解析為檔案物件或
檔案物件陣列。
每個檔案物件的結構描述都必須宣告全部四個支援的屬性:
| 屬性 | 型別 | 在 properties 中宣告 | 納入 required |
|---|---|---|---|
download_url | string | 是 | 是 |
file_id | string | 是 | 是 |
mime_type | string | 是 | 否 |
file_name | string | 是 | 否 |
mime_type 和 file_name 的值可省略,但仍必須在結構描述中
宣告這兩個屬性。若檔案結構描述有下列任一情況, 掃描工具 步驟和外掛程式提交作業都會拒絕接受:
遺漏四個屬性中的任何一個、未將
download_url 和 file_id 設為必填、將任一選填屬性設為必填,或
要求填寫 download_url 和 file_id 以外的屬性。你可以
宣告額外的選填屬性。
以下完整的工具描述元接受一個必填的檔案輸入:
{
"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 設定路由:
export default function PizzaListRouter() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<PizzaListPlugin />}>
<Route path="place/:placeId" element={<PizzaListPlugin />} />
</Route>
</Routes>
</BrowserRouter>
);
}
透過程式碼導覽:
const navigate = useNavigate();
function openDetails(placeId: string) {
navigate(`place/${placeId}`, { replace: false });
}
function closeDetails() {
navigate("..", { replace: true });
}
工具描述元參數
預設情況下,工具說明應包含此處列出的欄位。
請為所有會傳回 structuredContent 的工具宣告 outputSchema。
結構描述應準確描述工具傳回的物件,讓用戶端能夠
驗證結果,並讓模型能針對後續工具呼叫進行推理。
工具描述元上的 _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 { 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(),
})
),
},
securitySchemes: [
{ type: "noauth" },
{ type: "oauth2", scopes: ["search.read"] },
],
_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 { 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 () => ({
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/widgetDescription"] | 資源內容 | string | 元件載入時提供給模型的人類可讀摘要,可減少助理的重複說明。 |
_meta["openai/widgetPrefersBorder"] | 資源內容 | boolean | ChatGPT 中 _meta.ui.prefersBorder 的 OpenAI 專用相容性別名。 |
_meta["openai/widgetCSP"] | 資源內容 | object | 用於小工具 CSP 中繼資料的舊版 ChatGPT 相容性鍵。標準 CSP 欄位已改由 _meta.ui.csp 取代,但設定受信任的 openExternal 目的地時,仍須使用 redirect_domains。 |
_meta["openai/widgetDomain"] | 資源內容 | string(來源) | ChatGPT 中 _meta.ui.domain 的 OpenAI 專用相容性別名。 |
ChatGPT 支援舊版相容性鍵 _meta["openai/widgetCSP"],並使用下列 snake_case 欄位名稱:
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 不支援用於 window.openai.openExternal(...) 連結的 redirect_domains。若要將重新導向目標加入允許清單,仍須設定 _meta["openai/widgetCSP"].redirect_domains。
工具結果
工具結果可包含以下欄位。其中需注意:
| 鍵 | 型別 | 是否必填 | 備註 |
|---|---|---|---|
structuredContent | object | 選填 | 提供給模型與元件。若已宣告 outputSchema,則必須符合該結構描述。 |
content | string 或 Content[] | 選填 | 提供給模型與元件。 |
_meta | object | 選填 | 僅傳遞給元件,模型無法看見。 |
只有 structuredContent 和 content 會出現在對話紀錄中。主機會將 _meta 轉送給元件,讓你能將資料填入 UI,而不向模型揭露這些資料。
主機提供的工具結果中繼資料:
| 鍵 | 放置位置 | 型別 | 用途 |
|---|---|---|---|
_meta["openai/widgetSessionId"] | 工具結果的 _meta(由主機提供) | string | 目前已掛載的小工具執行個體的固定 ID;在小工具卸載前,可用來關聯記錄與工具呼叫。 |
範例:
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[] | 用於觸發 OAuth 的 RFC 7235 WWW-Authenticate 驗證挑戰。 |
用戶端提供的 _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 | 匿名化的對話 ID,用於關聯同一個 ChatGPT 工作階段中的工具呼叫。 |
_meta["openai/organization"] | 工具呼叫 | string | 與目前 ChatGPT 組織相關聯的匿名化組織 ID(若有提供)。 |
操作階段的 _meta["openai/userAgent"] 和 _meta["openai/userLocation"] 僅供參考;伺服器絕不可依據這些資訊做出授權決策,且必須能在缺少這些資訊時正常運作。請將 _meta["openai/userAgent"] 視為選用、盡力提供的中繼資料,不要依賴它來穩定識別呼叫伺服器的主機介面。
範例:
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 location = _meta?.["openai/userLocation"]?.city;
const cafes = await findNearbyCafes(location);
return {
content: [{ type: "text", text: formatIntro(locale, location) }],
structuredContent: { cafes },
};
}
);