For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navegación principal
Theme
Descripción general Documentación para usuarios de ChatGPT y Codex Casos de uso

Referencia

Referencia de extensiones de interfaz y metadatos específicos de ChatGPT.

Empieza con el estándar abierto. Usa la

especificación de MCP Apps

para los campos de interfaz y los métodos del puente que son comunes a distintos hosts. Las extensiones de OpenAI son opcionales y están disponibles en window.openai cuando necesitas funciones específicas de ChatGPT.

Puente window.openai para componentes

ChatGPT ofrece window.openai para los alias de compatibilidad y las extensiones opcionales de ChatGPT. Al crear una interfaz, usa el puente de MCP Apps siempre que la especificación compartida ofrezca una función equivalente. Usa window.openai solo para funciones específicas de ChatGPT.

Consulta cómo crear una interfaz para ChatGPT para ver guías de implementación paso a paso.

Si tu herramienta requiere confirmación, es normal que al principio falte toolInput. ChatGPT no carga en el widget los argumentos que requieren aprobación antes de que el usuario apruebe la llamada. Una vez que el usuario la aprueba, el host envía esos argumentos al widget mediante ui/notifications/tool-input.

Funciones

Función Qué hace Uso habitual
Estado y datos window.openai.toolInput Argumentos que se pasaron al invocar la herramienta. Si la herramienta requiere aprobación, este valor puede seguir siendo null hasta que el host envíe ui/notifications/tool-input después de la aprobación.
Estado y datos window.openai.toolOutput El valor de structuredContent que devuelve tu herramienta. Mantén los campos concisos, ya que el modelo los lee tal como están.
Estado y datos window.openai.toolResponseMetadata Metadatos canónicos del resultado de la herramienta a los que solo tiene acceso el widget. En ChatGPT incluyen status, call_tool_result y mcp_tool_result, y conservan la estructura completa del resultado de MCP, incluido el campo oculto _meta.
Estado y datos window.openai.widgetState Instantánea del estado de la interfaz que se conserva entre renderizados.
Estado y datos window.openai.setWidgetState(state) Guarda una nueva instantánea de forma síncrona. Llama a este método después de cada interacción significativa con la interfaz.
API del entorno de ejecución del widget window.openai.callTool(name, args) Invoca otra herramienta MCP desde el widget de la misma forma que en las llamadas que inicia el modelo.
API del entorno de ejecución del widget window.openai.sendFollowUpMessage({ prompt, scrollToBottom }) Pide a ChatGPT que publique un mensaje redactado por el componente. scrollToBottom es opcional y su valor predeterminado es true. Puedes establecerlo en false para evitar el desplazamiento automático.
API del entorno de ejecución del widget window.openai.uploadFile(file, { library?: boolean }) Sube un archivo seleccionado por el usuario y recibe un fileId. Pasa { library: true } para guardar también el archivo en la biblioteca de archivos de ChatGPT del usuario cuando esté disponible.
API del entorno de ejecución del widget window.openai.selectFiles() Abre el selector de la biblioteca de archivos de ChatGPT y devuelve los archivos autorizados para el complemento como { fileId, fileName, mimeType }[]. Comprueba que esta función esté disponible antes de usarla, ya que algunos usuarios podrían no tener acceso a la biblioteca.
API del entorno de ejecución del widget window.openai.getFileDownloadUrl({ fileId }) Obtén una URL de descarga temporal para un archivo que el widget haya subido, que se haya seleccionado en la biblioteca o que se haya pasado mediante parámetros de archivo. También admite archivos que una herramienta haya devuelto por referencia.
API del entorno de ejecución del widget window.openai.requestDisplayMode(...) Solicita el modo de imagen en imagen (PiP) o de pantalla completa.
API del entorno de ejecución del widget window.openai.requestModal({ params, template }) Abre una ventana modal controlada por ChatGPT. Omite template para usar la plantilla actual o pasa el URI de una plantilla registrada para cambiar el contenido de la ventana.
API del entorno de ejecución del widget window.openai.requestClose() Pide a ChatGPT que cierre el widget actual.
API del entorno de ejecución del widget window.openai.notifyIntrinsicHeight(...) Informa de los cambios de altura del widget para evitar que el contenido se corte al desplazarse.
API del entorno de ejecución del widget window.openai.openExternal({ href, redirectUrl }) Abre un enlace externo verificado en el navegador del usuario. Para los destinos de redirección aprobados, ChatGPT agrega ?redirectUrl=... de forma predeterminada. Establece redirectUrl: false para omitirlo.
API del entorno de ejecución del widget window.openai.setOpenInAppUrl({ href }) Permite cambiar el destino externo que se muestra en pantalla completa. Si no lo configuras, ChatGPT mantiene el comportamiento predeterminado y abre la ruta actual del iframe del componente.
Contexto window.openai.theme, window.openai.displayMode, window.openai.maxHeight, window.openai.safeArea, window.openai.view, window.openai.userAgent, window.openai.locale Señales del entorno que puedes consultar o a las que puedes suscribirte mediante useOpenAiGlobal para adaptar la apariencia y los textos de la interfaz.

