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
Theme
Visão geral Documentação para usuários do ChatGPT e do Codex Casos de uso

Adicione uma interface ao seu servidor MCP

Retorne recursos opcionais de interface em ferramentas MCP específicas.

Visão geral

A interface personalizada é opcional. Adicione uma quando as pessoas precisarem examinar, comparar, editar ou confirmar informações estruturadas ou navegar por elas ao usar seu plugin. Mantenha as ferramentas MCP úteis mesmo sem um componente para que o ChatGPT e o Codex possam concluir o fluxo de trabalho sem a interface.

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

Comece pelo MCP Apps

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

Ao criar uma interface:

  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 ao modelo.
  3. Mantenha as ferramentas úteis sem a interface para que o modelo possa concluir o fluxo de trabalho em clientes que não renderizam componentes.

Com essa base comum, a mesma interface funciona tanto no ChatGPT quanto em outros hosts compatíveis com MCP Apps.

Quando for implementar o padrão, consulte a especificação do MCP Apps.

Para aplicativos na barra lateral, visualizadores de arquivos e formulários mais elaborados, consulte Extensões.

Adicione extensões do ChatGPT

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

Dê preferência aos campos e métodos compartilhados

Use o campo ou método do MCP Apps sempre que a especificação compartilhada oferecer a funcionalidade desejada.

Objetivo Padrão MCP Apps Alias de compatibilidade do ChatGPT
Vincular uma ferramenta a um recurso de interface _meta.ui.resourceUri _meta["openai/outputTemplate"]
Receber a entrada da ferramenta ui/initialize + ui/notifications/tool-input window.openai.toolInput
Receber os resultados da ferramenta ui/notifications/tool-result window.openai.toolOutput
Chamar uma ferramenta pela interface tools/call window.openai.callTool
Enviar uma mensagem de acompanhamento ui/message window.openai.sendFollowUpMessage

Os aliases de compatibilidade continuam disponíveis para integrações existentes. Em novas interfaces, use os campos compartilhados e os métodos da ponte que aparecem na coluna do meio.

Alguns exemplos de extensões:

  • 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, quando viável, ofereça uma alternativa:

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

if (openai?.requestModal) {
  await openai.requestModal({
    template: "ui://widget/details.html",
  });
} else {
  document.body.textContent = "This host does not support modals.";
}

Evite condicionar o comportamento ao nome do host ou do produto. Verifique se a funcionalidade de que a interface precisa está disponível.

Para consultar as assinaturas das extensões e ver exemplos, acesse 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, com o mesmo estilo do contêiner do ChatGPT. Use a biblioteca para manter a aparência consistente sem precisar recriar os componentes básicos.

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

Escolha como apresentar a interface

Comece com uma interface integrada à conversa e solicite mais espaço apenas quando o fluxo de trabalho exigir. Escolha o formato mais compacto que permita às pessoas entender o resultado ou concluir a tarefa.

Cartão na conversa

Use um cartão na conversa para mostrar um resultado específico, pedir uma confirmação ou oferecer um pequeno conjunto de ações. O cartão deve funcionar por si só, sem exigir navegação por vários níveis.

Exemplos de cartões na conversa

Carrossel na conversa

Use um carrossel na conversa quando as pessoas precisarem percorrer um pequeno conjunto de opções semelhantes, com bastante conteúdo visual, e escolher uma delas.

Exemplo de carrossel na conversa

Tela cheia

Use a tela cheia para tarefas que precisam de mais espaço, como explorar mapas, trabalhar em áreas de edição ou navegar pelo conteúdo em detalhes. Projete a interface para funcionar com o campo de mensagem do ChatGPT, que continua disponível em tela cheia.

Exemplo de interface em tela cheia

Imagem em imagem

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

Exemplo de interface no modo 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ê associar um template de widget a cada chamada de ferramenta, o ChatGPT poderá renderizar o iframe mais vezes do que o necessário. Uma abordagem melhor é separar as ferramentas de processamento de dados das ferramentas de renderização:

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

Assim, o modelo pode analisar os dados que buscou antes de decidir se deve exibir a interface ao usuário. Com isso, aumenta bastante a probabilidade de o modelo atender ao objetivo específico que o usuário expressou.

Esse padrão faz parte da arquitetura do MCP Apps.

Na prática, muitas integrações de interface adotam a seguinte divisão:

  • Ferramentas de pesquisa e recuperação de dados (dados primeiro): retornam IDs e metadados, sem associar um template de widget.
  • Ferramentas de renderização (por exemplo, render_listings_widget): recebem uma lista de IDs já preparada e renderizam o widget.

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

