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

Référence

Référence des extensions d’interface et des métadonnées propres à ChatGPT.

Commencez par le standard ouvert. Utilisez la

spécification MCP Apps

pour les champs d’interface et les méthodes de passerelle communs. Les extensions OpenAI sont facultatives et sont disponibles dans window.openai si vous souhaitez utiliser des capacités propres à ChatGPT.

Passerelle de composants window.openai

ChatGPT fournit window.openai pour les alias de compatibilité et les extensions facultatives de ChatGPT. Les nouvelles interfaces doivent utiliser la passerelle MCP Apps dès que la spécification commune propose un équivalent, puis réserver window.openai aux capacités propres à ChatGPT.

Consultez Créer une interface ChatGPT pour des guides de mise en œuvre détaillés.

Si votre outil nécessite une confirmation, l’absence initiale de toolInput est normale. ChatGPT ne charge pas les arguments soumis à approbation dans les valeurs du widget avant l’approbation ; l’hôte les transmet via ui/notifications/tool-input une fois que l’utilisateur a approuvé l’appel.

Capacités

CapacitéFonctionUtilisation courante
État et donnéeswindow.openai.toolInputArguments fournis lors de l’appel de l’outil. Pour les outils soumis à approbation, cette valeur peut rester à null jusqu’à ce que l’hôte envoie ui/notifications/tool-input après l’approbation.
État et donnéeswindow.openai.toolOutputVotre structuredContent. Gardez les champs concis ; le modèle les lit tels quels.
État et donnéeswindow.openai.toolResponseMetadataMétadonnées canoniques du résultat de l’outil, réservées au widget. Dans ChatGPT, elles comprennent status, call_tool_result et mcp_tool_result, ce qui préserve l’intégralité de l’enveloppe du résultat MCP, y compris le champ masqué _meta.
État et donnéeswindow.openai.widgetStateInstantané de l’état de l’interface conservé entre les rendus.
État et donnéeswindow.openai.setWidgetState(state)Enregistre un nouvel instantané de façon synchrone ; appelez cette fonction après chaque interaction significative avec l’interface.
API de l’environnement d’exécution du widgetwindow.openai.callTool(name, args)Appelez un autre outil MCP depuis le widget (comme pour les appels lancés par le modèle).
API de l’environnement d’exécution du widgetwindow.openai.sendFollowUpMessage({ prompt, scrollToBottom })Demandez à ChatGPT de publier un message rédigé par le composant. scrollToBottom est facultatif, vaut true par défaut et peut être défini sur false pour empêcher le défilement automatique.
API de l’environnement d’exécution du widgetwindow.openai.uploadFile(file, { library?: boolean })Importez un fichier sélectionné par l’utilisateur et recevez un fileId. Passez { library: true } pour enregistrer également le fichier importé dans la bibliothèque de fichiers ChatGPT de l’utilisateur, lorsque celle-ci est disponible.
API de l’environnement d’exécution du widgetwindow.openai.selectFiles()Ouvrez le sélecteur de la bibliothèque de fichiers ChatGPT et renvoyez les fichiers auxquels le plugin est autorisé à accéder sous la forme { fileId, fileName, mimeType }[]. Vérifiez la disponibilité de cette fonction utilitaire avant de l’utiliser, car la bibliothèque de fichiers peut ne pas être accessible à tous les utilisateurs.
API de l’environnement d’exécution du widgetwindow.openai.getFileDownloadUrl({ fileId })Récupérez une URL de téléchargement temporaire pour un fichier importé par le widget, sélectionné dans la bibliothèque de fichiers, transmis via les paramètres de fichier ou renvoyé dans les références de fichiers d’un outil.
API de l’environnement d’exécution du widgetwindow.openai.requestDisplayMode(...)Demandez les modes PiP ou plein écran.
API de l’environnement d’exécution du widgetwindow.openai.requestModal({ params, template })Ouvrez une fenêtre modale gérée par ChatGPT. Omettez template pour utiliser le modèle d’interface actuel, ou passez l’URI d’un modèle enregistré pour changer le contenu de la fenêtre modale.
API de l’environnement d’exécution du widgetwindow.openai.requestClose()Demandez à ChatGPT de fermer le widget actuel.
API de l’environnement d’exécution du widgetwindow.openai.notifyIntrinsicHeight(...)Signalez les changements de hauteur du widget pour éviter que le contenu ne soit tronqué lors du défilement.
API de l’environnement d’exécution du widgetwindow.openai.openExternal({ href, redirectUrl })Ouvrez un lien externe vérifié dans le navigateur de l’utilisateur. Pour les destinations de redirection approuvées, ChatGPT ajoute ?redirectUrl=... par défaut ; définissez redirectUrl: false pour empêcher cet ajout.
API de l’environnement d’exécution du widgetwindow.openai.setOpenInAppUrl({ href })Remplacez, si nécessaire, la destination externe affichée en plein écran. Si aucune valeur n’est définie, ChatGPT conserve le comportement par défaut et ouvre le chemin actuel de l’iframe du composant.
Contextewindow.openai.theme, window.openai.displayMode, window.openai.maxHeight, window.openai.safeArea, window.openai.view, window.openai.userAgent, window.openai.localeSignaux de l’environnement que vous pouvez lire ou auxquels vous pouvez vous abonner via useOpenAiGlobal pour adapter les éléments visuels et le texte.