Función auxiliar useOpenAiGlobal

Muchos proyectos de interfaz para ChatGPT encapsulan el acceso a window.openai en pequeñas funciones auxiliares para que las vistas se puedan probar. La función de este ejemplo escucha los eventos openai:set_globals del host y permite que los componentes de React se suscriban a un solo valor global.

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];
    }
  );
}

Cerrar la interfaz

Llama a window.openai.requestClose() para pedirle a ChatGPT que cierre la interfaz actual.

Solicitar otro modo de presentación

Usa window.openai.requestDisplayMode para solicitar que la interfaz se muestre dentro de la conversación, en modo de imagen en imagen o en pantalla completa.

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

Abrir una ventana modal

Usa window.openai.requestModal para abrir una ventana modal controlada por el host. Indica el URI de otra plantilla de interfaz registrada por el mismo servidor MCP u omite template para abrir la plantilla actual.

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

API de archivos

ChatGPT ofrece funciones auxiliares para subir y descargar archivos como extensiones opcionales de window.openai.

API Finalidad Notas
window.openai.uploadFile(file, { library?: boolean }) Sube un archivo seleccionado por el usuario y recibe un fileId. Pasa { library: true } para guardar también el archivo en la biblioteca de archivos de ChatGPT si el usuario actual tiene acceso a ella.
window.openai.selectFiles() Abre el selector de la biblioteca para elegir archivos existentes. Devuelve [{ fileId, fileName, mimeType }]. Comprueba que esta función esté disponible antes de usarla, ya que algunos usuarios podrían no tener acceso a la biblioteca.
window.openai.getFileDownloadUrl({ fileId }) Solicita una URL de descarga temporal para un archivo. Admite archivos que el widget haya subido, que se hayan seleccionado en la biblioteca, que se hayan pasado mediante parámetros de archivo o que una herramienta haya devuelto por referencia.

La biblioteca de archivos de ChatGPT es opcional y es posible que algunos usuarios no tengan acceso a ella. Cuando la función window.openai.selectFiles() está disponible, los archivos que devuelve ya están autorizados para el complemento actual. Usa el fileId que devuelve con window.openai.getFileDownloadUrl({ fileId }) o en una entrada de herramienta que use parámetros de archivo.

Para subir un archivo seleccionado por el usuario, usa este código.

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

Para seleccionar archivos que el usuario ya subió a ChatGPT, usa este código.

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

Comprueba si window.openai.selectFiles está disponible y usa window.openai.uploadFile como alternativa cuando no se pueda acceder a la biblioteca de archivos.

Para solicitar una URL de descarga temporal, usa este código.

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

Definir entradas de archivo

Para que ChatGPT pueda pasar archivos a una herramienta, incluye cada campo de entrada de archivo de nivel superior en _meta["openai/fileParams"]. El valor de cada campo de la lista debe ser un objeto de archivo o un arreglo de objetos de archivo.

El esquema de cada objeto de archivo debe declarar las cuatro propiedades admitidas.

Propiedad Tipo Declarar en properties Incluir en required
download_url string Sí Sí
file_id string Sí Sí
mime_type string Sí No
file_name string Sí No