Fluxo de chamadas desacopladas

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 já verificado pelo modelo.

Exemplo de perguntas de acompanhamento sobre imóveis

Suponha que seu plugin mostre cartões com anúncios de imóveis e um mapa. No servidor, a ferramenta search só aceita filtros gerais (cidade, preço, quartos e banheiros) e não permite filtrar pela área atendida por uma escola.

Se o usuário perguntar “Quais desses imóveis ficam na área atendida pela Richmond Primary School?”, o desacoplamento funciona assim:

  1. search faz uma busca ampla e retorna os IDs e os metadados dos anúncios encontrados.
  2. O modelo refina esse conjunto de anúncios com base na nova pergunta.
  3. O modelo chama render_listings_widget apenas com os IDs que passaram pelo filtro.
  4. O widget exibe o conjunto final de anúncios filtrados.

Boas práticas:

  • Mantenha as ferramentas de dados reutilizáveis. Retorne o structuredContent completo para permitir o encadeamento de chamadas.
  • Concentre as ferramentas de renderização na apresentação. Mantenha a lógica de negócios fora da função que trata a renderização.
  • Explicite a dependência na descrição da ferramenta de renderização (por exemplo, “Sempre chame roll_dice primeiro”).
  • Repita a execução apenas quando necessário. Permita que a interface chame as ferramentas de dados diretamente em interações locais como “Lançar novamente”, sem remontar o widget.

Exemplo de desacoplamento

Exemplo com ferramentas desacopladas para lançar um dado:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod/v3";
import { build } from "esbuild";
import { fileURLToPath } from "node:url";

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

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

// Bundle the same browser source that is type-checked and tested directly.
const widgetBundle = await build({
  entryPoints: [fileURLToPath(new URL("./dice-widget.ts", import.meta.url))],
  bundle: true,
  format: "esm",
  platform: "browser",
  write: false,
});
const widgetScript = widgetBundle.outputFiles[0].text.replace(
  /<\/script/gi,
  "<\\/script"
);
const widgetHtml = `<!doctype html><html lang="en"><meta charset="utf-8"><title>Dice widget</title><body><script type="module">${widgetScript}</script></body></html>`;

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;

Instale esbuild junto com as dependências do servidor. Em seguida, salve o código abaixo, que será executado no navegador, como dice-widget.ts no mesmo diretório do arquivo do servidor.

import { App } from "@modelcontextprotocol/ext-apps";

const container = document.createElement("div");
container.style.cssText = "font-family: system-ui; padding: 8px;";
const resultLine = document.createElement("div");
resultLine.style.cssText = "font-size: 20px; margin-bottom: 6px;";
const output = document.createElement("span");
output.id = "out";
output.textContent = "—";
resultLine.append("Result: ", output);
const rerollButton = document.createElement("button");
rerollButton.id = "reroll";
rerollButton.textContent = "Re-roll";
rerollButton.disabled = true;
container.append(resultLine, rerollButton);
document.body.append(container);

const app = new App({ name: "Dice widget", version: "1.0.0" });
let latestInput: Record<string, unknown> = {};
let latestOutput: Record<string, unknown> = {};

function render(result: Record<string, unknown>) {
  latestOutput = result;
  output.textContent = String(result.value ?? "—");
}

app.ontoolinput = ({ arguments: input }) => {
  latestInput = input ?? {};
};
app.ontoolresult = (result) => {
  render(result.structuredContent ?? {});
};

// Call the data tool without remounting the widget.
rerollButton.onclick = async () => {
  const candidate = latestOutput.sides ?? latestInput.sides;
  const sides =
    typeof candidate === "number" &&
    Number.isInteger(candidate) &&
    candidate >= 2
      ? candidate
      : 6;
  const next = await app.callServerTool({
    name: "roll_dice",
    arguments: { sides },
  });
  if (next.structuredContent) render(next.structuredContent);
};

await app.connect();
rerollButton.disabled = false;

Gerencie o estado

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

Tipo de estado Responsável Duração Exemplos
Dados de negócio (fonte de verdade) Servidor MCP ou serviço externo Longa duração Tarefas, chamados, documentos
Estado da interface (temporário) Instância da interface Enquanto a instância da interface estiver ativa Linha selecionada, painel expandido, ordem de classificação
Estado entre sessões (persistente) Armazenamento sob seu controle Persiste entre sessões e conversas Filtros salvos, modo de visualização, espaço de trabalho

