For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navegação principal

Adicione uma interface ao seu servidor MCP

Retorne recursos opcionais de interface a partir de ferramentas MCP selecionadas.

Visão geral

Uma interface personalizada é opcional. Adicione uma quando um caso de uso do plug-in exigir que as pessoas inspecionem, comparem, editem, confirmem ou naveguem por informações estruturadas. Mantenha as ferramentas MCP úteis sem um componente para que o ChatGPT e o Codex possam concluir o fluxo de trabalho sem interface.

O servidor MCP retorna recursos de interface para ferramentas selecionadas. Os componentes são executados dentro de um iframe no ChatGPT, comunicam-se com o host pela ponte MCP Apps (JSON-RPC via postMessage) e são renderizados junto à conversa. O padrão aberto MCP Apps permite que a interface seja executada em diferentes hosts compatíveis.

Comece com MCP Apps

O ChatGPT implementa o padrão MCP Apps , que é aberto, para interfaces retornadas por um servidor MCP. MCP Apps define como seu servidor associa ferramentas a recursos de interface e como o iframe se comunica com seu host.

Para novas interfaces:

  1. Declare o recurso de interface com _meta.ui.resourceUri.
  2. Use a ponte JSON-RPC ui/* via postMessage para inicialização, notificações, chamadas de ferramentas, mensagens e contexto visível para o modelo.
  3. Mantenha as ferramentas úteis sem interface para que o modelo possa concluir o fluxo de trabalho em clientes que não renderizam componentes.

Essa base que prioriza padrões permite que a mesma interface seja executada no ChatGPT e em outros hosts compatíveis com MCP Apps.

Quando estiver pronto para implementar o padrão, use a especificação MCP Apps.

Adicione extensões do ChatGPT

Depois que o fluxo MCP Apps estiver funcionando, use window.openai apenas para capacidades que a especificação compartilhada não abrange. Essas extensões opcionais podem melhorar a experiência no ChatGPT sem que precisem fazer parte da base portável da interface.

Prefira campos e métodos compartilhados

Use o campo ou método do MCP Apps sempre que a especificação compartilhada abranger a capacidade:

ObjetivoPadrão MCP AppsAlias de compatibilidade do ChatGPT
Vincular uma ferramenta a um recurso de interface_meta.ui.resourceUri_meta["openai/outputTemplate"]
Receber dados de entrada da ferramentaui/initialize + ui/notifications/tool-inputwindow.openai.toolInput
Receber resultados da ferramentaui/notifications/tool-resultwindow.openai.toolOutput
Chamar uma ferramenta pela interfacetools/callwindow.openai.callTool
Enviar uma mensagem de continuaçãoui/messagewindow.openai.sendFollowUpMessage

Os aliases de compatibilidade continuam disponíveis para integrações existentes. Novas interfaces devem usar os campos compartilhados e os métodos da ponte apresentados na coluna central.

Alguns exemplos:

  • Checkout Instantâneo com window.openai.requestCheckout.
  • Manipulação de arquivos no ChatGPT com window.openai.uploadFile, window.openai.selectFiles e window.openai.getFileDownloadUrl.
  • Janelas modais controladas pelo host com window.openai.requestModal.
  • Persistência do estado do widget com window.openai.widgetState e window.openai.setWidgetState.

Verifique se cada extensão está disponível e ofereça uma alternativa quando viável:

const openai = typeof window !== "undefined" ? window.openai : undefined;

if (openai?.requestModal) {
  await openai.requestModal({
    /* ... */
  });
} else {
  // Fallback behavior for hosts without this extension.
}

Evite criar caminhos condicionais com base no nome do host ou do produto. Verifique se a capacidade de que sua interface precisa está disponível.

Para consultar assinaturas e exemplos de extensões, veja a referência da ponte de componentes window.openai.

Biblioteca opcional de componentes da OpenAI