Los valores de mime_type y file_name son opcionales, pero debes declarar ambas propiedades en el esquema. Tanto el paso Analizar herramientas como el proceso de envío del complemento rechazan los esquemas de archivo que omitan alguna de las cuatro propiedades, que no exijan tanto download_url como file_id, que marquen alguna de las propiedades opcionales como obligatoria o que exijan una propiedad distinta de download_url o file_id. Puedes declarar propiedades opcionales adicionales.

Este descriptor completo de herramienta acepta una entrada de archivo obligatoria.

{
  "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"]
  }
}

Para aceptar más de un archivo, define el campo de nivel superior como un arreglo y usa el mismo esquema de objeto de archivo en items. La herramienta puede exigir el campo de archivo de nivel superior independientemente de las propiedades obligatorias de cada objeto de archivo.

Durante la ejecución, ChatGPT pasa los valores de archivo con nombres de campo en snake case.

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

ChatGPT siempre incluye download_url y file_id. Puede omitir mime_type y file_name. Usa file_id como valor de fileId en window.openai.getFileDownloadUrl({ fileId }) cuando un widget necesite una nueva URL de descarga temporal.

Al guardar el estado del widget, usa el formato estructurado (modelContent, privateContent, imageIds) si quieres que el modelo tenga acceso a los ID de las imágenes en turnos posteriores.

El entorno de ejecución del sandbox refleja el historial de navegación del iframe en la interfaz de ChatGPT. Usa API de enrutamiento estándar, como React Router, para que el host mantenga sus controles de navegación sincronizados con tu interfaz.

Configuración del enrutador con BrowserRouter de React Router

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>
  );
}

Navegación mediante código

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")
  );
}

Parámetros del descriptor de herramienta

Por defecto, la descripción de una herramienta debe incluir los campos que figuran en la especificación.

Declara outputSchema para toda herramienta que devuelva structuredContent. El esquema debe describir el objeto exacto que devuelve la herramienta para que los clientes puedan validar los resultados y el modelo pueda determinar cómo proceder en las siguientes llamadas a herramientas.

Campos _meta del descriptor de herramienta

Usa estos campos _meta en el descriptor de la herramienta. Da preferencia a la clave estándar de MCP Apps _meta.ui.resourceUri para vincular una herramienta con una plantilla de interfaz. ChatGPT admite metadatos específicos de OpenAI por compatibilidad y para ofrecer extensiones opcionales.

Clave Ubicación Tipo Límites Finalidad
_meta["securitySchemes"] Descriptor de herramienta array Ninguno Copia que mantiene la compatibilidad con clientes anteriores que solo leen _meta.
_meta.ui.resourceUri Descriptor de herramienta string (URI) Ninguno URI estándar del recurso correspondiente a la plantilla de interfaz.
_meta.ui.visibility Descriptor de herramienta string[] Valor predeterminado ["model", "app"] Controla si la herramienta está disponible para el modelo, para la interfaz o para ambos. El valor app identifica la interfaz en el protocolo MCP Apps.
_meta["openai/outputTemplate"] Descriptor de herramienta string (URI) Ninguno Alias opcional de _meta.ui.resourceUri específico de OpenAI que se admite por compatibilidad en ChatGPT.
_meta["openai/profile"] Descriptor de herramienta boolean Opcional. Solo true designa una herramienta de perfil. Identifica la herramienta autenticada de solo lectura que devuelve el perfil actual. Impleméntala para ayudar a los usuarios a reconocer y administrar varias cuentas conectadas. Los usuarios pueden conectar varias cuentas incluso sin esta herramienta. Consulta cómo admitir varias cuentas.
_meta["openai/widgetAccessible"] Descriptor de herramienta boolean Valor predeterminado false Campo de compatibilidad específico de OpenAI que usan las integraciones de interfaz existentes. Da preferencia a _meta.ui.visibility junto con tools/call.
_meta["openai/visibility"] Descriptor de herramienta string public (predeterminado) o private Campo de compatibilidad específico de OpenAI que usan las integraciones de interfaz existentes. Da preferencia a _meta.ui.visibility.
_meta["openai/toolInvocation/invoking"] Descriptor de herramienta string ≤ 64 caracteres Texto breve de estado que se muestra mientras se ejecuta la herramienta.
_meta["openai/toolInvocation/invoked"] Descriptor de herramienta string ≤ 64 caracteres Texto breve de estado que se muestra cuando la herramienta termina de ejecutarse.
_meta["openai/fileParams"] Descriptor de herramienta string[] Ninguno Lista de campos de entrada de nivel superior que representan archivos. Cada campo recibe { download_url, file_id, mime_type?, file_name? }.