Mantenha cada valor no sistema responsável por ele. A interface deve exibir os dados de referência retornados pelas ferramentas e aplicar a eles o estado temporário 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 o usuário executa uma ação, o fluxo é o seguinte:

  1. A interface chama uma ferramenta MCP.
  2. O servidor valida a solicitação e atualiza os dados.
  3. O servidor retorna um retrato atualizado do estado oficial dos dados.
  4. A interface exibe esses dados e preserva o estado de apresentação que continua compatível.

Retorne conteúdo estruturado suficiente para que tanto o modelo quanto a interface possam interpretar o novo estado. Assim, a conversa continua útil mesmo que a interface não carregue.

Mantenha o estado temporário na interface

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

Quando o modelo precisar saber de uma seleção ou de uma edição ainda não aplicada, envie essas informações por ui/update-model-context. Esse é o mecanismo portável do MCP Apps para atualizar o contexto visível ao modelo.

O ChatGPT também permite, opcionalmente, persistir o estado de cada widget:

  • Leia o estado atual em window.openai.widgetState.
  • Grave uma nova versão do estado com window.openai.setWidgetState(state).

setWidgetState é síncrono. Chame esse método após cada alteração relevante no estado da interface. Não é preciso usar await.

import { useState } from "react";

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

  function selectTask(selectedId: string) {
    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 os dados de negócio nem como armazenamento persistente.

Torne as imagens visíveis ao modelo

Se a interface trabalha com imagens, use o seguinte formato estruturado para o 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 dos arquivos que o modelo deve receber nos turnos seguintes da conversa.
const selectedFiles = await window.openai.selectFiles();

window.openai.setWidgetState({
  modelContent: "Review the currently selected images.",
  privateContent: {
    currentView: "image-viewer",
    filters: ["crop", "sharpen"],
  },
  imageIds: selectedFiles.map((file) => file.fileId),
});

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

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

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

Ao adicionar armazenamento persistente:

  • Mantenha a latência baixa o suficiente para que a interface responda às interações do usuário.
  • Proteja os dados privados com autorização no servidor.
  • Leve em conta os requisitos de residência de dados e conformidade.
  • Aplique limites de taxa às solicitações geradas por novas tentativas ou por instâncias simultâneas da interface.
  • Versione os objetos armazenados para poder migrá-los sem interromper as conversas existentes.

Evite usar localStorage para o estado essencial. 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 do projeto do componente

Agora que você conhece a ponte do MCP Apps e as extensões opcionais do ChatGPT, crie a estrutura inicial do projeto do componente.

Como boa prática, separe o código do componente 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. Recomendamos o Node 18 ou superior.

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

Se o componente precisar de bibliotecas para arrastar e soltar elementos, gerar gráficos ou oferecer outras funcionalidades, adicione-as agora. Inclua apenas as dependências necessárias para reduzir o tamanho do pacote final.

Escreva o componente React

O arquivo de entrada deve montar um componente em um elemento root. O componente deve renderizar a interface a partir do resultado mais recente da ferramenta recebido pela ponte do MCP Apps (por exemplo, via ui/notifications/tool-result).

Na página de exemplos, você encontra interfaces como a lista de pizzarias do Pizzaz.

Explore a galeria de componentes do Pizzaz

Os exemplos de interface incluem componentes que você pode usar como referência ao projetar sua interface:

  • Lista do Pizzaz: lista ordenada de cartões com favoritos e botões de chamada para ação.
    Captura de tela do componente de lista do Pizzaz
  • Carrossel do Pizzaz: componente com rolagem horizontal que usa o Embla para demonstrar layouts com muito conteúdo de mídia.
    Captura de tela do componente de carrossel do Pizzaz
  • Mapa do Pizzaz: integração com o Mapbox que inclui um inspetor em tela cheia e sincroniza o estado com o host.
    Captura de tela do componente de mapa do Pizzaz
  • Álbum do Pizzaz: galeria de imagens empilhadas para explorar um lugar em detalhes.
    Captura de tela do componente de álbum do Pizzaz
  • Vídeo do Pizzaz: reprodutor controlado por script, com sobreposições e controles de tela cheia.

Cada exemplo mostra como empacotar recursos, integrar as 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.

Hooks auxiliares do React

Este hook permite receber notificações de ui/notifications/tool-result:

import { useEffect, useState } from "react";

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 interface a partir de toolResult?.structuredContent e trate esse valor como uma entrada não confiável.

Localização do widget

O host disponibiliza a configuração regional em document.documentElement.lang. Use essa configuração para carregar as traduções e formatar datas e números. Execute npm install react-intl no diretório web/ antes de usar este exemplo.

import { IntlProvider, FormattedMessage } from "react-intl";

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

export function PluginUI() {
  const locale = document.documentElement.lang || "en-US";
  return (
    <IntlProvider
      locale={locale}
      messages={messages[locale] ?? messages["en-US"]}
    >
      <FormattedMessage id="greeting" defaultMessage="Hello" />
    </IntlProvider>
  );
}

Empacote o componente para o iframe

Depois de escrever o componente React, você pode empacotá-lo em um único módulo JavaScript que o servidor poderá incluir diretamente no HTML:

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

Execute npm run build para gerar dist/component.js. Se o esbuild indicar que faltam dependências, verifique se você executou npm install no diretório web/ e se os nomes nas importações correspondem aos dos pacotes instalados (por exemplo, confira a diferença entre @react-dnd/html5-server-side e react-dnd-html5-server-side).

Incorpore o componente à resposta do servidor

Disponibilize o componente como um recurso MCP com o tipo MIME de interface do MCP Apps (text/html;profile=mcp-app). Se você usar @modelcontextprotocol/ext-apps/server, prefira a constante RESOURCE_MIME_TYPE à string literal:

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 ampliar a compatibilidade com o MCP Apps, use _meta.ui.resourceUri. O ChatGPT também aceita _meta["openai/outputTemplate"] como alias de compatibilidade.

Trate o URI do recurso como uma chave de cache. Ao fazer uma alteração no HTML, no JavaScript ou no 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 a APIs.
  • resourceDomains para scripts, estilos, imagens e outros recursos.
  • frameDomains apenas quando o componente precisar incorporar iframes de origens específicas.

Frames aninhados são bloqueados por padrão. Restrinja cada lista de permissões ao mínimo necessário. Durante a revisão do plugin, a política declarada é comparada ao comportamento da interface.

Você pode incorporar um editor ou uma interface de administração existente que use o mesmo domínio registrável do servidor MCP. Por exemplo, um servidor em https://api.example.com/mcp pode declarar https://app.example.com em frameDomains. Apresente a justificativa exigida ao enviar o plugin para análise e siga a política de iframes, inclusive as restrições de hospedagem compartilhada e os requisitos de revisão.

Para produção, recomendamos o uso de templates de interface de componentes.

Durante o desenvolvimento, você pode gerar novamente o pacote do componente sempre que alterar o código React e recarregar o servidor sem interromper sua execução.

Permita finalizar compras pela interface

Se quiser permitir que os usuários finalizem compras pela interface do plugin, 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 úteis mesmo sem a interface. Em seguida, escolha um fluxo externo de finalização de compra ou, quando disponível, uma opção de pagamento integrada.

Use um fluxo externo de finalização de compra por padrão

A finalização de compra externa é a abordagem recomendada e está disponível de forma geral. Inclua no componente um link para o fluxo de finalização de compra que você, como lojista, hospeda no seu próprio domínio. Nesse fluxo, você cuida de:

  • Definição de preços e cobrança.
  • Impostos, descontos e taxas.
  • Envio e processamento dos pedidos.
  • Reembolsos, suporte e conformidade.

Atualmente, a aprovação se limita a plugins para a compra de produtos físicos. Só ofereça outras categorias de comércio se a OpenAI as tiver habilitado explicitamente para o seu plugin.

Use formas de pagamento salvas

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

Use o painel de pagamento do ChatGPT

A finalização de compra integrada com o painel de pagamento do ChatGPT está em beta privado para alguns marketplaces. A disponibilidade é restrita a determinados desenvolvedores e usuários.

Nas 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 de compra tem quatro etapas:

  1. Uma ferramenta MCP retorna uma sessão de finalização de 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 decide 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 escolhida e retorna o pedido concluído.

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

  • Um ID de sessão exclusivo.
  • Os itens do pedido e suas quantidades.
  • Os totais expressos em números inteiros na menor unidade da moeda.
  • Metadados do provedor de pagamentos e do lojista.
  • Os links obrigatórios para informações legais, privacidade, reembolsos e suporte.

Use o servidor como fonte de verdade para os preços e o status dos pedidos. Verifique o token de pagamento, torne a operação idempotente, armazene o pedido de forma persistente e retorne o 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 no componente os cancelamentos, as recusas de pagamento e os erros do provedor de pagamentos.

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, acesse a referência da API de finalização de compra.