A biblioteca de componentes @openai/apps-sdk-ui oferece botões, cartões, controles de entrada e elementos básicos de layout prontos para uso que combinam com o contêiner do ChatGPT. Use-a quando quiser manter um estilo consistente sem recriar componentes básicos.

Você também pode explorar o repositório de exemplos de interface no GitHub.

Escolha uma forma de apresentação

Comece com uma interface incorporada à conversa e solicite mais espaço apenas quando o fluxo de trabalho precisar. Escolha a forma de apresentação mais compacta que permita às pessoas entender o resultado ou concluir a tarefa.

Cartão incorporado à conversa

Use um cartão incorporado à conversa para um resultado específico, uma confirmação ou um pequeno conjunto de ações. Mantenha-o autossuficiente e evite muitos níveis de navegação.

Exemplos de cartões incorporados à conversa

Use um carrossel incorporado à conversa quando as pessoas precisarem examinar rapidamente e escolher entre poucas opções semelhantes e visualmente ricas.

Exemplo de carrossel incorporado à conversa

tela cheia

Use tela cheia para tarefas que precisam de mais espaço visual, como mapas, áreas de edição ou navegação detalhada. Projete a experiência para funcionar com o editor do ChatGPT, que continua disponível em tela cheia.

Exemplo de interface em tela cheia

Imagem em imagem

Use imagem em imagem para uma atividade em andamento que deva permanecer visível enquanto a conversa continua, como uma sessão ao vivo, um jogo ou um vídeo.

Exemplo de interface em imagem em imagem

Para orientações detalhadas sobre layout, interação, design visual e acessibilidade, consulte as diretrizes de interface.

Separe o processamento de dados da renderização da interface

Padrão desacoplado

Se você anexar um template de widget a cada chamada de ferramenta, o ChatGPT poderá renderizar novamente seu iframe com frequência excessiva. Um padrão melhor é separar as ferramentas de processamento de dados das ferramentas de renderização:

  • Ferramentas de dados buscam, processam ou alteram dados e retornam apenas resultados de ferramentas.
  • Ferramentas de renderização recebem os dados finais e retornam o template do widget.

Isso permite que o modelo aplique sua inteligência aos dados que buscou antes de decidir renderizar uma interface para o usuário, aumentando muito a probabilidade de alcançar o objetivo específico que o usuário expressou.

Esse padrão faz parte da arquitetura MCP Apps.

Na prática, muitas integrações de interface usam esta divisão:

  • Ferramentas de pesquisa/busca de dados (dados primeiro): Retornam IDs e metadados sem um template de widget anexado.
  • Ferramentas de renderização (por exemplo, render_listings_widget): Recebem uma lista preparada de IDs e renderizam o widget.

Somente a ferramenta de renderização deve incluir _meta.ui.resourceUri.

Fluxo de chamadas desacoplado

Fluxo de chamadas recomendado:

  1. O modelo chama a ferramenta de dados (por exemplo, roll_dice).
  2. O modelo recebe structuredContent da ferramenta de dados.
  3. O modelo chama a ferramenta de renderização com esses dados.
  4. O widget é renderizado uma única vez com o contexto final, verificado pelo modelo.

Exemplo: consultas de acompanhamento sobre imóveis

Suponha que seu plug-in mostre cartões de anúncios de imóveis e um mapa, mas a ferramenta search no servidor só aceite filtros amplos (cidade, preço, quartos, banheiros) e não consiga filtrar pela área de atendimento de uma escola.

Se um usuário perguntar “Quais destes imóveis estão na área de atendimento da Richmond Primary School?”, o desacoplamento ajuda:

  1. search faz uma busca ampla e retorna os IDs dos anúncios candidatos e seus metadados.
  2. O modelo refina esse conjunto de candidatos para responder à pergunta de acompanhamento.
  3. O modelo chama render_listings_widget apenas com os IDs filtrados.
  4. O widget renderiza o conjunto final filtrado.