Fonction utilitaire useOpenAiGlobal

De nombreux projets d’interface ChatGPT encapsulent l’accès à window.openai dans de petites fonctions utilitaires pour que les vues restent testables. La fonction utilitaire de cet exemple écoute les événements openai:set_globals de l’hôte et permet aux composants React de s’abonner à une seule valeur globale :

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

Fermez l’interface

Appelez window.openai.requestClose() pour demander à ChatGPT de fermer l’interface actuelle.

Demandez un autre mode d’affichage

Utilisez window.openai.requestDisplayMode pour demander un affichage intégré, en incrustation d’image ou en plein écran :

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

Ouvrez une fenêtre modale

Utilisez window.openai.requestModal pour ouvrir une fenêtre modale contrôlée par l’hôte. Fournissez l’URI d’un autre modèle d’interface enregistré par le même serveur MCP, ou omettez template pour ouvrir le modèle actuel :

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

API de fichiers

ChatGPT prend en charge des fonctions utilitaires d’importation et de téléchargement de fichiers via window.openai, sous forme d’extensions facultatives.

APIFonctionRemarques
window.openai.uploadFile(file, { library?: boolean })Importez un fichier sélectionné par l’utilisateur et recevez un fileId.Passez { library: true } pour enregistrer également le fichier importé dans la bibliothèque de fichiers ChatGPT de l’utilisateur, lorsque celle-ci est accessible à l’utilisateur actuel.
window.openai.selectFiles()Ouvrez le sélecteur de la bibliothèque de fichiers pour choisir des fichiers existants.Renvoie [{ fileId, fileName, mimeType }]. Vérifiez la disponibilité de cette fonction utilitaire, car la bibliothèque de fichiers peut ne pas être accessible à tous les utilisateurs.
window.openai.getFileDownloadUrl({ fileId })Demandez une URL de téléchargement temporaire pour un fichier.Fonctionne pour les fichiers importés par le widget, sélectionnés dans la bibliothèque de fichiers, transmis via les paramètres de fichier ou renvoyés par les références de fichiers des outils.

La bibliothèque de fichiers de ChatGPT est facultative et peut ne pas être accessible à tous les utilisateurs. Lorsque la fonction utilitaire est disponible, les fichiers renvoyés par window.openai.selectFiles() sont déjà autorisés pour le plugin actuel. Utilisez la valeur fileId renvoyée avec window.openai.getFileDownloadUrl({ fileId }) ou dans une entrée d’outil qui utilise des paramètres de fichier.

Importez un fichier sélectionné par l’utilisateur :

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

Sélectionnez des fichiers que l’utilisateur a déjà importés dans ChatGPT :

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

Vérifiez la disponibilité de window.openai.selectFiles et utilisez window.openai.uploadFile comme solution de repli lorsque la bibliothèque de fichiers n’est pas disponible.

Demandez une URL de téléchargement temporaire :

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

Définissez les fichiers en entrée

Pour permettre à ChatGPT de transmettre des fichiers à un outil, répertoriez chaque entrée de fichier de premier niveau dans _meta["openai/fileParams"]. Chaque champ répertorié doit correspondre à un objet fichier ou à un tableau d’objets fichier.