Ejemplo

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.` }],
    };
  }
);

Anotaciones

Para identificar una herramienta como “de solo lectura”, usa los siguientes campos de ToolAnnotations en su descriptor.

Clave Tipo Obligatoria Notas
readOnlyHint boolean Sí Indica que la herramienta solo recupera información o realiza cálculos y que no crea, actualiza, elimina ni envía datos fuera de la conversación.
destructiveHint boolean Sí Declara que la herramienta puede eliminar o sobrescribir datos del usuario para que el host sepa que debe solicitar aprobación explícita antes de ejecutarla.
openWorldHint boolean Sí Declara que la herramienta accede a la internet pública o interactúa con entidades externas sin un alcance delimitado, incluso mediante acciones de solo lectura como la búsqueda web. El alojamiento externo por sí solo no convierte una cuenta privada o un espacio de trabajo de alcance delimitado en un entorno de mundo abierto.
idempotentHint boolean No Declara que repetir la llamada a la herramienta con los mismos argumentos no produce ningún efecto adicional en su entorno.

Estas indicaciones solo influyen en cómo ChatGPT o Codex presenta la llamada a la herramienta al usuario. Cada servidor debe aplicar su propia lógica de autorización.

Ejemplo

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() },
  })
);

Campos _meta del recurso del componente

Configura estas claves en la plantilla de recurso que sirve tu componente (registerResource). Permiten que ChatGPT describa el iframe renderizado y controle cómo se presenta sin exponer los metadatos a otros clientes.

Clave Ubicación Tipo Finalidad
_meta.ui.prefersBorder Contenido del recurso boolean Indica que el componente debería mostrarse dentro de una tarjeta con borde cuando se admita esa presentación.
_meta.ui.csp Contenido del recurso object Ubicación recomendada para los campos estándar de CSP del widget, que incluyen connectDomains, resourceDomains y, de forma opcional, frameDomains.
_meta.ui.domain Contenido del recurso string (origen) Origen dedicado a los componentes alojados. Es obligatorio al enviar un complemento con interfaz y debe ser único para cada complemento. El valor predeterminado es https://web-sandbox.oaiusercontent.com.
_meta["openai/ui"].availableDisplayModes Contenido del recurso string[] Los modos de visualización admitidos son inline, fullscreen y pip. Permite que ChatGPT elija un modo de visualización antes de cargar el componente.
_meta["openai/widgetDescription"] Contenido del recurso string Resumen legible que se muestra al modelo cuando se carga el componente, para reducir las explicaciones redundantes del asistente.
_meta["openai/widgetPrefersBorder"] Contenido del recurso boolean Alias de compatibilidad específico de OpenAI para _meta.ui.prefersBorder en ChatGPT.
_meta["openai/widgetCSP"] Contenido del recurso object Clave de compatibilidad de versiones anteriores de ChatGPT para los metadatos de CSP del widget. Los campos estándar de CSP se configuran ahora en _meta.ui.csp, pero redirect_domains sigue siendo obligatorio para los destinos de confianza de openExternal.
_meta["openai/widgetDomain"] Contenido del recurso string (origen) Alias de compatibilidad específico de OpenAI para _meta.ui.domain en ChatGPT.

ChatGPT admite la clave de compatibilidad _meta["openai/widgetCSP"] de versiones anteriores con los siguientes nombres de campo en snake_case.

  • connect_domains: string[]
  • resource_domains: string[]
  • frame_domains?: string[]
  • redirect_domains?: string[]. Extensión de ChatGPT para los destinos de redirección de window.openai.openExternal.

Para interfaces nuevas, suele ser preferible el objeto estándar _meta.ui.csp, que admite los siguientes campos.

  • connectDomains: string[]. Dominios con los que el widget puede comunicarse mediante fetch/XHR.
  • resourceDomains: string[]. Dominios de recursos estáticos (imágenes, fuentes, scripts y estilos).
  • frameDomains?: string[]. Lista opcional de orígenes permitidos para contenido integrado mediante iframes. De forma predeterminada, los widgets no pueden renderizar marcos anidados. Los complementos pueden integrar contenido de su propio dominio, incluidos editores e interfaces de administración existentes, de acuerdo con la política sobre iframes. Debes justificar el uso de iframes al enviar el complemento. Su uso puede requerir una revisión adicional o demorar la aprobación.

Sin embargo, _meta.ui.csp no admite redirect_domains para los enlaces de window.openai.openExternal(...). Para incluir destinos de redirección en la lista de permitidos, debes seguir configurando _meta["openai/widgetCSP"].redirect_domains.

Declarar los modos de visualización antes de que se cargue la interfaz

Configura _meta["openai/ui"].availableDisplayModes en el contenido del recurso que devuelve tu servidor. Si el componente solo admite pantalla completa, ChatGPT podrá abrirlo de inmediato en ese modo sin mostrar primero un indicador de carga dentro de la conversación.

Devuelve este objeto en el arreglo contents del recurso y asigna el HTML del componente a html.

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

Incluye todos los modos que admita tu componente, por ejemplo, ["inline", "fullscreen"]. Sigue declarando availableDisplayModes también durante la inicialización de MCP Apps. La declaración del servidor complementa ese proceso, que también admite modos de visualización dinámicos.

Resultados de las herramientas

Los resultados de las herramientas pueden contener los campos que define la especificación. En la tabla se destacan tres de ellos.

Clave Tipo Obligatoria Notas
structuredContent object No Se pone a disposición del modelo y del componente. Si se declara outputSchema, el contenido debe ajustarse a ese esquema.
content string o Content[] No Se pone a disposición del modelo y del componente.
_meta object No Solo se entrega al componente. El modelo no tiene acceso a este campo.

Solo structuredContent y content aparecen en la transcripción de la conversación. El host reenvía _meta al componente para que puedas hidratar la interfaz sin exponer esos datos al modelo.

Metadatos del resultado de la herramienta que proporciona el host

Clave Ubicación Tipo Finalidad
_meta["openai/widgetSessionId"] Campo _meta del resultado de la herramienta (proporcionado por el host) string ID estable de la instancia del widget que está montada. Úsalo para correlacionar registros y llamadas a herramientas hasta que se desmonte el widget.

Ejemplo

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])
        ),
      },
    };
  }
);

Resultado de herramienta con error

Para devolver un error en el resultado de la herramienta, usa la siguiente clave de _meta.

Clave Finalidad Tipo Notas
_meta["mcp/www_authenticate"] Resultado de error string o string[] Desafíos de autenticación WWW-Authenticate definidos en RFC 7235 para iniciar el flujo de OAuth.

Campos _meta que proporciona el cliente

Clave Cuándo se proporciona Tipo Finalidad
_meta["openai/locale"] Inicialización y llamadas a herramientas string (BCP 47) Configuración regional solicitada (los clientes anteriores pueden enviar _meta["webplus/i18n"]).
_meta["openai/userAgent"] Llamadas a herramientas string Información orientativa y opcional sobre el agente de usuario, para análisis o ajustes de formato.
_meta["openai/userLocation"] Llamadas a herramientas object Información aproximada sobre la ubicación (city, region, country, timezone, longitude, latitude).
_meta["openai/subject"] Llamadas a herramientas string ID de usuario anonimizado que se envía a los servidores MCP para identificar al usuario y aplicar límites de solicitudes.
_meta["openai/session"] Llamadas a herramientas string ID de conversación anonimizado para correlacionar llamadas a herramientas dentro de una misma sesión de ChatGPT.
_meta["openai/organization"] Llamadas a herramientas string ID de la organización actual de ChatGPT, anonimizado, cuando está disponible.

Durante la ejecución, _meta["openai/userAgent"] y _meta["openai/userLocation"] solo proporcionan información orientativa. Los servidores nunca deben basar sus decisiones de autorización en estos campos y deben poder funcionar aunque falten. Trata _meta["openai/userAgent"] como un metadato opcional que se proporciona cuando es posible. No permite identificar de forma fiable desde qué interfaz del host se llama a tu servidor.

Ejemplo

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 },
    };
  }
);