Práticas recomendadas:

  • Mantenha as ferramentas de dados reutilizáveis. Retorne structuredContent completo para permitir o encadeamento.
  • Mantenha as ferramentas de renderização focadas na apresentação. Não misture lógica de negócio no manipulador de renderização.
  • Indique a dependência na descrição da ferramenta de renderização (por exemplo, “Sempre chame roll_dice primeiro”).
  • Execute novamente apenas de forma intencional. Permita que a interface chame ferramentas de dados diretamente para interações locais, como “Rolar novamente”, sem remontar o widget.

Exemplo desacoplado

Exemplo (ferramentas desacopladas para rolagem de dados):

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod/v3";

const TEMPLATE_URI = "ui://widget/dice.html";

const server = new McpServer(
  { name: "Decoupled dice", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// The widget only renders the latest tool result.
// Re-roll calls the data tool directly to avoid remounting the widget.
const widgetHtml = `
  <div style="font-family: system-ui; padding: 8px;">
    <div style="font-size: 20px; margin-bottom: 6px;">
      Result: <span id="out">—</span>
    </div>
    <button id="reroll">Re-roll</button>
  </div>

  <script>
    const outputEl = document.getElementById("out");
    const rerollButton = document.getElementById("reroll");
    const pendingRequests = new Map();
    let nextRequestId = 1;
    let latestToolInput;
    let latestToolOutput;

    function render(result) {
      outputEl.textContent = String(result?.value ?? "—");
    }

    function request(method, params) {
      const id = nextRequestId++;
      window.parent.postMessage({ jsonrpc: "2.0", id, method, params }, "*");
      return new Promise((resolve, reject) => {
        pendingRequests.set(id, { resolve, reject });
      });
    }

    window.addEventListener(
      "message",
      (event) => {
        if (event.source !== window.parent) return;
        const message = event.data;
        if (!message || message.jsonrpc !== "2.0") return;

        if (message.id !== undefined && pendingRequests.has(message.id)) {
          const pending = pendingRequests.get(message.id);
          pendingRequests.delete(message.id);
          if (message.error) pending.reject(message.error);
          else pending.resolve(message.result);
          return;
        }

        if (message.method === "ui/notifications/tool-input") {
          latestToolInput = message.params;
        }

        if (message.method === "ui/notifications/tool-result") {
          latestToolOutput = message.params?.structuredContent;
          render(latestToolOutput);
        }
      },
      { passive: true }
    );

    rerollButton.onclick = async () => {
      const sides = latestToolOutput?.sides ?? latestToolInput?.sides ?? 6;
      const next = await request("tools/call", {
        name: "roll_dice",
        arguments: { sides },
      });
      if (next?.structuredContent) {
        render(next.structuredContent);
      }
    };
  </script>
`.trim();

server.registerResource("dice-widget", TEMPLATE_URI, {}, async () => ({
  contents: [
    {
      uri: TEMPLATE_URI,
      mimeType: "text/html;profile=mcp-app",
      text: widgetHtml,
      _meta: { ui: { prefersBorder: true } },
    },
  ],
}));

// 1) Data tool: no output template, returns chainable structuredContent.
server.registerTool(
  "roll_dice",
  {
    title: "Roll dice",
    description: "Roll an N-sided die and return { sides, value }.",
    inputSchema: { sides: z.number().int().min(2) },
    outputSchema: {
      sides: z.number().int().min(2),
      value: z.number().int().min(1),
    },
    _meta: {
      "openai/toolInvocation/invoking": "Rolling…",
      "openai/toolInvocation/invoked": "Rolled.",
    },
  },
  async ({ sides }) => {
    const value = 1 + Math.floor(Math.random() * sides);
    return {
      structuredContent: { sides, value },
      content: [{ type: "text", text: `Rolled ${value} on ${sides} sides.` }],
    };
  }
);

// 2) Render tool: owns the template and requires data from roll_dice.
server.registerTool(
  "render_dice_widget",
  {
    title: "Render dice widget",
    description:
      "Render the dice widget from roll data. First call roll_dice, then pass its sides and value to this tool.",
    inputSchema: {
      sides: z.number().int().min(2),
      value: z.number().int().min(1),
    },
    outputSchema: {
      sides: z.number().int().min(2),
      value: z.number().int().min(1),
    },
    _meta: {
      ui: { resourceUri: TEMPLATE_URI },
      "openai/toolInvocation/invoking": "Rendering…",
      "openai/toolInvocation/invoked": "Rendered.",
    },
  },
  async ({ sides, value }) => ({
    structuredContent: { sides, value },
    content: [
      {
        type: "text",
        text: `Showing a ${sides}-sided roll: ${value}.`,
      },
    ],
  })
);

export default server;

Gerencie o estado

A interface de um servidor MCP trabalha com três tipos de estado:

Tipo de estadoResponsávelDuraçãoExemplos
Dados de negócio (fonte de verdade)Servidor MCP ou serviço externoLonga duraçãoTarefas, tickets, documentos
Estado da interface (efêmero)Instância da interfaceEnquanto a instância da interface estiver ativaLinha selecionada, painel expandido, ordem de classificação
Estado entre sessões (durável)Armazenamento sob seu controleEntre sessões e conversasFiltros salvos, modo de visualização, workspace

Mantenha cada valor no sistema responsável por ele. A interface deve renderizar os dados de referência dos resultados das ferramentas e aplicar sobre eles uma camada temporária de estado de apresentação.

MCP server or external service

├── Authoritative business data


UI

├── Ephemeral presentation state

└── Rendered view = business data + UI state

Mantenha os dados de negócio no servidor

Os dados de negócio são a fonte de verdade. Não os armazene apenas na interface. Quando um usuário realiza uma ação:

  1. A interface chama uma ferramenta MCP.
  2. O servidor valida a solicitação e atualiza os dados.
  3. O servidor retorna uma cópia atualizada do estado de referência.
  4. A interface renderiza essa cópia do estado, preservando o estado de apresentação compatível.

Retorne conteúdo estruturado suficiente para que tanto o modelo quanto a interface entendam o novo estado. Isso também permite que a conversa continue sendo útil caso a interface não consiga carregar.

Mantenha o estado temporário da interface na própria interface

Use o estado do framework para valores que afetam apenas a apresentação, como um item selecionado, um painel aberto ou um filtro em elaboração. Cada instância renderizada da interface tem seu próprio estado.

Quando o modelo precisar saber sobre uma seleção ou uma edição preparada, envie essa informação por meio de ui/update-model-context. Esse é o mecanismo portável do MCP Apps para atualizar o contexto visível ao modelo.

O ChatGPT também oferece persistência opcional no escopo do widget:

  • Leia a cópia atual do estado em window.openai.widgetState.
  • Grave uma nova cópia do estado com window.openai.setWidgetState(state).

setWidgetState é síncrono. Chame-o após cada mudança significativa no estado da interface; não há nada a aguardar com await.

import { useState } from "react";

export function TaskList({ tasks }) {
  const [state, setState] = useState(
    window.openai?.widgetState ?? { selectedId: null }
  );

  function selectTask(selectedId) {
    const nextState = { ...state, selectedId };
    setState(nextState);
    window.openai?.setWidgetState?.(nextState);
  }

  return (
    <ul>
      {tasks.map((task) => (
        <li key={task.id}>
          <button
            type="button"
            aria-pressed={state.selectedId === task.id}
            onClick={() => selectTask(task.id)}
          >
            {task.title}
          </button>
        </li>
      ))}
    </ul>
  );
}

O estado do widget pertence a uma única instância renderizada da interface. Não o use como fonte de verdade para dados de negócio nem como armazenamento durável.

Torne as imagens visíveis ao modelo

Para interfaces que trabalham com imagens, use o formato estruturado de estado do widget:

  • modelContent: texto ou JSON que o modelo deve ver.
  • privateContent: estado exclusivo da interface que o modelo não deve ver.
  • imageIds: IDs de arquivos que o modelo deve receber nos próximos turnos.
window.openai.setWidgetState({
  modelContent: "Review the currently selected images.",
  privateContent: {
    currentView: "image-viewer",
    filters: ["crop", "sharpen"],
  },
  imageIds: ["file_123", "file_456"],
});

Inclua apenas IDs de arquivos enviados com window.openai.uploadFile, selecionados com window.openai.selectFiles, recebidos por parâmetros de arquivo na entrada da ferramenta ou retornados por referências a arquivos no resultado da ferramenta.

Armazene no servidor o estado que deve persistir entre sessões

Armazene as preferências e os dados que devem persistir entre conversas, dispositivos ou sessões em um armazenamento sob seu controle. Autentique o usuário para que o servidor MCP possa associar cada solicitação à conta correta.

Ao adicionar armazenamento durável:

  • Mantenha a latência baixa o suficiente para uma interface interativa.
  • Proteja dados privados com autorização no servidor.
  • Planeje como atender aos requisitos de residência de dados e conformidade.
  • Aplique limites de taxa ao tráfego de novas tentativas ou de instâncias simultâneas da interface.
  • Versione os objetos armazenados para poder migrá-los sem prejudicar as conversas existentes.

Evite localStorage para o estado principal. A interface é executada em um iframe isolado, e o armazenamento do navegador não oferece uma camada de dados confiável entre dispositivos ou sessões.

Crie a estrutura inicial do projeto do componente

Agora que você entende a ponte do MCP Apps (e as extensões opcionais do ChatGPT), é hora de criar a estrutura inicial do projeto do seu componente.

Uma boa prática é manter o código do componente separado da lógica do servidor. Uma estrutura comum é:

plugin-ui/
  server/            # MCP server (Python or Node)
  web/               # Component bundle source
    package.json
    tsconfig.json
    src/component.tsx
    dist/component.js   # Build output

Crie o projeto e instale as dependências (recomenda-se Node 18+):

cd plugin-ui/web
npm init -y
npm install react@^18 react-dom@^18
npm install -D typescript esbuild

Se o componente precisar de bibliotecas para arrastar e soltar, gráficos ou outras funcionalidades, adicione-as agora. Mantenha poucas dependências para reduzir o tamanho do pacote.

Escreva o componente React

O arquivo de entrada deve montar um componente em um elemento root e renderizá-lo com base no resultado mais recente da ferramenta recebido pela ponte do MCP Apps (por exemplo, ui/notifications/tool-result).

A página de exemplos inclui exemplos de interface, como a lista de pizzarias do Pizzaz.

Os exemplos de interface incluem componentes de exemplo. Use-os como referência ao criar sua própria interface:

  • Pizzaz List: Lista de cartões ordenados por classificação, com favoritos e botões de chamada para ação.
    Captura de tela do componente de lista do Pizzaz
  • Pizzaz Carousel: Componente de rolagem horizontal baseado no Embla que demonstra layouts com bastante conteúdo de mídia.
    Captura de tela do componente de carrossel do Pizzaz
  • Pizzaz Map: Integração com o Mapbox, com inspetor em tela cheia e sincronização de estado com o host.
    Captura de tela do componente de mapa do Pizzaz
  • Pizzaz Album: Visualização de galeria em pilha, criada para explorar um único lugar em detalhes.
    Captura de tela do componente de álbum do Pizzaz
  • Pizzaz Video: Reprodutor controlado por scripts, com sobreposições e controles de tela cheia.

Cada exemplo mostra como empacotar recursos, conectar APIs do host e estruturar o estado para conversas reais. Copie o exemplo mais próximo do seu caso de uso e adapte a camada de dados às respostas das suas ferramentas.

Ganchos auxiliares do React

Uma pequena função auxiliar para assinar ui/notifications/tool-result:

type ToolResult = { structuredContent?: unknown } | null;

export function useToolResult() {
  const [toolResult, setToolResult] = useState<ToolResult>(null);

  useEffect(() => {
    const onMessage = (event: MessageEvent) => {
      if (event.source !== window.parent) return;
      const message = event.data;
      if (!message || message.jsonrpc !== "2.0") return;
      if (message.method !== "ui/notifications/tool-result") return;
      setToolResult(message.params ?? null);
    };

    window.addEventListener("message", onMessage, { passive: true });
    return () => window.removeEventListener("message", onMessage);
  }, []);

  return toolResult;
}

Renderize a partir de toolResult?.structuredContent e trate esse conteúdo como uma entrada não confiável.

Localização do widget

O host replica a localidade em document.documentElement.lang. Use essa localidade para carregar traduções e formatar datas e números. Um padrão comum com react-intl:

import { IntlProvider } from "react-intl";
import en from "./locales/en-US.json";
import es from "./locales/es-ES.json";

const messages: Record<string, Record<string, string>> = {
  "en-US": en,
  "es-ES": es,
};

export function PluginUI() {
  const locale = document.documentElement.lang || "en-US";
  return (
    <IntlProvider
      locale={locale}
      messages={messages[locale] ?? messages["en-US"]}
    >
      {/* Render UI with <FormattedMessage> or useIntl() */}
    </IntlProvider>
  );
}

Empacote para o iframe

Ao terminar de escrever o componente React, você pode compilá-lo em um único módulo JavaScript que o servidor pode incorporar diretamente:

// package.json
{
  "scripts": {
    "build": "esbuild src/component.tsx --bundle --format=esm --outfile=dist/component.js"
  }
}

Execute npm run build para gerar dist/component.js. Se o esbuild indicar dependências ausentes, confirme que você executou npm install no diretório web/ e que as importações correspondem aos nomes dos pacotes instalados (por exemplo, @react-dnd/html5-server-side versus react-dnd-html5-server-side).

Incorpore o componente à resposta do servidor

Exponha o componente como um recurso MCP com o tipo MIME de interface do MCP Apps (text/html;profile=mcp-app). Se você usa @modelcontextprotocol/ext-apps/server, prefira RESOURCE_MIME_TYPE em vez de inserir a string diretamente:

import {
  registerAppResource,
  RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import { readFileSync } from "node:fs";

const component = readFileSync("web/dist/component.js", "utf8");

registerAppResource(
  server,
  "project-board",
  "ui://project-board/v1.html",
  {},
  async () => ({
    contents: [
      {
        uri: "ui://project-board/v1.html",
        mimeType: RESOURCE_MIME_TYPE,
        text: `<div id="root"></div><script type="module">${component}</script>`,
        _meta: {
          ui: {
            prefersBorder: true,
            domain: "https://example.com",
            csp: {
              connectDomains: ["https://api.example.com"],
              resourceDomains: ["https://static.example.com"],
            },
          },
        },
      },
    ],
  })
);

Associe o URI do recurso apenas às ferramentas que devem renderizar o componente. Para maior compatibilidade com MCP Apps, use _meta.ui.resourceUri. O ChatGPT também aceita _meta["openai/outputTemplate"] como um alias de compatibilidade.

Trate o URI do recurso como uma chave de cache. Ao fazer uma alteração no HTML, JavaScript ou CSS que quebre a compatibilidade, publique um novo URI e atualize todas as ferramentas que fazem referência a ele.

Política de segurança de conteúdo (CSP)

Declare os domínios exatos aos quais o componente se conecta ou dos quais carrega recursos:

  • connectDomains para solicitações de API.
  • resourceDomains para scripts, estilos, imagens e outros recursos.
  • frameDomains somente quando o componente precisar incorporar iframes de origens específicas.

Frames aninhados são bloqueados por padrão. Mantenha cada lista de permissões o mais restrita possível. O processo de revisão de plug-ins verifica se o comportamento da interface está de acordo com a política declarada.

Você pode incorporar um editor ou uma interface administrativa existente hospedados no próprio domínio registrável do seu servidor MCP. Por exemplo, um servidor em https://api.example.com/mcp pode declarar https://app.example.com em frameDomains. Forneça a justificativa exigida no envio e siga a política de iframes, incluindo as restrições de hospedagem compartilhada e os requisitos de revisão dessa política.

Templates de interface de componentes são a abordagem recomendada para produção.

Durante o desenvolvimento, você pode recompilar o pacote do componente sempre que o código React mudar e recarregar o servidor a quente.

Ofereça a finalização da compra na sua interface

Se você quiser permitir que os usuários finalizem compras pelos fluxos de interface do seu plug-in, use o componente para apresentar produtos, preços, termos e opções de pagamento antes da confirmação. Mantenha as ferramentas de catálogo e pedidos subjacentes úteis mesmo sem a interface e escolha um fluxo externo de finalização da compra ou, quando disponível, uma opção de pagamento incorporada.

Use a finalização da compra externa por padrão

A finalização da compra externa é a abordagem recomendada e está disponível de forma geral. Inclua no componente um link para um fluxo de finalização da compra hospedado pelo lojista no seu próprio domínio, onde você gerencia:

  • Preços e cobrança de pagamentos.
  • Impostos, descontos e taxas.
  • Envio e atendimento de pedidos.
  • Reembolsos, suporte e conformidade.

A aprovação atual se limita a plug-ins para compras de produtos físicos. Não ofereça outras categorias de comércio, a menos que a OpenAI as tenha habilitado explicitamente para o seu plug-in.

Use formas de pagamento salvas

Para compras elegíveis de produtos físicos, uma interface opcional pode permitir que os clientes selecionem uma forma de pagamento que já salvaram no seu serviço. Esse fluxo pode exibir formas de pagamento salvas elegíveis, mas não pode coletar novas credenciais de pagamento. Seu servidor MCP processa a compra e retorna o resultado oficial do pedido.

Use o painel de pagamento do ChatGPT

A finalização da compra incorporada com o painel de pagamento do ChatGPT está em beta privado para marketplaces selecionados e não está disponível para todos os desenvolvedores ou usuários.

Para integrações habilitadas, window.openai.requestCheckout abre o painel de pagamento do ChatGPT:

const order = await window.openai.requestCheckout(checkoutSession);

O fluxo de finalização da compra tem quatro partes:

  1. Uma ferramenta MCP retorna uma sessão de finalização da compra em structuredContent.
  2. O componente exibe os itens do pedido, os totais, os termos e as opções de atendimento do pedido.
  3. O componente chama requestCheckout(checkoutSession) depois que o usuário opta por pagar.
  4. O ChatGPT envia o token de pagamento selecionado para a ferramenta complete_checkout do servidor MCP, que efetua a cobrança na forma de pagamento e retorna o pedido concluído.

A sessão de finalização da compra deve incluir:

  • Um ID de sessão exclusivo.
  • Itens do pedido e quantidades.
  • Totais expressos como números inteiros na menor unidade da moeda.
  • Metadados do provedor de pagamentos e do comerciante.
  • Links obrigatórios para informações legais, privacidade, reembolsos e suporte.

Trate o servidor como a fonte de verdade para preços e status dos pedidos. Verifique o token de pagamento, torne a operação idempotente, persista o pedido e retorne um comprovante oficial. Nunca confie em totais calculados apenas no componente.

Use payment_mode: "test" para testar o fluxo de ponta a ponta sem movimentar dinheiro real. Trate cancelamentos, pagamentos recusados e erros do provedor de pagamentos no componente.

Para consultar todos os campos da sessão de finalização de compra, o comportamento do provedor de pagamentos, a estrutura do resultado de complete_checkout e os requisitos de pagamento delegado, veja a referência da API de finalização de compra.