For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
主导航

参考资料

ChatGPT 专用 UI 扩展和元数据参考资料。

从开放标准入手。使用

MCP Apps 规范

中的共享 UI 字段和桥接方法。 OpenAI 扩展是可选的,位于 window.openai 中, 可在您需要 ChatGPT 专用能力时使用。

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 中,这包括 statuscall_tool_resultmcp_tool_result,保留完整的 MCP 结果封装,包括隐藏的 _meta
状态与数据window.openai.widgetState在多次渲染之间持久保存的 UI 状态快照。
状态与数据window.openai.setWidgetState(state)同步存储新快照;请在每次有意义的 UI 交互后调用。
小组件运行时 APIwindow.openai.callTool(name, args)从小组件调用另一个 MCP 工具(与模型发起的调用行为一致)。
小组件运行时 APIwindow.openai.sendFollowUpMessage({ prompt, scrollToBottom })请求 ChatGPT 发送由组件编写的消息。scrollToBottom 为可选参数,默认值为 true,可设为 false 以阻止自动滚动。
小组件运行时 APIwindow.openai.uploadFile(file, { library?: boolean })上传用户选择的文件并获得 fileId。传入 { library: true } 可在用户的 ChatGPT 文件库可用时,将上传的文件同时保存到该文件库中。
小组件运行时 APIwindow.openai.selectFiles()打开 ChatGPT 文件库选择器,以 { fileId, fileName, mimeType }[] 的形式返回已授权供插件使用的文件。请检测此辅助函数是否可用,因为文件库可能并非对所有用户都可用。
小组件运行时 APIwindow.openai.getFileDownloadUrl({ fileId })获取文件的临时下载 URL。文件可以由小组件上传、从文件库中选择、通过文件参数传入,或通过工具的文件引用返回。
小组件运行时 APIwindow.openai.requestDisplayMode(...)请求画中画或全屏模式。
小组件运行时 APIwindow.openai.requestModal({ params, template })创建由 ChatGPT 管理的模态窗口。省略 template 可使用当前模板,也可传入已注册的模板 URI 来切换模态窗口内容。
小组件运行时 APIwindow.openai.requestClose()请求 ChatGPT 关闭当前小组件。
小组件运行时 APIwindow.openai.notifyIntrinsicHeight(...)报告小组件动态变化的高度,避免滚动内容被裁切。
小组件运行时 APIwindow.openai.openExternal({ href, redirectUrl })在用户的浏览器中打开经过审核的外部链接。对于已批准的重定向目标,ChatGPT 默认会附加 ?redirectUrl=...;设置 redirectUrl: false 可跳过此操作。
小组件运行时 APIwindow.openai.setOpenInAppUrl({ href })可选择覆盖全屏模式下显示的外部目标。如果未设置,ChatGPT 会保持默认行为,打开组件当前的 iframe 路径。
上下文window.openai.themewindow.openai.displayModewindow.openai.maxHeightwindow.openai.safeAreawindow.openai.viewwindow.openai.userAgentwindow.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_urlstring
file_idstring
mime_typestring
file_namestring

mime_typefile_name 的值是可选的,但您必须在模式中 声明这两个属性。如果文件模式存在以下任一情况, 扫描工具 步骤和插件提交都会拒绝该模式: 遗漏四个属性中的任意一个;未将 download_urlfile_id 设为必填;将任一可选属性设为必填;或 将 download_urlfile_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_urlfile_id,但可能省略 mime_typefile_name。当小组件需要新的临时下载 URL 时, 请将 file_id 作为 fileId 的值, 用于 window.openai.getFileDownloadUrl({ fileId })

持久化小组件状态时,如果您希望模型在后续对话轮次中看到图像 ID,请使用结构化格式(modelContentprivateContentimageIds)。

主机支持的导航

沙盒运行时会将 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"]工具描述符数组为仅读取 _meta 的客户端提供用于向后兼容的镜像字段。
_meta.ui.resourceUri工具描述符字符串(URI)UI 模板的标准资源 URI。
_meta.ui.visibility工具描述符string[]默认为 ["model", "app"]控制工具可供模型、UI 或两者使用。app 值是 MCP Apps 协议中表示 UI 的标识符。
_meta["openai/outputTemplate"]工具描述符字符串(URI)ChatGPT 中 _meta.ui.resourceUri 的 OpenAI 专用可选兼容别名。
_meta["openai/profile"]工具描述符boolean可选;只有值为 true 时才表示这是账户资料工具标识用于返回当前账户资料且需要身份验证的只读工具。实现此工具可帮助用户识别和管理多个已连接的账户。即使未实现此工具,用户也可以连接多个账户。请参阅支持多个账户
_meta["openai/widgetAccessible"]工具描述符布尔值默认为 false现有 UI 集成使用的 OpenAI 专用兼容字段;优先使用 _meta.ui.visibility + tools/call
_meta["openai/visibility"]工具描述符stringpublic(默认)或 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 字段 来配置工具描述符:

类型是否必填说明
readOnlyHintboolean必填表明工具仅检索或计算信息,不会在对话之外创建、更新、删除或发送数据。
destructiveHintboolean必填声明工具可能删除或覆盖用户数据,以便主机先请求明确审批。
openWorldHintboolean必填声明工具会访问公共互联网或范围不受限的外部实体,包括通过网页搜索等只读操作进行访问。范围明确的私有账户或工作空间不会仅因托管在外部就被视为开放世界。
idempotentHintboolean可选声明使用相同参数调用工具不会对其环境产生额外影响。

这些提示仅影响 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 字段的首选元数据配置位置,包括 connectDomainsresourceDomains 和可选的 frameDomains
_meta.ui.domain资源内容string(源)托管组件的专用源(提交带 UI 的插件时必填,且每个插件必须使用唯一的源)。默认为 https://web-sandbox.oaiusercontent.com
_meta["openai/widgetDescription"]资源内容string组件加载时提供给模型的易读摘要,可减少助手的重复说明。
_meta["openai/widgetPrefersBorder"]资源内容booleanChatGPT 中 _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

工具结果

工具结果可以包含以下字段。重点如下:

类型是否必需说明
structuredContentobject可选提供给模型和组件。如果已声明 outputSchema,则必须与其匹配。
contentstring 或 Content[]可选提供给模型和组件。
_metaobject可选仅传递给组件,对模型不可见。

只有 structuredContentcontent 会出现在对话记录中。主机会将 _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大致位置提示信息(cityregioncountrytimezonelongitudelatitude)。
_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 },
    };
  }
);