Chaque schéma d’objet fichier doit déclarer les quatre propriétés prises en charge :

PropriétéTypeÀ déclarer dans propertiesÀ inclure dans required
download_urlstringOuiOui
file_idstringOuiOui
mime_typestringOuiNon
file_namestringOuiNon

Les valeurs mime_type et file_name sont facultatives, mais vous devez déclarer les propriétés correspondantes dans le schéma. L’étape Analyser les outils et la soumission du plugin rejettent tout schéma de fichier qui omet l’une des quatre propriétés, ne rend pas obligatoires download_url et file_id, rend obligatoire l’une des deux propriétés facultatives ou exige une propriété autre que download_url ou file_id. Vous pouvez déclarer des propriétés facultatives supplémentaires.

Ce descripteur d’outil complet accepte un fichier obligatoire en entrée :

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

Pour accepter plusieurs fichiers, définissez le champ de premier niveau comme un tableau et utilisez le même schéma d’objet fichier dans items. L’outil peut rendre obligatoire le champ de fichier de premier niveau indépendamment des propriétés obligatoires dans chaque objet fichier.

À l’exécution, ChatGPT transmet les valeurs des fichiers avec des noms de champs en snake case :

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

ChatGPT inclut toujours download_url et file_id ; il peut omettre mime_type et file_name. Utilisez file_id comme valeur de fileId pour window.openai.getFileDownloadUrl({ fileId }) lorsqu’un widget a besoin d’une nouvelle URL de téléchargement temporaire.

Lorsque vous enregistrez l’état du widget, utilisez le format structuré (modelContent, privateContent, imageIds) si vous souhaitez que le modèle ait accès aux identifiants des images lors des échanges suivants.

Navigation prise en charge par l’hôte

L’environnement d’exécution du bac à sable répercute l’historique de navigation de l’iframe dans l’interface de ChatGPT. Utilisez des API de routage standard, comme React Router, et l’hôte maintiendra ses commandes de navigation synchronisées avec votre interface.

Configuration du routeur avec BrowserRouter de React Router :

