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é | Fonction | Utilisation courante |
|---|---|---|
| État et données | window.openai.toolInput | Arguments 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ées | window.openai.toolOutput | Votre structuredContent. Gardez les champs concis ; le modèle les lit tels quels. |
| État et données | window.openai.toolResponseMetadata | Mé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ées | window.openai.widgetState | Instantané de l’état de l’interface conservé entre les rendus. |
| État et données | window.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 widget | window.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 widget | window.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 widget | 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 disponible. |
| API de l’environnement d’exécution du widget | window.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 widget | window.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 widget | window.openai.requestDisplayMode(...) | Demandez les modes PiP ou plein écran. |
| API de l’environnement d’exécution du widget | window.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 widget | window.openai.requestClose() | Demandez à ChatGPT de fermer le widget actuel. |
| API de l’environnement d’exécution du widget | window.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 widget | window.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 widget | window.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. |
| Contexte | window.openai.theme, window.openai.displayMode, window.openai.maxHeight, window.openai.safeArea, window.openai.view, window.openai.userAgent, window.openai.locale | Signaux 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.
| API | Fonction | Remarques |
|---|---|---|
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_url | string | Oui | Oui |
file_id | string | Oui | Oui |
mime_type | string | Oui | Non |
file_name | string | Oui | Non |
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é | Emplacement | Type | Limites | Rôle |
|---|---|---|---|---|
_meta["securitySchemes"] | Descripteur d’outil | array | Aucune | Copie destinée à assurer la rétrocompatibilité avec les clients qui ne lisent que _meta. |
_meta.ui.resourceUri | Descripteur d’outil | string (URI) | Aucune | URI de ressource standard du modèle d’interface. |
_meta.ui.visibility | Descripteur d’outil | string[] | 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’outil | string (URI) | Aucune | Alias de compatibilité facultatif propre à OpenAI pour _meta.ui.resourceUri dans ChatGPT. |
_meta["openai/profile"] | Descripteur d’outil | boolean | Facultatif ; seule la valeur true désigne un outil de profil | Identifie 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’outil | boolean | par défaut : false | Champ de compatibilité propre à OpenAI utilisé par les intégrations d’interface existantes ; privilégiez _meta.ui.visibility + tools/call. |
_meta["openai/visibility"] | Descripteur d’outil | string | public (par défaut) ou private | Champ de compatibilité propre à OpenAI utilisé par les intégrations d’interface existantes ; privilégiez _meta.ui.visibility. |
_meta["openai/toolInvocation/invoking"] | Descripteur d’outil | string | ≤ 64 caractères | Court texte d’état affiché pendant l’exécution de l’outil. |
_meta["openai/toolInvocation/invoked"] | Descripteur d’outil | string | ≤ 64 caractères | Court texte d’état affiché une fois l’exécution de l’outil terminée. |
_meta["openai/fileParams"] | Descripteur d’outil | string[] | Aucune | Liste 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é | Type | Obligatoire | Remarques |
|---|---|---|---|
readOnlyHint | boolean | Obligatoire | Indiquez 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. |
destructiveHint | boolean | Obligatoire | Dé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. |
openWorldHint | boolean | Obligatoire | Dé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. |
idempotentHint | boolean | Facultatif | Dé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é | Emplacement | Type | Rôle |
|---|---|---|---|
_meta.ui.prefersBorder | Contenu de la ressource | boolean | Indiquez que le composant devrait s’afficher dans une carte avec bordure lorsque cette présentation est prise en charge. |
_meta.ui.csp | Contenu de la ressource | object | Emplacement recommandé dans les métadonnées pour les champs CSP standard du widget : connectDomains, resourceDomains et, facultativement, frameDomains. |
_meta.ui.domain | Contenu de la ressource | string (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 ressource | string | Ré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 ressource | boolean | Alias de compatibilité propre à OpenAI pour _meta.ui.prefersBorder dans ChatGPT. |
_meta["openai/widgetCSP"] | Contenu de la ressource | object | Ancienne 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 ressource | string (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 dewindow.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é | Type | Obligatoire | Remarques |
|---|---|---|---|
structuredContent | object | Facultatif | Accessible au modèle et au composant. Doit respecter le schéma outputSchema déclaré, s’il est fourni. |
content | string ou Content[] | Facultatif | Accessible au modèle et au composant. |
_meta | object | Facultatif | Transmis 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é | Emplacement | Type | Rôle |
|---|---|---|---|
_meta["openai/widgetSessionId"] | _meta du résultat de l’outil (fourni par l’hôte) | string | Identifiant 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ôle | Type | Remarques |
|---|---|---|---|
_meta["mcp/www_authenticate"] | Résultat d’erreur | string 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 transmission | Type | Rôle |
|---|---|---|---|
_meta["openai/locale"] | Initialisation + appels d’outils | string (BCP 47) | Paramètres régionaux demandés (les anciens clients peuvent envoyer _meta["webplus/i18n"]). |
_meta["openai/userAgent"] | Appels d’outils | string | Indication 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’outils | object | Indication de localisation approximative (city, region, country, timezone, longitude, latitude). |
_meta["openai/subject"] | Appels d’outils | string | Identifiant utilisateur anonymisé envoyé aux serveurs MCP à des fins de limitation du débit et d’identification |
_meta["openai/session"] | Appels d’outils | string | Identifiant de conversation anonymisé permettant de corréler les appels d’outils au sein d’une même session ChatGPT. |
_meta["openai/organization"] | Appels d’outils | string | Identifiant 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 },
};
}
);