1. Tool als Vertrag: Was genau beschreiben wir
Wenn Sie ein Tool auf dem MCP‑Server registrieren, beschreiben Sie es mit einem kleinen Objekt. Die vereinfachte Struktur für das TypeScript‑SDK sieht so aus:
server.registerTool(
"suggest_gifts",
{
title: "Suggest gifts",
description: "Wählt Geschenke anhand des Profils des Empfängers aus.",
inputSchema: {
type: "object",
// hier tauchen wir jetzt tiefer ein
},
},
async ({ input }) => {
// Ihr Code
}
);
Das Modell weiß nicht, was im Handler async ({ input }) => { ... } passiert. Für es gibt es nur drei Dinge:
- name/title — wie das Tool heißt.
- description — wann es sinnvoll ist, es zu verwenden.
- inputSchema — welche Argumente übergeben werden müssen und in welchem Format.
Alles, was wir in dieser Vorlesung tun, betrifft Punkt 3 (und ein wenig die Metadaten _meta/annotations, über die wir später sprechen).
Wichtig zu verstehen: JSON Schema im Kontext der ChatGPT App ist kein langweiliger Validator, sondern Teil des Prompts für das Modell. Das Modell liest die description der Felder tatsächlich, versteht, was enum ist, bemerkt minItems, format usw.
Das heißt, Sie schützen nicht nur das Backend vor fehlerhaften Daten, sondern erklären dem KI‑Modell, wie es Ihre Funktion korrekt aufrufen soll.
2. Basis‑JSON‑Schema für das Tool suggest_gifts
Beginnen wir einfach. Angenommen, wir haben folgendes Szenario:
Der Nutzer schreibt:
„Wähle ein Geschenk für meinen Bruder, 25 Jahre, Budget 50–70 US‑Dollar, liebt Videospiele und Brettspiele“.
Das Tool suggest_gifts sollte ungefähr folgende Argumente annehmen:
- Alter des Empfängers;
- Beziehungsart (Bruder, Kollege, Partner usw.);
- minimales und maximales Budget;
- Liste der Interessen.
Beschreiben wir das als JSON Schema „ganz direkt“, ohne Zod, als reines Objekt:
const suggestGiftsInputSchema = {
type: "object",
properties: {
age: {
type: "integer",
minimum: 0,
maximum: 120,
description: "Alter des Geschenkempfängers in Jahren.",
},
relationship: {
type: "string",
enum: ["friend", "partner", "sibling", "colleague", "parent"],
description:
"Beziehungsart zum Empfänger: friend, partner, sibling (Bruder/Schwester), colleague, parent.",
},
minBudget: {
type: "number",
minimum: 0,
description: "Minimales Budget in der Währung des Nutzers.",
},
maxBudget: {
type: "number",
minimum: 0,
description: "Maximales Budget in der Währung des Nutzers.",
},
interests: {
type: "array",
items: {
type: "string",
description:
"Kurzer Name eines Interesses, z. B.: videogames, boardgames, books.",
},
minItems: 1,
description: "Liste der Interessen des Empfängers.",
},
},
required: ["relationship", "maxBudget"],
};
Einige wichtige Punkte, die man hier gleich ansprechen sollte.
Erstens: description der Felder. In einer gewöhnlichen API könnten Sie sie auch weglassen – der Frontend‑Entwickler liest ohnehin Swagger und versteht es. Hier ist der „Client“ jedoch das Modell, das versucht, aus Name und Beschreibung den Sinn abzuleiten. Je klarer Sie sagen: „Alter in Jahren“, „Budget in der Währung des Nutzers“, „enum mit festen Werten“, desto weniger seltsame Argumente sehen Sie zur Laufzeit.
Zweitens ist enum eines der mächtigsten Steuerungsmittel für das Modell. Wenn Sie dem Modell erlauben, in relationship beliebige Strings zu schreiben, bekommen Sie „bro“, „girlfriend“, „bestie“, „teammate“ und noch Kreativeres. Wenn Sie ein enum vorgeben, wird das Modell mit sehr hoher Wahrscheinlichkeit nur aus diesen Werten wählen. Das reduziert „Halluzinationen“ in den Argumenten direkt.
Drittens muss nicht alles required sein. Zum Beispiel kann age optional sein: Wenn der Nutzer es nicht angegeben hat, wird das Modell kein „ungefähres Alter“ aus der Luft erfinden (sofern Sie die Beschreibung entsprechend formulieren). Hier beginnt die Kunst: die Balance zwischen Flexibilität und Strenge.
Nun verwenden wir dieses Schema bei der Registrierung des Tools:
server.registerTool(
"suggest_gifts",
{
title: "Suggest gifts",
description:
"Wählt Geschenkideen nach Budget, Beziehungsart und Interessen des Empfängers aus.",
inputSchema: suggestGiftsInputSchema,
},
async ({ input }) => {
// hier entspricht input bereits ungefähr dem Schema
// ...
}
);
Ein solches „manuelles“ Objekt eignet sich gut für schnelle Experimente. Mit wachsender Anwendung wird es jedoch zu einer eigenen Welt, die leicht von Ihren TypeScript‑Typen abdriftet. Später kommen wir auf dieses Problem zurück und schauen, wie man es mit Zod und der Generierung von JSON Schema aus Typen löst.
3. JSON Schema als Prompt: wie man description schreibt, damit das Modell nicht leidet
Formal geht es bei JSON Schema um Validierung. Informell, in der LLM‑Welt, ist es außerdem ein strukturierter Prompt. Einige praktische Regeln:
- Das Feld description sollte die Frage beantworten „Was gehört hier hinein und in welchem Format?“.
Eine Formulierung wie „Datum“ hilft nicht. Die Formulierung „ISO‑8601‑Datum im Format YYYY-MM-DD, zum Beispiel "2025-02-14"“ hilft hingegen enorm. - Wenn ein Feld mit Geld zu tun hat – geben Sie die Einheiten an.
Schreiben Sie besser explizit „Betrag in der Währung des Nutzers“ oder „Betrag in US‑Dollar“. Andernfalls könnte das Modell ehrlich 50 schreiben, und Sie rätseln, ob das 50 Yen oder 50 Euro sind. - String‑„Kategorien“ macht man fast immer besser mit enum.
Wenn ein Feld eine String‑„Kategorie“ ist, verwenden Sie besser ein enum und beschreiben Sie jeden Wert in der description des Tools. Zum Beispiel kann man für relationship in der Tool‑Beschreibung schreiben: „relationship: einer von friend (Freund), partner (romantischer Partner), sibling (Bruder oder Schwester), colleague (Arbeitskollege), parent (Elternteil). Erfinde keine anderen Werte.“ - Für Arrays ist es nützlich, minItems zu setzen und zu erklären, was für eine Liste das ist.
Wenn ein Feld ein Array ist, geben Sie minItems an und erklären Sie kurz, was genau diese Liste ist. Zum Beispiel ist interests nicht eine „Beschreibung einer Person in Prosa“, sondern „eine Menge kurzer Tags“.
Das klingt etwas pedantisch, aber in der Praxis ist der Unterschied zwischen „es gibt Beschreibungen“ und „es gibt keine Beschreibungen“ der Unterschied zwischen einer stabilen Anwendung und der ewigen Lotterie „Was schickt das Modell heute?“
Insight
MCP‑Tools haben strikte Größenbeschränkungen — und genau die sind oft die Ursache für „mysteriöse“ Abstürze, seltsame Fehler und dafür, dass der Assistent Ihre Tools plötzlich nicht mehr sieht.
Die zentrale Regel ist einfach: Das Tool muss vollständig in ~4 KB JSON passen. Das betrifft nicht nur den Text der description, sondern die gesamte Struktur:
- Beschreibung des Tools,
- Schemas der Argumente (inputSchema),
- verschachtelte Objekte und enum,
- _meta und Annotations.
Wenn Ihr Tool zu groß wird, verhält sich die Plattform unvorhersehbar: Es tauchen Fehler wie "Tool description is too long", "Schema validation failed", "Manifest exceeds size limits" auf, und manchmal lädt ChatGPT das Tool einfach nicht oder „vergisst“ dessen Existenz.
Empfehlung: Halten Sie description im Rahmen von 1000–2000 Zeichen, und das gesamte Tool — im „sicheren“ Bereich von ~4 KB. Wenn die Beschreibung zu lang wird, ist das fast immer ein Zeichen dafür, dass das Tool zu viele Dinge auf einmal macht. Einzelne Tools sollten schmal und sehr präzise sein — so versteht das Modell ihre Grenzen zuverlässiger und macht seltener Fehler bei den Eingabedaten.
4. TypeScript und Zod: Eine einzige Quelle der Wahrheit statt zwei
Das manuelle Schreiben des JSON‑Schemas ist für TypeScript‑Entwickler schmerzhaft. Man muss zwei parallele Welten pflegen:
- Typen im TS‑Code;
- JSON Schema für das Modell.
Mit dem Wachstum der Anwendung driften sie auseinander. Heute ändern Sie ein Feld im TypeScript‑Typ, morgen vergessen Sie, das Schema zu aktualisieren — und eine Woche später fangen Sie einen Crash im Prod ein.
Der de‑facto Standardansatz in der TS‑Welt ist — Zod zu verwenden und Zod -> JSON Schema zu konvertieren.
Installieren Sie die Abhängigkeiten (falls noch nicht geschehen):
npm install zod zod-to-json-schema
Beschreiben wir das Eingabeschema für suggest_gifts mit Zod:
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
const SuggestGiftsInputZod = z.object({
age: z
.number()
.int()
.min(0)
.max(120)
.describe("Alter des Geschenkempfängers in Jahren."),
relationship: z
.enum(["friend", "partner", "sibling", "colleague", "parent"])
.describe(
"Beziehungsart: friend (Freund), partner (Partner), sibling (Bruder/Schwester), colleague (Kollege), parent (Elternteil)."
),
minBudget: z
.number()
.min(0)
.optional()
.describe("Minimales Budget in der Währung des Nutzers."),
maxBudget: z
.number()
.min(0)
.describe("Maximales Budget in der Währung des Nutzers."),
interests: z
.array(
z
.string()
.min(1)
.describe(
"Kurzer Interesse‑Tag, z. B.: videogames, boardgames, books."
)
)
.min(1)
.describe("Liste der Interessen des Empfängers."),
});
Jetzt haben Sie:
- Runtime‑Validierung: SuggestGiftsInputZod.parse(input);
- TypeScript‑Typ: type SuggestGiftsInput = z.infer<typeof SuggestGiftsInputZod>;
- JSON Schema für das Modell: zodToJsonSchema(SuggestGiftsInputZod).
Verwenden wir das bei der Registrierung des Tools:
type SuggestGiftsInput = z.infer<typeof SuggestGiftsInputZod>;
const suggestGiftsInputSchemaJson = zodToJsonSchema(
SuggestGiftsInputZod,
"SuggestGiftsInput"
);
server.registerTool(
"suggest_gifts",
{
title: "Suggest gifts",
description:
"Wählt Geschenkideen nach Budget, Beziehungsart und Interessen des Empfängers aus.",
inputSchema: suggestGiftsInputSchemaJson,
},
async ({ input }) => {
// hier kann man input zusätzlich mit Zod prüfen:
const args = SuggestGiftsInputZod.parse(input) as SuggestGiftsInput;
// anschließend arbeiten wir mit dem typisierten args
}
);
Dieser Ansatz liefert genau die Single source of truth — eine einzige Quelle der Wahrheit: Sie beschreiben das Schema einmal, und TypeScript‑Typ sowie JSON Schema werden automatisch generiert.
In der realen Welt fügen Sie zusätzlich Tests hinzu, die prüfen, dass zodToJsonSchema die erwartete Struktur ausgibt, aber das ist Thema des Moduls über Tests.
Insight: ChatGPT kommt mit optionalen Parametern schlecht zurecht
Eine der schmerzhaftesten Erfahrungen in der Praxis: Sobald Sie optional-Felder in Tool‑Schemata aktiv nutzen, sinkt die Qualität der Tool‑Aufrufe spürbar. Das Modell „versteht“ in der Theorie, was optionale Parameter sind, aber in der Praxis werden sie meist einfach gar nicht gesendet — selbst wenn Sie sie aus Geschäftssicht dringend brauchen.
Im Response API hat man dieses Problem elegant gelöst: Dort wurden optionale Felder einfach entfernt — alle Tool‑Parameter müssen als required deklariert sein. Doch das Problem bleibt: Die Idee „Ich markiere die Hälfte der Felder als optional, und das Modell entscheidet selbst, was es füllt“ zerschellt an der Realität: Meistens sendet es einfach nichts.
5. Wo die „Schema“ endet und das „Interface‑Design“ beginnt
Bisher haben wir ständig über inputSchema gesprochen — also darüber, welche Argumente das Modell für den Start des Tools generieren soll. Aber nach dem Tool‑Aufruf ist noch nicht Schluss: Das Ergebnis muss noch im UI gerendert werden.
Hier ist es hilfreich, zwei Ebenen zu trennen:
- Das Schema für das Tool beschreibt die Eingabeargumente, die das Modell generieren soll. Das ist immer JSON, das im Raum von MCP / Tool‑Call lebt.
- Die UI‑Komponente (Widget) liest toolOutput.structuredContent und baut darauf das Interface. Das Format von structuredContent entwerfen Sie ebenfalls selbst, aber das ist nicht das JSON Schema für das Modell (auch wenn Sie es für sich formalisieren können).
Manchmal versuchen Entwickler, mit einem JSON‑Objekt zwei Fliegen mit einer Klappe zu schlagen — sowohl die Eingaben für das Modell als auch das Datenformat für das UI zu kombinieren. Das endet selten gut. Bequemer ist es, zu trennen:
- inputSchema — wofür das Modell die Eingaben braucht, um das Tool zu starten;
- structuredContent — wofür das UI die Daten braucht, um das Ergebnis zu rendern.
Zum Beispiel enthält inputSchema für suggest_gifts keine id von Geschenken. Und structuredContent enthält dagegen eine Liste von Karten mit id, title, price, einem Kauf‑Link usw.
6. Annotations und _meta: Wie man UX und Sicherheit beeinflusst
Neben dem eigentlichen Parameterschema und der Struktur der Antwort gibt es noch eine weitere Ebene — wie die Plattform das Tool behandelt und dem Nutzer anzeigt. Dafür sind Metadaten und Annotations zuständig.
Zusätzlich zu den Standardfeldern title, description, inputSchema kann ein Tool weitere Metadaten und Annotations haben. Im Apps SDK und MCP leben einige dieser Dinge in _meta (z. B. securitySchemes), andere — in speziellen Feldern wie OpenAI‑spezifischen Hints wie readOnlyHint und destructiveHint.
Wichtig: Diese Annotations ändern das JSON Schema nicht, beeinflussen aber, wie ChatGPT das Tool dem Nutzer zeigt und wie es dessen Aufruf bewertet.
Beispiel: readOnlyHint und destructiveHint
Angenommen, Sie haben zwei Tools:
- list_gifts — einfach eine Geschenkeliste abrufen (sicher);
- create_order — eine Bestellung anlegen (potenziell gefährlich: Geld, Adresse, alles ernst).
Sie können sie ungefähr so kennzeichnen (Pseudocode):
server.registerTool(
"list_gifts",
{
title: "List gift suggestions",
description: "Ruft eine Liste verfügbarer Geschenke anhand der angegebenen Filter ab.",
inputSchema: listGiftsInputSchema,
_meta: {
readOnlyHint: true,
},
},
async ({ input }) => { /* ... */ }
);
server.registerTool(
"create_order",
{
title: "Create gift order",
description:
"Erstellt eine Bestellung für ein konkretes Geschenk im Namen des Nutzers. Nur nach ausdrücklicher Bestätigung verwenden.",
inputSchema: createOrderInputSchema,
_meta: {
destructiveHint: true,
},
},
async ({ input }) => { /* ... */ }
);
Die Semantik ist hier folgende. readOnlyHint signalisiert ChatGPT, dass das Tool nichts verändert und sicher ist; Modell und UI können es freier aufrufen. destructiveHint sagt, dass das Tool irreversible oder kritische Aktionen ausführt, daher erscheinen dem Nutzer häufiger Bestätigungen, und das Modell ist vorsichtiger.
In Ihrer Gift‑App ist suggest_gifts eindeutig read‑only, wohingegen Tools zur Auftragserstellung, zum Abbuchen von Geld und zum Ändern von Nutzerdaten besser als potenziell destructive markiert werden.
openWorldHint und ähnliche Felder
In manchen Fällen möchten Sie dem Modell signalisieren, dass das Tool in einer „offenen Welt“ arbeitet, seine Ergebnisse also nicht erschöpfend sind. Zum Beispiel wird search_products niemals alle existierenden Produkte zurückgeben, sondern nur relevante.
Solche Annotations helfen dem Modell, keine starken Schlüsse zu ziehen wie „Wenn ein Produkt in search_products nicht gefunden wurde, existiert es nicht“. Das ist ein feiner UX‑Punkt, aber in Produktions‑Apps ist der Unterschied deutlich spürbar.
_meta rund um die UI‑Darstellung
Wenn Ihr Tool ein Ergebnis zurückgibt, können Sie zusätzlich in _meta Einstellungen angeben, die das Widget beeinflussen. Zum Beispiel: Welche HTML‑Vorlage als Output‑Template genutzt werden soll, ob Rahmen nötig sind, welcher Text während des Aufrufs angezeigt wird usw.
Im offiziellen Beispiel registriert der Server das HTML‑Widget separat als MCP‑Ressource und verweist dann über _meta["openai/outputTemplate"] darauf.
server.registerTool(
"suggest_gifts",
{
title: "Suggest gifts",
description: "Wählt Geschenkideen aus.",
inputSchema: suggestGiftsInputSchemaJson,
_meta: {
"openai/outputTemplate": "ui://widget/gifts.html", // Dies ist die ID einer MCP‑Ressource: server.registerResource(...)
"openai/toolInvocation/invoking": "Suche Geschenke …", // Wird während der Suche angezeigt
"openai/toolInvocation/invoked": "Geschenkoptionen gefunden", // Wird angezeigt, wenn die Suche abgeschlossen ist
},
},
async ({ input }) => {
// ...
return {
content: [],
structuredContent: { items: gifts },
};
}
);
Damit beschreiben Sie an einem Ort:
- die Form der Eingabedaten für das Modell (inputSchema);
- wie das Tool im UI aussehen und sich verhalten wird (_meta).
7. Schema‑Design: Was man vom Modell verlangen sollte — und was nicht
Eine typische Falle ist, dem Modell die gesamte Arbeit aufzubürden. Zum Beispiel beschreiben Sie im inputSchema das Feld giftId und schreiben in die description: „UUID des Geschenks aus unserer Datenbank“. Das Modell wird natürlich ehrlich versuchen, eine UUID wie "0f21b5f0-5a3a-4d1b-8f0b-9f1a6e3c1234" zu generieren, nur existiert ein solches Geschenk bei Ihnen höchstwahrscheinlich nicht.
Gute Regel: Fordern Sie das Modell nicht auf, technische Identifikatoren und Daten zu generieren, die an Ihre interne Welt gekoppelt sind.
Stattdessen sollte man ein mehrstufiges Szenario bauen:
- suggest_gifts gibt eine Liste von Geschenken mit id, title, price usw. zurück;
- UI/Modell erlauben dem Nutzer, eine der vorgeschlagenen Optionen auszuwählen;
- create_order akzeptiert giftId aus dem bereits existierenden Satz.
Aus Sicht der Schemata bedeutet das:
- inputSchema der Tools, die „nach außen“ (zum Nutzer) schauen, beschreiben nur das, was ein Mensch sinnvoll eingeben kann: Suchparameter, Filter, Kriterien;
- inputSchema der Tools, die mit internen Entitäten arbeiten, stützen sich auf bereits bekannte id, statt das Modell zu bitten, sie zu erfinden.
Für Ihre Gift‑App bedeutet das, dass Sie in suggest_gifts das Modell nicht bitten, einen SKU‑Code zu „erfinden“, sondern nur die Abfrageparameter. Die SKUs selbst fügen Sie auf der Backend‑Seite hinzu, und das UI zeigt sie dem Nutzer.
Hinweis: SKU ist ein internationaler eindeutiger Produktcode. Beispiel "GFT-CHC-500-BS".
8. Kleiner Praxisblock: Wir setzen alles zusammen
Fassen wir an einem Ort alles zusammen, worüber wir oben gesprochen haben: Zod‑Schema, Generierung von JSON Schema, Registrierung des Tools mit _meta und Nutzung des Schemas in der Business‑Logik. Wir bauen ein minimales, aber zusammenhängendes Beispiel für die Gift‑App.
Zuerst Zod‑Schema und Typ:
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
const SuggestGiftsInputZod = z.object({
relationship: z
.enum(["friend", "partner", "sibling", "colleague", "parent"])
.describe("Beziehungsart zum Geschenkempfänger."),
maxBudget: z
.number()
.min(0)
.describe("Maximales Budget in der Währung des Nutzers."),
interests: z
.array(
z
.string()
.min(1)
.describe("Kurzer Interesse‑Tag, z. B.: videogames.")
)
.min(1)
.describe("Liste der Interessen des Empfängers."),
});
type SuggestGiftsInput = z.infer<typeof SuggestGiftsInputZod>;
const suggestGiftsInputSchemaJson = zodToJsonSchema(
SuggestGiftsInputZod,
"SuggestGiftsInput"
);
Als Nächstes — Registrierung des Tools mit _meta für das UI:
server.registerTool(
"suggest_gifts",
{
title: "Suggest gifts",
description:
"Verwende dieses Tool, wenn Geschenkideen nach Budget, Beziehung und Interessen ausgewählt werden sollen.",
inputSchema: suggestGiftsInputSchemaJson,
_meta: {
"openai/outputTemplate": "ui://widget/gifts.html",
"openai/toolInvocation/invoking": "Suche Geschenke …",
"openai/toolInvocation/invoked": "Geschenkoptionen gefunden",
readOnlyHint: true,
},
},
async ({ input }) => {
const args = SuggestGiftsInputZod.parse(input) as SuggestGiftsInput;
const gifts = await findGifts(args); // Ihre Business-Logik
return {
content: [],
structuredContent: {
items: gifts,
},
};
}
);
Irgendwo daneben haben Sie eine typisierte Business‑Funktion:
async function findGifts(input: SuggestGiftsInput) {
// hier können Sie input.relationship, input.maxBudget, input.interests verwenden
// und ein Array von Objekten vom Typ Gift zurückgeben
return [
{
id: "gift-1",
title: "Brettspiel zu Videospielen",
price: 45,
currency: "USD",
},
];
}
Auf der Widget‑Seite nehmen Sie dann window.openai.toolOutput.structuredContent.items und rendern Karten, aber dazu mehr in ein paar Lektionen.
9. Typische Fehler beim Beschreiben von Tools
Fehler Nr. 1: Zu allgemeine oder nichtssagende Feldbeschreibungen.
Wenn Sie description: "Datum" oder description: "Filterparameter" schreiben, erhält das Modell praktisch null nützliche Informationen. Das ist wie eine Dokumentation der Art „Die Methode macht etwas Wichtiges“. Verwenden Sie Beschreibungen, die die Fragen „Was gehört hier hinein?“ und „In welchem Format?“ beantworten. Zum Beispiel: „ISO‑8601‑Datum im Format YYYY-MM-DD, z. B. "2025-02-14"“ oder „Betrag in der Währung des Nutzers, Beispiel: 49.99“.
Fehler Nr. 2: Kein enum dort, wo es sich aufdrängt.
Oft sind Entwickler zu bequem, Strings in ein enum zu verwandeln, und lassen type: "string". Infolgedessen denkt sich das Modell eigene Werte aus, das Backend wundert sich, das UI bricht. Wenn Sie einen festen Satz von Optionen haben (relationship, Status‑Typen, Sortierarten) — fast immer lohnt es sich, ein enum zu machen und die möglichen Werte aufzulisten. Das erhöht die Vorhersagbarkeit der Tool‑Calls stark.
Fehler Nr. 3: Zwei Quellen der Wahrheit für Schema und Typen.
Klassiker: In TypeScript ändern Sie das Feld maxBudget zu priceMax, im JSON Schema vergessen Sie es. Das Modell sendet weiterhin maxBudget, der Code erwartet priceMax, alles fällt um. Solche Fehler entdeckt man oft erst im Prod. Daher ist es besser, von Anfang an Zod oder ein ähnliches Werkzeug zu nutzen, das sowohl Typ als auch JSON Schema aus einer Deklaration generiert.
Fehler Nr. 4: Das Modell zur Generierung interner Identifikatoren auffordern.
Felder wie userId, giftId, orderId, wenn Sie sie als „UUID des Nutzers in unserem System“ beschrieben haben, werden unweigerlich vom Modell mit ausgedachten Werten gefüllt. Selbst wenn Sie ein pattern für UUID hinzufügen, generiert das Modell lediglich „richtig aussehende“ UUIDs, die nichts bedeuten. Solche Felder füllen Sie besser im Backend anhand des Kontexts (Authentifizierung, vorheriger Tool‑Call), statt das Modell darum zu bitten.
Fehler Nr. 5: Riesige „Alleskönner“-Schemata für alle Fälle.
Manchmal möchte man ein einziges Tool do_everything mit einem riesigen Objekt bauen, die Hälfte der Felder nullable, die Hälfte optional. Das Modell geht darin unter. Besser ist es, die Funktionalität in mehrere Tools mit schmaleren und verständlicheren Schemata aufzuteilen: Eines ist für die Geschenksuche zuständig, ein anderes — für die Details eines konkreten Geschenks, ein drittes — für das Anlegen einer Bestellung.
Fehler Nr. 6: _meta und Annotations ignorieren.
Viele Entwickler begnügen sich mit name, description und inputSchema, und lassen _meta-Felder wie openai/outputTemplate sowie Hints wie destructiveHint außer Acht. Am Ende führen Tools, die „still“ gefährliche Aktionen ausführen, nicht zu Hinweisen und Bestätigungen im UI. Das verschlechtert das Vertrauen der Nutzer und schafft das Risiko unerwarteter Operationen. Nutzen Sie Annotations, um read‑only und gefährliche Tools klar zu markieren und freundliche Ausführungsstatus zu setzen.
Fehler Nr. 7: Keine Eingabevalidierung auf dem Server.
Selbst wenn JSON Schema und Zod scheinbar alles beschreiben, ist es riskant, sich nur auf das Modell zu verlassen. Manchmal kann das Modell teilweise valide Daten liefern oder Sie ändern das Schema und vergessen Geschäftsrestriktionen. Das Umhüllen des Handlers in try { parse } catch { ... } mit einer freundlichen Fehlermeldung gibt dem Modell die Chance, Argumente zu korrigieren — und Ihnen die Chance, nicht wegen eines unglücklichen Tool‑Calls den ganzen Service zu Fall zu bringen.
GO TO FULL VERSION