export default function PizzaListRouter() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<PizzaListPlugin />}>
          <Route path="place/:placeId" element={<PizzaListPlugin />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

Navigation par programmation :

const navigate = useNavigate();

function openDetails(placeId: string) {
  navigate(`place/${placeId}`, { replace: false });
}

function closeDetails() {
  navigate("..", { replace: true });
}

Paramètres du descripteur d’outil

Par défaut, la description d’un outil devrait inclure les champs répertoriés ici.

Déclarez outputSchema pour tout outil qui renvoie structuredContent. Le schéma devrait décrire précisément l’objet renvoyé par votre outil afin que les clients puissent valider les résultats et que le modèle puisse raisonner sur les appels d’outils suivants.

Champs _meta du descripteur d’outil

Utilisez ces champs _meta dans le descripteur d’outil. Privilégiez la clé standard de MCP Apps _meta.ui.resourceUri pour associer un outil à un modèle d’interface. ChatGPT prend en charge des métadonnées propres à OpenAI pour assurer la compatibilité et proposer des extensions facultatives.

CléEmplacementTypeLimitesRôle
_meta["securitySchemes"]Descripteur d’outilarrayAucuneCopie destinée à assurer la rétrocompatibilité avec les clients qui ne lisent que _meta.
_meta.ui.resourceUriDescripteur d’outilstring (URI)AucuneURI de ressource standard du modèle d’interface.
_meta.ui.visibilityDescripteur d’outilstring[]par défaut : ["model", "app"]Détermine si un outil est accessible au modèle, à l’interface ou aux deux. La valeur app est l’identifiant de l’interface dans le protocole MCP Apps.
_meta["openai/outputTemplate"]Descripteur d’outilstring (URI)AucuneAlias de compatibilité facultatif propre à OpenAI pour _meta.ui.resourceUri dans ChatGPT.
_meta["openai/profile"]Descripteur d’outilbooleanFacultatif ; seule la valeur true désigne un outil de profilIdentifie l’outil authentifié, en lecture seule, qui renvoie le profil actuel. Implémentez-le pour aider les utilisateurs à reconnaître et à gérer plusieurs comptes connectés. Les utilisateurs peuvent connecter plusieurs comptes sans cet outil. Consultez Prise en charge de plusieurs comptes.
_meta["openai/widgetAccessible"]Descripteur d’outilbooleanpar défaut : falseChamp de compatibilité propre à OpenAI utilisé par les intégrations d’interface existantes ; privilégiez _meta.ui.visibility + tools/call.
_meta["openai/visibility"]Descripteur d’outilstringpublic (par défaut) ou privateChamp de compatibilité propre à OpenAI utilisé par les intégrations d’interface existantes ; privilégiez _meta.ui.visibility.
_meta["openai/toolInvocation/invoking"]Descripteur d’outilstring≤ 64 caractèresCourt texte d’état affiché pendant l’exécution de l’outil.
_meta["openai/toolInvocation/invoked"]Descripteur d’outilstring≤ 64 caractèresCourt texte d’état affiché une fois l’exécution de l’outil terminée.
_meta["openai/fileParams"]Descripteur d’outilstring[]AucuneListe des champs d’entrée de premier niveau qui représentent des fichiers. Chaque champ reçoit { download_url, file_id, mime_type?, file_name? }.

Exemple :

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

Annotations

Pour indiquer qu’un outil est « en lecture seule », utilisez les champs ToolAnnotations suivants dans le descripteur d’outil :

CléTypeObligatoireRemarques
readOnlyHintbooleanObligatoireIndiquez que l’outil se limite à récupérer des informations ou à effectuer des calculs, sans créer, modifier, supprimer ni envoyer de données en dehors de la conversation.
destructiveHintbooleanObligatoireDéclarez que l’outil peut supprimer ou écraser des données utilisateur afin que l’hôte sache qu’il doit d’abord demander une approbation explicite.
openWorldHintbooleanObligatoireDéclarez que l’outil accède à l’Internet public ou à des entités externes sans périmètre délimité, y compris par des actions en lecture seule comme la recherche web. Un compte ou un espace de travail privé au périmètre délimité ne constitue pas un environnement ouvert du seul fait qu’il est hébergé à l’extérieur.
idempotentHintbooleanFacultatifDéclarez que des appels répétés à l’outil avec les mêmes arguments n’ont aucun effet supplémentaire sur son environnement.

Ces indications influencent uniquement la manière dont ChatGPT ou Codex présente l’appel d’outil à l’utilisateur ; les serveurs doivent toujours appliquer leur propre logique d’autorisation.

Exemple :

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

Champs _meta de la ressource du composant

Définissez ces clés dans le modèle de ressource qui sert votre composant (registerResource). Elles aident ChatGPT à décrire et à présenter l’iframe affichée sans divulguer de métadonnées aux autres clients.

CléEmplacementTypeRôle
_meta.ui.prefersBorderContenu de la ressourcebooleanIndiquez que le composant devrait s’afficher dans une carte avec bordure lorsque cette présentation est prise en charge.
_meta.ui.cspContenu de la ressourceobjectEmplacement recommandé dans les métadonnées pour les champs CSP standard du widget : connectDomains, resourceDomains et, facultativement, frameDomains.
_meta.ui.domainContenu de la ressourcestring (origine)Origine dédiée aux composants hébergés (obligatoire lors de la soumission d’un plugin avec une interface ; doit être unique pour chaque plugin). Valeur par défaut : https://web-sandbox.oaiusercontent.com.
_meta["openai/widgetDescription"]Contenu de la ressourcestringRésumé en langage naturel transmis au modèle lors du chargement du composant, qui réduit les explications redondantes de l’assistant.
_meta["openai/widgetPrefersBorder"]Contenu de la ressourcebooleanAlias de compatibilité propre à OpenAI pour _meta.ui.prefersBorder dans ChatGPT.
_meta["openai/widgetCSP"]Contenu de la ressourceobjectAncienne clé de compatibilité ChatGPT pour les métadonnées CSP du widget. Les champs CSP standard sont remplacés par _meta.ui.csp, mais redirect_domains reste obligatoire pour les destinations de confiance de openExternal.
_meta["openai/widgetDomain"]Contenu de la ressourcestring (origine)Alias de compatibilité propre à OpenAI pour _meta.ui.domain dans ChatGPT.

ChatGPT prend en charge l’ancienne clé de compatibilité _meta["openai/widgetCSP"] avec les noms de champs suivants au format snake_case :

  • connect_domains: string[]
  • resource_domains: string[]
  • frame_domains?: string[]
  • redirect_domains?: string[]. Extension ChatGPT pour les cibles de redirection de window.openai.openExternal.

L’objet standard _meta.ui.csp est généralement recommandé pour les nouvelles interfaces et prend en charge les champs suivants :

  • connectDomains: string[]. Domaines que le widget peut contacter via fetch/XHR.
  • resourceDomains: string[]. Domaines des ressources statiques (images, polices, scripts, styles).
  • frameDomains? : string[]. Liste facultative des origines autorisées pour les intégrations dans des iframes. Par défaut, les widgets ne peuvent pas afficher de cadres imbriqués. Les Plugins peuvent intégrer du contenu de leur propre domaine, notamment des éditeurs et des interfaces d’administration existants, conformément à la politique relative aux iframes. Une justification est requise lors de la soumission, et l’utilisation d’iframes peut nécessiter une révision supplémentaire ou allonger le délai d’approbation.

Cependant, _meta.ui.csp ne prend pas en charge redirect_domains pour les liens window.openai.openExternal(...). Pour ajouter des cibles de redirection à la liste des destinations autorisées, vous devez toujours définir _meta["openai/widgetCSP"].redirect_domains.

Résultats des outils

Les résultats des outils peuvent contenir les champs suivants, notamment :

CléTypeObligatoireRemarques
structuredContentobjectFacultatifAccessible au modèle et au composant. Doit respecter le schéma outputSchema déclaré, s’il est fourni.
contentstring ou Content[]FacultatifAccessible au modèle et au composant.
_metaobjectFacultatifTransmis uniquement au composant. Masqué au modèle.

Seuls structuredContent et content apparaissent dans la transcription de la conversation. L’hôte transmet _meta au composant pour vous permettre d’alimenter l’interface sans exposer les données au modèle.

Métadonnées des résultats des outils fournies par l’hôte :

CléEmplacementTypeRôle
_meta["openai/widgetSessionId"]_meta du résultat de l’outil (fourni par l’hôte)stringIdentifiant stable de l’instance du widget actuellement montée ; utilisez-le pour corréler les journaux et les appels d’outils jusqu’au démontage du widget.

Exemple :

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

Résultat d’outil signalant une erreur

Pour renvoyer une erreur dans le résultat de l’outil, utilisez la clé _meta suivante :

CléRôleTypeRemarques
_meta["mcp/www_authenticate"]Résultat d’erreurstring ou string[]Défis d’authentification WWW-Authenticate conformes à la RFC 7235 pour déclencher OAuth.

Champs _meta fournis par le client

CléMoment de transmissionTypeRôle
_meta["openai/locale"]Initialisation + appels d’outilsstring (BCP 47)Paramètres régionaux demandés (les anciens clients peuvent envoyer _meta["webplus/i18n"]).
_meta["openai/userAgent"]Appels d’outilsstringIndication facultative de l’agent utilisateur, fournie dans la mesure du possible à des fins d’analyse ou de mise en forme.
_meta["openai/userLocation"]Appels d’outilsobjectIndication de localisation approximative (city, region, country, timezone, longitude, latitude).
_meta["openai/subject"]Appels d’outilsstringIdentifiant utilisateur anonymisé envoyé aux serveurs MCP à des fins de limitation du débit et d’identification
_meta["openai/session"]Appels d’outilsstringIdentifiant de conversation anonymisé permettant de corréler les appels d’outils au sein d’une même session ChatGPT.
_meta["openai/organization"]Appels d’outilsstringIdentifiant d’organisation anonymisé associé à l’organisation ChatGPT actuelle, lorsqu’il est disponible.

Pendant l’exécution, _meta["openai/userAgent"] et _meta["openai/userLocation"] ne sont que des indications ; les serveurs ne doivent jamais s’appuyer sur ces valeurs pour prendre des décisions d’autorisation et doivent tolérer leur absence. Considérez _meta["openai/userAgent"] comme une métadonnée facultative, fournie dans la mesure du possible, et non comme un moyen fiable d’identifier l’interface hôte qui appelle votre serveur.

Exemple :

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