Beginne mit dem offenen Standard. Verwende die
MCP Apps-Spezifikation
für gemeinsame UI-Felder und Bridge-Methoden.
OpenAI-Erweiterungen sind optional und stehen in window.openai bereit,
wenn du ChatGPT-spezifische Funktionen benötigst.
Komponenten-Bridge window.openai
ChatGPT stellt window.openai für Kompatibilitätsaliase und optionale
ChatGPT-Erweiterungen bereit. Neue Benutzeroberflächen sollten die MCP Apps-Bridge verwenden, wenn die gemeinsame
Spezifikation eine entsprechende Funktion bietet. Verwende window.openai dann nur für
ChatGPT-spezifische Funktionen.
Schritt-für-Schritt-Anleitungen zur Implementierung findest du unter Eine ChatGPT-Benutzeroberfläche entwickeln.
Wenn dein Tool eine Bestätigung erfordert, ist es normal, dass toolInput anfangs fehlt.
ChatGPT lädt genehmigungspflichtige Argumente erst nach der Genehmigung in die Widget-Werte.
Der Host übermittelt sie über
ui/notifications/tool-input, sobald die nutzende Person den Aufruf genehmigt.
Funktionen
| Funktion | Funktionsweise | Typische Verwendung |
|---|---|---|
| Zustand & Daten | window.openai.toolInput | Argumente, die beim Aufruf des Tools übergeben wurden. Bei genehmigungspflichtigen Tools kann dieser Wert null bleiben, bis der Host nach der Genehmigung ui/notifications/tool-input sendet. |
| Zustand & Daten | window.openai.toolOutput | Dein structuredContent. Halte die Feldinhalte knapp; das Modell liest sie wortwörtlich. |
| Zustand & Daten | window.openai.toolResponseMetadata | Kanonische Metadaten zum Tool-Ergebnis, die nur dem Widget zur Verfügung stehen. In ChatGPT umfassen sie status, call_tool_result und mcp_tool_result. Dabei bleibt die vollständige MCP-Ergebnisstruktur einschließlich des verborgenen Feldes _meta erhalten. |
| Zustand & Daten | window.openai.widgetState | Momentaufnahme des UI-Zustands, die zwischen Rendervorgängen gespeichert bleibt. |
| Zustand & Daten | window.openai.setWidgetState(state) | Speichert synchron eine neue Momentaufnahme. Rufe diese Funktion nach jeder relevanten UI-Interaktion auf. |
| APIs der Widget-Laufzeit | window.openai.callTool(name, args) | Rufe ein anderes MCP-Tool aus dem Widget auf (entspricht den vom Modell initiierten Aufrufen). |
| APIs der Widget-Laufzeit | window.openai.sendFollowUpMessage({ prompt, scrollToBottom }) | Fordere ChatGPT auf, eine von der Komponente verfasste Nachricht zu senden. scrollToBottom ist optional und hat den Standardwert true. Setze den Wert auf false, um automatisches Scrollen zu verhindern. |
| APIs der Widget-Laufzeit | window.openai.uploadFile(file, { library?: boolean }) | Lade eine von der nutzenden Person ausgewählte Datei hoch und erhalte eine fileId. Übergib { library: true }, um die hochgeladene Datei auch in ihrer ChatGPT-Dateibibliothek zu speichern, sofern diese verfügbar ist. |
| APIs der Widget-Laufzeit | window.openai.selectFiles() | Öffne den Auswahldialog der ChatGPT-Dateibibliothek und gib die für das Plug-in autorisierten Dateien als { fileId, fileName, mimeType }[] zurück. Prüfe, ob diese Hilfsfunktion verfügbar ist, da die Dateibibliothek möglicherweise nicht allen Nutzenden zur Verfügung steht. |
| APIs der Widget-Laufzeit | window.openai.getFileDownloadUrl({ fileId }) | Rufe eine temporäre Download-URL für eine Datei ab, die vom Widget hochgeladen, aus der Dateibibliothek ausgewählt, über Dateiparameter übergeben oder über Dateireferenzen eines Tools zurückgegeben wurde. |
| APIs der Widget-Laufzeit | window.openai.requestDisplayMode(...) | Fordere den PiP- oder Vollbildmodus an. |
| APIs der Widget-Laufzeit | window.openai.requestModal({ params, template }) | Öffne einen von ChatGPT verwalteten modalen Dialog. Lass template weg, um die aktuelle Vorlage zu verwenden, oder übergib die URI einer registrierten Vorlage, um den Dialoginhalt zu wechseln. |
| APIs der Widget-Laufzeit | window.openai.requestClose() | Fordere ChatGPT auf, das aktuelle Widget zu schließen. |
| APIs der Widget-Laufzeit | window.openai.notifyIntrinsicHeight(...) | Melde dynamische Änderungen der Widget-Höhe, damit beim Scrollen keine Inhalte abgeschnitten werden. |
| APIs der Widget-Laufzeit | window.openai.openExternal({ href, redirectUrl }) | Öffne einen geprüften externen Link im Browser der nutzenden Person. Bei genehmigten Weiterleitungszielen hängt ChatGPT standardmäßig ?redirectUrl=... an. Setze redirectUrl: false, um das zu verhindern. |
| APIs der Widget-Laufzeit | window.openai.setOpenInAppUrl({ href }) | Überschreibe optional das im Vollbildmodus angezeigte externe Ziel. Ohne diese Angabe behält ChatGPT das Standardverhalten bei und öffnet den aktuellen iframe-Pfad der Komponente. |
| Kontext | window.openai.theme, window.openai.displayMode, window.openai.maxHeight, window.openai.safeArea, window.openai.view, window.openai.userAgent, window.openai.locale | Umgebungssignale, die du über useOpenAiGlobal auslesen oder abonnieren kannst, um Darstellung und Texte anzupassen. |
Hilfsfunktion useOpenAiGlobal
Viele Projekte für ChatGPT-Benutzeroberflächen kapseln den Zugriff auf window.openai in kleinen Hilfsfunktionen,
damit die Ansichten testbar bleiben. Die Hilfsfunktion in diesem Beispiel reagiert auf
openai:set_globals-Ereignisse des Hosts und ermöglicht React-Komponenten, einen einzelnen
globalen Wert zu abonnieren:
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]
);
}
Benutzeroberfläche schließen
Rufe window.openai.requestClose() auf, um ChatGPT aufzufordern, die aktuelle Benutzeroberfläche zu schließen.
Einen anderen Darstellungsmodus anfordern
Verwende window.openai.requestDisplayMode, um eine Inline-, Bild-in-Bild-
oder Vollbilddarstellung anzufordern:
await window.openai?.requestDisplayMode({ mode: "fullscreen" });
// On mobile, picture-in-picture may be presented as fullscreen.
Einen modalen Dialog öffnen
Verwende window.openai.requestModal, um einen vom Host gesteuerten modalen Dialog zu öffnen. Gib die
URI einer anderen UI-Vorlage an, die auf demselben MCP-Server registriert ist, oder lass
template weg, um die aktuelle Vorlage zu öffnen:
await window.openai.requestModal({
template: "ui://widget/checkout.html",
});
Datei-APIs
ChatGPT unterstützt Hilfsfunktionen zum Hoch- und Herunterladen von Dateien als optionale Erweiterungen
von window.openai.
| API | Zweck | Hinweise |
|---|---|---|
window.openai.uploadFile(file, { library?: boolean }) | Lade eine von der nutzenden Person ausgewählte Datei hoch und erhalte eine fileId. | Übergib { library: true }, um die hochgeladene Datei auch in der ChatGPT-Dateibibliothek der nutzenden Person zu speichern, sofern diese ihr zur Verfügung steht. |
window.openai.selectFiles() | Öffne den Auswahldialog der Dateibibliothek für vorhandene Dateien. | Gibt [{ fileId, fileName, mimeType }] zurück. Prüfe, ob diese Hilfsfunktion verfügbar ist, da die Dateibibliothek möglicherweise nicht allen zur Verfügung steht. |
window.openai.getFileDownloadUrl({ fileId }) | Eine temporäre Download-URL für eine Datei anfordern. | Funktioniert für Dateien, die vom Widget hochgeladen, aus der Dateibibliothek ausgewählt, über Dateiparameter übergeben oder über Dateireferenzen eines Werkzeugs zurückgegeben wurden. |
Die ChatGPT-Dateibibliothek ist optional und steht möglicherweise nicht allen zur Verfügung.
Wenn die Hilfsfunktion verfügbar ist, sind die von window.openai.selectFiles() zurückgegebenen Dateien bereits für
das aktuelle Plug-in autorisiert. Verwende die zurückgegebene fileId mit
window.openai.getFileDownloadUrl({ fileId }) oder in einer Werkzeugeingabe, die
Dateiparameter verwendet.
Eine von der nutzenden Person ausgewählte Datei hochladen:
const { fileId } = await window.openai.uploadFile(file, {
library: true,
});
Dateien auswählen, die die nutzende Person bereits in ChatGPT hochgeladen hat:
if (window.openai?.selectFiles) {
const files = await window.openai.selectFiles();
// [{ fileId, fileName, mimeType }]
}
Prüfe, ob window.openai.selectFiles verfügbar ist, und greife auf
window.openai.uploadFile zurück, wenn die Dateibibliothek nicht verfügbar ist.
Eine temporäre Download-URL anfordern:
const { downloadUrl } = await window.openai.getFileDownloadUrl({ fileId });
Dateieingaben definieren
Damit ChatGPT Dateien an ein Werkzeug übergeben kann, liste jede Dateieingabe auf oberster Ebene in
_meta["openai/fileParams"] auf. Jedes aufgeführte Feld muss ein Dateiobjekt oder
ein Array von Dateiobjekten ergeben.
Jedes Schema für Dateiobjekte muss alle vier unterstützten Eigenschaften deklarieren:
| Eigenschaft | Typ | In properties deklarieren | In required aufnehmen |
|---|---|---|---|
download_url | string | Ja | Ja |
file_id | string | Ja | Ja |
mime_type | string | Ja | Nein |
file_name | string | Ja | Nein |
mime_type und file_name sind optionale Werte, aber du musst ihre
Eigenschaften im Schema deklarieren. Beim Schritt Werkzeuge scannen und beim Einreichen des Plug-ins wird ein
Dateischema abgelehnt, wenn eine der vier Eigenschaften fehlt, wenn
download_url und file_id nicht beide als erforderlich markiert sind, wenn eine der beiden optionalen Eigenschaften als erforderlich markiert ist oder
wenn eine andere Eigenschaft als download_url oder file_id erforderlich ist. Du kannst
zusätzliche optionale Eigenschaften deklarieren.
Dieser vollständige Werkzeugdeskriptor akzeptiert eine erforderliche Dateieingabe:
{
"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"]
}
}
Um mehrere Dateien zu akzeptieren, definiere das Feld auf oberster Ebene als Array und verwende
dasselbe Schema für Dateiobjekte in items. Das Werkzeug kann das Dateifeld auf oberster Ebene
unabhängig von den erforderlichen Eigenschaften innerhalb der einzelnen Dateiobjekte als erforderlich festlegen.
Zur Laufzeit übergibt ChatGPT Dateiwerte mit Feldnamen in Snake Case:
{
"download_url": "https://...",
"file_id": "file_...",
"mime_type": "image/png",
"file_name": "input.png"
}
ChatGPT übergibt immer download_url und file_id; mime_type
und file_name können fehlen. Verwende file_id als Wert für fileId in
window.openai.getFileDownloadUrl({ fileId }), wenn ein Widget eine neue
temporäre Download-URL benötigt.
Verwende beim dauerhaften Speichern des Widget-Zustands die strukturierte Form (modelContent, privateContent, imageIds), wenn das Modell in nachfolgenden Gesprächsrunden Bild-IDs sehen soll.
Vom Host unterstützte Navigation
Die Sandbox-Laufzeitumgebung spiegelt den Navigationsverlauf aus dem iframe in die ChatGPT-Oberfläche. Verwende Standard-Routing-APIs wie React Router. Der Host hält dann seine Navigationselemente mit deiner Benutzeroberfläche synchron.
Router-Setup mit BrowserRouter von React Router:
export default function PizzaListRouter() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<PizzaListPlugin />}>
<Route path="place/:placeId" element={<PizzaListPlugin />} />
</Route>
</Routes>
</BrowserRouter>
);
}
Programmgesteuerte Navigation:
const navigate = useNavigate();
function openDetails(placeId: string) {
navigate(`place/${placeId}`, { replace: false });
}
function closeDetails() {
navigate("..", { replace: true });
}
Parameter des Werkzeugdeskriptors
Eine Werkzeugbeschreibung sollte standardmäßig die hier aufgeführten Felder enthalten.
Deklariere outputSchema für jedes Werkzeug, das structuredContent zurückgibt.
Das Schema sollte das vom Werkzeug zurückgegebene Objekt exakt beschreiben, damit Clients
die Ergebnisse validieren und das Modell darauf aufbauend weitere Werkzeugaufrufe planen kann.
_meta-Felder im Werkzeugdeskriptor
Verwende diese _meta-Felder im Werkzeugdeskriptor. Verwende vorzugsweise den im MCP Apps-Standard
definierten Schlüssel _meta.ui.resourceUri, um ein Werkzeug mit einer UI-Vorlage zu verknüpfen. ChatGPT unterstützt
OpenAI-spezifische Metadaten für Kompatibilität und optionale Erweiterungen.
| Schlüssel | Position | Typ | Beschränkungen | Zweck |
|---|---|---|---|---|
_meta["securitySchemes"] | Werkzeugdeskriptor | array | Keine | Spiegelung zur Abwärtskompatibilität für Clients, die nur _meta lesen. |
_meta.ui.resourceUri | Werkzeugdeskriptor | string (URI) | Keine | Standard-Ressourcen-URI für die UI-Vorlage. |
_meta.ui.visibility | Werkzeugdeskriptor | string[] | Standardwert: ["model", "app"] | Steuert, ob ein Werkzeug dem Modell, der Benutzeroberfläche oder beiden zur Verfügung steht. Der Wert app ist die Kennung für die Benutzeroberfläche im MCP Apps-Protokoll. |
_meta["openai/outputTemplate"] | Werkzeugdeskriptor | string (URI) | Keine | OpenAI-spezifischer optionaler Kompatibilitätsalias für _meta.ui.resourceUri in ChatGPT. |
_meta["openai/profile"] | Werkzeugdeskriptor | boolean | Optional; nur true kennzeichnet ein Profil-Tool | Kennzeichnet das authentifizierte Tool ohne Schreibzugriff, das das aktuelle Profil zurückgibt. Implementiere es, damit Nutzende mehrere verbundene Konten leichter erkennen und verwalten können. Nutzende können auch ohne dieses Tool mehrere Konten verbinden. Siehe Unterstützung mehrerer Konten. |
_meta["openai/widgetAccessible"] | Tool-Deskriptor | boolean | Standardwert: false | OpenAI-spezifisches Kompatibilitätsfeld, das bestehende UI-Integrationen verwenden; verwende vorzugsweise _meta.ui.visibility + tools/call. |
_meta["openai/visibility"] | Werkzeugdeskriptor | string | public (Standard) oder private | OpenAI-spezifisches Kompatibilitätsfeld, das bestehende UI-Integrationen verwenden. Verwende bevorzugt _meta.ui.visibility. |
_meta["openai/toolInvocation/invoking"] | Tool-Deskriptor | string | ≤ 64 Zeichen | Kurzer Statustext während der Ausführung des Tools. |
_meta["openai/toolInvocation/invoked"] | Tool-Deskriptor | string | ≤ 64 Zeichen | Kurzer Statustext nach Abschluss der Tool-Ausführung. |
_meta["openai/fileParams"] | Tool-Deskriptor | string[] | Keine | Liste der Eingabefelder auf oberster Ebene, die Dateien darstellen. Jedes Feld erhält { download_url, file_id, mime_type?, file_name? }. |
Beispiel:
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.` }],
};
}
);
Annotationen
Um ein Tool als „ohne Schreibzugriff“ zu kennzeichnen, verwende die folgenden
Felder von
ToolAnnotations
im Tool-Deskriptor:
| Schlüssel | Typ | Erforderlich | Hinweise |
|---|---|---|---|
readOnlyHint | boolean | Erforderlich | Gib an, dass das Tool nur Informationen abruft oder berechnet und außerhalb der Unterhaltung keine Daten erstellt, aktualisiert, löscht oder sendet. |
destructiveHint | boolean | Erforderlich | Gib an, dass das Tool Nutzerdaten löschen oder überschreiben kann, damit der Host weiß, dass er zuerst eine ausdrückliche Genehmigung einholen muss. |
openWorldHint | boolean | Erforderlich | Gib an, dass das Tool auf das öffentliche Internet oder einen nicht klar abgegrenzten Kreis externer Stellen zugreift. Das umfasst auch Aktionen ohne Schreibzugriff wie die Websuche. Ein abgegrenztes privates Konto oder ein abgegrenzter privater Workspace gilt nicht allein deshalb als offene Umgebung, weil es beziehungsweise er extern gehostet wird. |
idempotentHint | boolean | Optional | Gib an, dass ein erneuter Aufruf des Tools mit denselben Argumenten keine zusätzlichen Auswirkungen auf seine Umgebung hat. |
Diese Hinweise beeinflussen nur, wie ChatGPT oder Codex den Tool-Aufruf gegenüber den Nutzenden darstellt. Server müssen weiterhin ihre eigene Autorisierungslogik durchsetzen.
Beispiel:
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() },
})
);
_meta-Felder der Komponentenressource
Setze diese Schlüssel in der Ressourcenvorlage, die deine Komponente bereitstellt (registerResource). Sie helfen ChatGPT, den gerenderten iframe zu beschreiben und darzustellen, ohne Metadaten an andere Clients weiterzugeben.
| Schlüssel | Position | Typ | Zweck |
|---|---|---|---|
_meta.ui.prefersBorder | Ressourceninhalte | boolean | Gib an, dass die Komponente innerhalb einer umrandeten Karte dargestellt werden soll, sofern dies unterstützt wird. |
_meta.ui.csp | Ressourceninhalte | object | Bevorzugter Ort für die standardmäßigen CSP-Metadatenfelder des Widgets: connectDomains, resourceDomains und optional frameDomains. |
_meta.ui.domain | Ressourceninhalte | string (Origin) | Eigene Origin für gehostete Komponenten (beim Einreichen eines Plug-ins mit UI erforderlich; muss für jedes Plug-in eindeutig sein). Der Standardwert ist https://web-sandbox.oaiusercontent.com. |
_meta["openai/widgetDescription"] | Ressourceninhalte | string | Für Menschen lesbare Zusammenfassung, die dem Modell beim Laden der Komponente bereitgestellt wird und überflüssige Erläuterungen des Assistenten reduziert. |
_meta["openai/widgetPrefersBorder"] | Ressourceninhalte | boolean | OpenAI-spezifischer Kompatibilitätsalias für _meta.ui.prefersBorder in ChatGPT. |
_meta["openai/widgetCSP"] | Ressourceninhalte | object | Älterer ChatGPT-Kompatibilitätsschlüssel für CSP-Metadaten des Widgets. Die standardmäßigen CSP-Felder werden durch _meta.ui.csp ersetzt, aber redirect_domains ist weiterhin für vertrauenswürdige Ziele von openExternal erforderlich. |
_meta["openai/widgetDomain"] | Ressourceninhalte | string (Origin) | OpenAI-spezifischer Kompatibilitätsalias für _meta.ui.domain in ChatGPT. |
ChatGPT unterstützt den älteren Kompatibilitätsschlüssel _meta["openai/widgetCSP"] mit den folgenden Feldnamen in snake_case:
connect_domains:string[]resource_domains:string[]frame_domains?:string[]redirect_domains?:string[]. ChatGPT-Erweiterung für Weiterleitungsziele vonwindow.openai.openExternal.
Für neue Benutzeroberflächen wird im Allgemeinen das standardisierte Objekt _meta.ui.csp empfohlen. Es unterstützt:
connectDomains:string[]. Domains, die das Widget über fetch/XHR kontaktieren darf.resourceDomains:string[]. Domains für statische Ressourcen (Bilder, Schriftarten, Skripte, Styles).frameDomains?:string[]. Optionale Liste der für iframe-Einbettungen zulässigen Origins. Standardmäßig können Widgets keine untergeordneten Frames rendern. Plug-ins können gemäß der iframe-Richtlinie Inhalte ihrer eigenen Domain einbetten, darunter vorhandene Editoren und Verwaltungsoberflächen. Bei der Einreichung ist eine Begründung erforderlich. Die Verwendung von iframes kann ein zusätzliches Review erfordern oder die Genehmigung verzögern.
Allerdings unterstützt _meta.ui.csp kein redirect_domains für Links über window.openai.openExternal(...). Um Weiterleitungsziele auf die Zulassungsliste zu setzen, musst du weiterhin _meta["openai/widgetCSP"].redirect_domains festlegen.
Tool-Ergebnisse
Tool-Ergebnisse können die folgenden Felder enthalten. Besonders relevant sind:
| Schlüssel | Typ | Erforderlich | Hinweise |
|---|---|---|---|
structuredContent | object | Optional | Wird dem Modell und der Komponente bereitgestellt. Muss dem deklarierten outputSchema entsprechen, sofern angegeben. |
content | string oder Content[] | Optional | Wird dem Modell und der Komponente bereitgestellt. |
_meta | object | Optional | Wird nur an die Komponente übermittelt. Für das Modell nicht sichtbar. |
Nur structuredContent und content erscheinen im Gesprächsprotokoll. Der Host leitet _meta an die Komponente weiter, damit du die Benutzeroberfläche mit Daten versorgen kannst, ohne sie dem Modell offenzulegen.
Vom Host bereitgestellte Metadaten zu Tool-Ergebnissen:
| Schlüssel | Position | Typ | Zweck |
|---|---|---|---|
_meta["openai/widgetSessionId"] | _meta im Tool-Ergebnis (vom Host) | string | Stabile ID der aktuell gemounteten Widget-Instanz. Verwende sie, um Logs und Tool-Aufrufe einander zuzuordnen, bis das Widget ausgehängt wird. |
Beispiel:
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])
),
},
};
}
);
Tool-Ergebnis mit Fehler
Um einen Fehler im Tool-Ergebnis zurückzugeben, verwende den folgenden Schlüssel in _meta:
| Schlüssel | Zweck | Typ | Hinweise |
|---|---|---|---|
_meta["mcp/www_authenticate"] | Fehlerergebnis | string oder string[] | WWW-Authenticate-Authentifizierungsaufforderungen gemäß RFC 7235 zum Auslösen von OAuth. |
Vom Client bereitgestellte _meta-Felder
| Schlüssel | Bereitgestellt bei | Typ | Zweck |
|---|---|---|---|
_meta["openai/locale"] | Initialisierung + Tool-Aufrufe | string (BCP 47) | Angefordertes Gebietsschema (ältere Clients senden möglicherweise _meta["webplus/i18n"]). |
_meta["openai/userAgent"] | Tool-Aufrufe | string | Optionale, nach Möglichkeit bereitgestellte Angabe zum User Agent für Analysen oder Formatierung. |
_meta["openai/userLocation"] | Tool-Aufrufe | object | Ungefähre Standortangabe (city, region, country, timezone, longitude, latitude). |
_meta["openai/subject"] | Tool-Aufrufe | string | Anonymisierte Nutzer-ID, die zur Durchsetzung von Ratenlimits und zur Identifizierung an MCP-Server gesendet wird |
_meta["openai/session"] | Tool-Aufrufe | string | Anonymisierte Gesprächs-ID, um Tool-Aufrufe innerhalb derselben ChatGPT-Sitzung einander zuzuordnen. |
_meta["openai/organization"] | Tool-Aufrufe | string | Anonymisierte Organisations-ID, die der aktuellen ChatGPT-Organisation zugeordnet ist, sofern verfügbar. |
Die während der Ausführung übermittelten Werte _meta["openai/userAgent"] und _meta["openai/userLocation"] dienen nur als Hinweise. Server sollten sich bei Autorisierungsentscheidungen niemals darauf verlassen und müssen auch ohne diese Angaben funktionieren. Behandle _meta["openai/userAgent"] als optionale Metadaten, die nach Möglichkeit bereitgestellt werden, und nicht als zuverlässige Möglichkeit, die aufrufende Host-Oberfläche zu erkennen.
Beispiel:
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 },
};
}
);