docs: refresh translations for recent English changes (#3379)

This commit is contained in:
Max 2026-08-24 19:29:54 +01:00 • committed by GitHub
commit c5d7d0b6d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 3149 additions and 1047 deletions

View file

@ -1,25 +1,25 @@
---
translation:
sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601]
sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5]
tool: 1
---
# Fehler behandeln {#handling-errors}
Ein Tool kann auf zwei Arten scheitern, und das SDK behandelt sie sehr unterschiedlich.
Ein Tool kann auf drei Arten scheitern, und das SDK behandelt jede anders.
Löse eine gewöhnliche Exception aus, und das **Modell** sieht sie. Löse `MCPError` aus, und das **Protokoll** sieht sie.
Löse `ToolError` aus, und das **Modell** sieht deine Meldung. Löse `MCPError` aus, und das **Protokoll** sieht sie. Löse irgendetwas anderes aus, und es ist ein Absturz: Das Modell erfährt nur, dass der Aufruf fehlgeschlagen ist, und dein Log bekommt den Traceback.
Auf dieser Seite geht es um die Wahl zwischen beiden.
Auf dieser Seite geht es um die Wahl.
## Ein Fehler, den das Modell beheben kann {#an-error-the-model-can-fix}
Nimm ein Tool, das etwas nachschlägt, und lass das Nachschlagen ins Leere laufen:
```python title="server.py" hl_lines="11-12"
```python title="server.py" hl_lines="2 12-13"
--8<-- "docs_src/handling_errors/tutorial001.py"
```
An diesen zwei Zeilen ist nichts MCP-Spezifisches. `get_author` löst einen schlichten `ValueError` aus, so wie es jede Python-Funktion täte.
Mit `ToolError` aus `mcp.server.mcpserver.exceptions` sagt ein Tool dem Modell, dass etwas schiefgegangen ist.
Ruf es mit einem Titel auf, der nicht im Katalog steht, und sieh dir das Ergebnis an:
@ -30,13 +30,15 @@ result.structured_content # None
```
* Der Request war **erfolgreich**. Es gibt ein Ergebnis; beim Aufrufer wurde nichts ausgelöst.
* `is_error` ist `True`, und die Meldung deiner Exception (mit dem Tool-Namen als Präfix) steht in `content` – genau dort, wo das Modell liest.
* `is_error` ist `True`, und deine Meldung (mit dem Tool-Namen als Präfix) steht in `content` – genau dort, wo das Modell liest.
* `structured_content` ist `None`. Ein fehlgeschlagener Aufruf hat keinen Rückgabewert, den man strukturieren könnte.
Das ist ein **Tool-Fehler**, und er ist der Standard für *jede* Exception, die dein Tool auslöst. Fast immer ist es auch genau das, was du willst.
Das ist ein **Tool-Fehler**, und fast immer ist es genau das, was du willst.
Das Modell ist es, das dein Tool aufruft. Es hat die Argumente gewählt. Ein Tool-Fehler ist also ein Zug im Gespräch: Das Modell liest *„No book titled 'Nothing' in the catalog.“*, merkt, dass es den Titel falsch geraten hat, und ruft erneut mit einem besseren auf. Du hast ein einziges `raise` geschrieben und einen sich selbst korrigierenden Agenten bekommen.
Auf dem Server hinterlässt `ToolError` eine einzige `INFO`-Zeile im Log, ohne Traceback. Du hast den Fehler kommen sehen, also gibt es nichts zu untersuchen.
!!! tip
Gib aus einem Tool nie eine Fehlermeldung per `return` zurück. Ein zurückgegebener String hat `is_error=False`;
für das Modell (und für jede Client-UI) sieht es also aus, als hätte das Tool funktioniert und dieser String
@ -44,7 +46,7 @@ Das Modell ist es, das dein Tool aufruft. Es hat die Argumente gewählt. Ein Too
## Ein Fehler, den das Modell nicht beheben kann {#an-error-the-model-cannot-fix}
Tausche jetzt `ValueError` gegen `MCPError`.
Tausche jetzt `ToolError` gegen `MCPError`.
```python title="server.py" hl_lines="1 3 14"
--8<-- "docs_src/handling_errors/tutorial002.py"
@ -77,10 +79,10 @@ Tausche jetzt `ValueError` gegen `MCPError`.
Die beiden Wege beantworten zwei verschiedene Fragen.
* **Löse irgendeine Exception aus** bei einem Fehlschlag der *Ausführung*: Das, was dein Tool versucht hat, hat nicht geklappt. Das Modell hat den Aufruf gewählt, also sollte das Modell die Folge sehen und die Chance bekommen, sich zu fangen. Ein falsch geschriebener Titel, eine vorgelagerte API mit Timeout, eine Zeile, die es nicht gibt: alles Tool-Fehler.
* **Löse `ToolError` aus** bei einem Fehlschlag der *Ausführung*: Das, was dein Tool versucht hat, hat nicht geklappt. Das Modell hat den Aufruf gewählt, also sollte das Modell die Folge sehen und die Chance bekommen, sich zu fangen. Ein falsch geschriebener Titel, eine vorgelagerte API mit Timeout, eine Zeile, die es nicht gibt: alles Tool-Fehler.
* **Löse `MCPError` aus**, wenn der *Request selbst* abgelehnt werden soll: Dem Client fehlt eine Capability, auf die dein Tool angewiesen ist, der Server ist nicht in einem Zustand, irgendwen zu bedienen, der Aufrufer hat einen erforderlichen Schritt übersprungen. Kein erneuter Versuch des Modells behebt irgendetwas davon, also bringt es nichts, ihm die Meldung zu geben.
Eine Frage entscheidet: **Hätte ein klügeres Modell das vermeiden können?** Ja -> gewöhnliche Exception. Nein -> `MCPError`.
Eine Frage entscheidet: **Hätte ein klügeres Modell das vermeiden können?** Ja -> `ToolError`. Nein -> `MCPError`.
Nach diesem Test hat die zweite Version von `get_author` die falsche Wahl getroffen: Ein besserer Titel behebt das Problem, also hätte das Modell die Meldung sehen sollen. Sie soll dir den Mechanismus zeigen, nicht ihn empfehlen.
@ -89,6 +91,25 @@ Nach diesem Test hat die zweite Version von `get_author` die falsche Wahl getrof
`data`-Payload entgegen. Was immer du hineinlegst, bekommt der Client: Das SDK leitet eine ausgelöste
`MCPError` wortwörtlich weiter, statt sie zu bereinigen.
## Jede andere Exception {#any-other-exception}
Nimm jetzt die Prüfung heraus und lass das Nachschlagen im Dictionary von selbst fehlschlagen:
```python title="server.py" hl_lines="11"
--8<-- "docs_src/handling_errors/tutorial004.py"
```
`CATALOG[title]` löst `KeyError` aus. Damit hast du nicht gerechnet, also behandelt das SDK es als Absturz:
```python
result.is_error # True
result.content # [TextContent(text="Error executing tool get_author")]
```
Der Aufruf gibt weiterhin `is_error=True` zurück, das Modell weiß also, dass er fehlgeschlagen ist, und kann weitermachen. Was es nicht bekommt, ist der Text der Exception: Ein `KeyError` aus deinem Code oder ein Stapel SQL von einem Treiber drei Bibliotheken tiefer kann das Innenleben deines Servers beschreiben, deshalb verlässt er den Server nie.
Stattdessen bekommst du ihn. Der Server loggt den Absturz auf `ERROR` mit dem vollständigen Traceback, als `Tool 'get_author' raised an unexpected exception`. Ein Produktions-Log auf `WARNING` bleibt deshalb still, solange nur `ToolError` ausgelöst wird, und meldet sich in dem Moment, in dem tatsächlich etwas kaputt ist.
## Eine Ressource, die es nicht gibt {#a-resource-that-doesnt-exist}
Ressourcen ziehen dieselbe Grenze und bringen für den häufigen Fall eine benannte Exception mit.
@ -109,7 +130,7 @@ Wenn sie das nicht kann, löse `ResourceNotFoundError` aus. Das SDK macht daraus
}
```
Beachte, dass es hier kein halbes Ergebnis mit `is_error=True` gibt. Das Lesen einer Ressource liefert entweder Inhalte oder schlägt fehl: Ressourcen haben nur den Protokollweg. Templates und alles Weitere zu Ressourcen stehen in **[Ressourcen](resources.md)**.
Beachte, dass es hier kein halbes Ergebnis mit `is_error=True` gibt. Das Lesen einer Ressource liefert entweder Inhalte oder schlägt fehl: Ressourcen haben nur den Protokollweg. `ResourceError` ist dasselbe für einen Fehlschlag, der nicht „nicht gefunden“ ist (`-32603`, deine Meldung), und beide sind eine einzige `INFO`-Zeile in deinem Log. Jede andere Exception außer `MCPError` ist ein Absturz: Der Client bekommt `-32603`, das nur den URI nennt, und der Traceback landet auf `ERROR` in deinem Log. Templates und alles Weitere zu Ressourcen stehen in **[Ressourcen](resources.md)**.
## Fehler, die du nie auslöst {#errors-you-never-raise}
@ -120,19 +141,21 @@ Schick `get_author` einen `title`, der kein String ist, und das SDK weist ihn an
Das bedeutet eine ganze Klasse von `raise`-Anweisungen, die du nicht schreibst: Validiere deine eigenen Type Hints nicht noch einmal.
!!! info
Alles auf dieser Seite ist das, was ein **Client** sieht, und der In-Memory-`Client`, mit dem du
Tests schreibst, sieht exakt dasselbe. Selbst `raise_exceptions=True` macht aus einem Tool-Fehler
keinen Traceback mehr: Bis dieses Flag greifen könnte, ist deine Exception längst das
Ergebnis mit `is_error=True`. Prüfe das Ergebnis mit Assertions. **[Testen](../get-started/testing.md)** beschreibt das Muster.
Alles, was ein **Client** auf dieser Seite sieht, sieht auch der In-Memory-`Client`, mit dem du
Tests schreibst. Selbst `raise_exceptions=True` reicht die Exception eines scheiternden
Tools nicht an den Aufrufer zurück: Bis dieses Flag greifen könnte, ist deine Exception längst
das Ergebnis mit `is_error=True`. Prüfe das Ergebnis mit Assertions. Brauchst du den Traceback eines Absturzes, steht er
im Log des Servers, und pytests `caplog` fängt ihn ein. **[Testen](../get-started/testing.md)** beschreibt das Muster.
## Zusammenfassung {#recap}
* Löse **irgendeine Exception** in einem Tool aus -> der Aufruf gibt `is_error=True` mit deiner Meldung in `content` zurück. Das Modell liest sie und kann es erneut versuchen. Das ist der Standard.
* Löse **`ToolError`** in einem Tool aus -> der Aufruf gibt `is_error=True` mit deiner Meldung in `content` zurück. Das Modell liest sie und kann es erneut versuchen.
* Löse **`MCPError`** aus -> der Aufruf selbst schlägt mit einem JSON-RPC-Fehler fehl. Das Modell sieht nichts; der Host kümmert sich darum. `code`, `message` und `data` kommen unverändert durch.
* Die entscheidende Frage: *Hätte ein klügeres Modell das vermeiden können?* Ja -> Exception. Nein -> `MCPError`.
* Die entscheidende Frage: *Hätte ein klügeres Modell das vermeiden können?* Ja -> `ToolError`. Nein -> `MCPError`.
* Jede **andere Exception** ist ein Absturz -> `is_error=True` mit nichts als `Error executing tool <name>` für das Modell und einem `ERROR`-Eintrag samt Traceback für dich.
* `ResourceNotFoundError` aus einem Ressourcen-Handler -> das `-32602` des Protokolls, mit dem URI in `data`.
* Ungültige Argumente werden anhand des Schemas abgewiesen, bevor deine Funktion läuft; dafür schreibst du kein `raise`.
* `from mcp import MCPError`; die Fehlercode-Konstanten kommen aus `mcp.types`.
* Imports: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError` und die Fehlercode-Konstanten aus `mcp.types`.
Fehler behandelt. Das ist alles, was ein Server *nach außen anbietet*. Was jeder Handler lesen und während der Ausführung zurück an den Client tun kann, ist der nächste Abschnitt: **[Im Handler](../handlers/index.md)**.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a]
sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3]
tool: 1
---
# Medien {#media}
@ -86,6 +86,24 @@ Eine Endung, die nicht erkannt wird, fällt auf `application/octet-stream` zurü
`Audio` aus MP3-Bytes, bekommt der Client `mime_type="audio/wav"` mitgeteilt und scheitert dann
folgerichtig am Dekodieren. Wenn du `data=` übergibst, übergib auch `format=`.
## Eine Ressource einbetten {#embedding-a-resource}
Ein Tool kann auch ein Dokument zurückgeben: etwas Text oder Bytes zusammen mit dem URI, unter dem es liegt, und einem MIME-Typ. Das ist eine **`EmbeddedResource`**, eine weitere Art von Content-Block. Anders als ein einfaches `str` sagt sie dem Client, was der Inhalt ist, sodass der Client ihn als Anhang anzeigen oder eine Ressource wiedererkennen kann, die er schon kennt.
```python title="server.py" hl_lines="7 14 16-18"
--8<-- "docs_src/media/tutorial005.py"
```
* `brand://guidelines` ist eine gewöhnliche Ressource (die behandelt **[Ressourcen](resources.md)**). Das Tool reicht dem Modell auf Anfrage dasselbe Dokument, und weil es `guidelines()` direkt aufruft, bleibt es bei einer einzigen maßgeblichen Quelle.
* `EmbeddedResource` und `TextResourceContents` kommen aus `mcp.types`. Einen Helfer wie für Bilder gibt es nicht: Der Block, den du baust, landet unverändert im Ergebnis, und es gibt kein `structured_content`.
* Verwende den URI, unter dem die Ressource registriert ist, damit ein Client erkennen kann, dass der Anhang und `brand://guidelines` dasselbe Dokument sind. Erlaubt ist jeder URI, registriert oder nicht.
```python
result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))]
```
Für binären Inhalt nimm statt `TextResourceContents` ein `BlobResourceContents(uri=..., mime_type=..., blob=...)`, mit den Bytes base64-kodiert in `blob`. Willst du nur einen Zeiger senden, den der Client später per `resources/read` lesen kann, gib stattdessen einen `ResourceLink(name=..., uri=...)` zurück; auch das ist ein Content-Block.
## Icons {#icons}
Ein `Icon` ist Metadaten, kein Inhalt. Es trägt das Bild nicht; es zeigt per URI auf eines, und ein Client kann es abrufen und neben dem Namen deines Servers, einem Tool, einer Ressource oder einem Prompt anzeigen.
@ -115,6 +133,7 @@ Die Icons eines Tools liegen auf dem `Tool`-Objekt aus `tools/list`, die einer R
* Gib ein `Image` oder `Audio` aus einem Tool zurück, und der Client empfängt einen `ImageContent`- bzw. `AudioContent`-Block: deine Bytes base64-kodiert, mit einem MIME-Typ.
* Baue eines aus einem `path=` und lass die Endung den MIME-Typ bestimmen, oder aus `data=` im Speicher plus einem expliziten `format=`.
* Gib eine `EmbeddedResource` zurück, um ein Dokument (Text oder ein base64-Blob, mit seinem URI und MIME-Typ) ins Ergebnis zu legen, oder einen `ResourceLink`, um nur den Zeiger zu senden.
* Medien-Ergebnisse tragen kein `structured_content` und kein Output-Schema.
* Ein `Icon` ist ein Zeiger: ein `src`-URI plus optional `mime_type`, `sizes` und `theme`.
* `icons=[...]` funktioniert auf dem Server, auf Tools, auf Ressourcen und auf Prompts, und Clients finden sie auf den passenden Objekten.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094]
sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd]
tool: 1
---
# Prompts {#prompts}
@ -140,10 +140,55 @@ Der `prompts/list`-Eintrag enthält jetzt alles, was ein Client braucht, um ein
```
!!! info
Wenn du **[Tools](tools.md)** gelesen hast, kennst du schon alles auf dieser Seite. Derselbe Dekorator, derselbe
Wenn du **[Tools](tools.md)** gelesen hast, kennst du bis hierher schon alles. Derselbe Dekorator, derselbe
Docstring als Beschreibung, dasselbe `Annotated`/`Field`. Das Einzige, was sich ändert: wer
ihn auslöst (die Person) und wohin das Ergebnis geht (in die Unterhaltung).
## Mehr als Text {#more-than-text}
`UserMessage` und `AssistantMessage` akzeptieren überall dort, wo sie einen `str` akzeptieren, auch einen Content-Block oder einen `Image`-/`Audio`-Helfer. Zwei Fälle kommen bei Prompts vor: ein Dokument anhängen und ein Bild anhängen.
### Eine Datei einbetten {#embedding-a-file}
```python title="server.py" hl_lines="5 12 21 23"
--8<-- "docs_src/prompts/tutorial004.py"
```
* Der Styleguide ist eine Ressource unter `style://python` (die behandelt **[Ressourcen](resources.md)**), gelesen aus einer `style-guide.md` neben `server.py`. Lege dort eine beliebige Markdown-Datei ab.
* `EmbeddedResource(resource=TextResourceContents(...))`, beide aus `mcp.types`, trägt die Datei samt URI und MIME-Typ als erste Nachricht; die Anweisung, die sich darauf bezieht, folgt als reiner Text.
* Einbetten, statt den Guide in den f-String einzufügen, erlaubt dem Client, ihn als Anhang zu zeigen und `style://python` später erneut zu öffnen, und das Modell erhält die Datei unverändert. Für eine Binärdatei nimm `BlobResourceContents` mit einem base64-kodierten `blob`.
Gerendert ist der `content` der ersten Nachricht ein `resource`-Block:
```json
{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}}
```
### Ein Bild anhängen {#attaching-an-image}
```python title="server.py" hl_lines="4 15"
--8<-- "docs_src/prompts/tutorial005.py"
```
* `Image` ist der Helfer aus **[Bilder, Audio und Icons](media.md)**. `UserMessage` wandelt ihn beim Rendern des Prompts in einen `ImageContent`-Block um (die Datei base64-kodiert, der MIME-Typ aus `.png` erraten); `Audio` wird auf dieselbe Weise zu einem `AudioContent`.
* Lege ein beliebiges PNG namens `architecture.png` neben `server.py`. Prompt-Argumente sind Strings, daher kommt das Bild immer vom Server; `component` liefert nur die Worte.
```json
{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"}
```
## Die Liste zur Laufzeit ändern {#changing-the-list-at-runtime}
Prompts lassen sich hinzufügen, während Clients verbunden sind, z. B. damit eine Person eine Anweisung als eigenen Menüeintrag speichern kann. Registriere den Prompt und benachrichtige dann:
```python title="server.py" hl_lines="5 23-27"
--8<-- "docs_src/prompts/tutorial006.py"
```
* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` registriert eine Funktion genau so, wie `@mcp.prompt()` es täte, und `mcp.remove_prompt(name)` ist die Umkehrung. `add_prompt` behält einen vorhandenen Eintrag gleichen Namens, statt ihn zu überschreiben; deshalb entfernt das Tool zuerst einen etwaigen alten, damit Speichern ein Ersetzen ist. `prompts/list` spiegelt die Änderung sofort wider.
* `await ctx.notify_prompts_changed()` sendet `notifications/prompts/list_changed` an jeden `2026-07-28`-Client, der auf einem `subscriptions/listen`-Stream lauscht (**[Abonnements](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` sendet sie an den aufrufenden Client, wenn dieser älter als 2026 ist (**[Legacy-Clients unterstützen](../run/legacy-clients.md)**). Rufe beide auf; jede tut nichts, wenn es niemanden zu benachrichtigen gibt.
* Ein Client, der die Benachrichtigung erhält, ruft `prompts/list` erneut auf. Im Python-`Client` ist das `async with client.listen(prompts_list_changed=True) as sub:`, was ein `PromptsListChanged`-Event liefert.
## Zusammenfassung {#recap}
* `@mcp.prompt()` auf einer Funktion macht sie zu einem Prompt. Der Name kommt von der Funktion, die Beschreibung vom Docstring.
@ -152,5 +197,7 @@ Der `prompts/list`-Eintrag enthält jetzt alles, was ein Client braucht, um ein
* Gibst du einen `str` zurück, wird daraus eine User-Nachricht. Gib eine Liste von `UserMessage` / `AssistantMessage` zurück, um eine mehrteilige Unterhaltung anzustoßen.
* `title=` und `Field(description=...)` sind das, was ein Client in seiner Oberfläche anzeigt.
* Ein fehlendes erforderliches Argument lässt den ganzen Request fehlschlagen. Es gibt kein Fehlerergebnis pro Prompt.
* Verpacke eine `EmbeddedResource` oder ein `Image` in eine `UserMessage`, um ein Dokument oder ein Bild anzuhängen.
* Füge Prompts zur Laufzeit mit `mcp.add_prompt(...)` / `mcp.remove_prompt(...)` hinzu oder entferne sie, dann `await ctx.notify_prompts_changed()` und `await ctx.session.send_prompt_list_changed()`.
Serverseitige Autovervollständigung für die Argumente eines Prompts (oder eines Ressourcen-Templates) ist **[Vervollständigungen](completions.md)**.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, e866c192e11d1c14, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c6f7d2a148aa49f4, c851964bb3301907, d715db6f8dccc9cc, ef86634aa70498a7]
sections: [a838d57f003aed44, 857d03886a0137ed, 42d9efcb9f542867, 2290ff08435b5573, 91be9b73602abcf1, 6cdbad079f7b47f0, d4b607372fb28b51, 18dbf726ac45e0b7, c7eff2a5698225fa, c851964bb3301907, 8f296f1f09e4c400, d715db6f8dccc9cc, a0c344a48450dbe4]
tool: 1
---
# Strukturierte Ausgabe {#structured-output}
@ -105,7 +105,7 @@ Nicht jede Form verdient eine Klasse. Ein `TypedDict` erzeugt dasselbe Schema:
--8<-- "docs_src/structured_output/tutorial003.py"
```
Ein `TypedDict` ist zur Laufzeit ein einfaches `dict`, also baust du genau das und gibst es zurück. Das Schema, die Validierung und `structured_content` sind identisch mit der `BaseModel`-Variante (abgesehen von den Beschreibungen, für die ein `TypedDict` keinen Platz hat).
Ein `TypedDict` ist zur Laufzeit ein einfaches `dict`, also baust du genau das und gibst es zurück. Das Schema, die Validierung und `structured_content` folgen denselben Regeln wie die `BaseModel`-Variante: Füge einen Klassen-Docstring oder `Annotated[..., Field(description=...)]` hinzu, und sie werden zu den Beschreibungen; ein `NotRequired`-Schlüssel, den du im Dict weglässt, bleibt auch aus `structured_content` draußen.
## Eine Dataclass {#a-dataclass}
@ -187,18 +187,19 @@ Solange du den Wert von Hand baust, merkst du davon nichts: Pydantic hat schon s
Die Annotation verspricht `WeatherData`. Die Upstream-Response liefert `humidity` nicht mehr mit.
!!! check
Ruf `get_weather` auf, und es reicht dem Client nicht stillschweigend ein halb leeres Objekt weiter. Der Aufruf schlägt fehl,
und die ersten Zeilen des Fehlers nennen das Feld:
Ruf `get_weather` auf, und es reicht dem Client nicht stillschweigend ein halb leeres Objekt weiter. Der Aufruf schlägt fehl:
Der Client bekommt `is_error=True` mit `Error executing tool get_weather`, sodass das Modell weiß, dass der
Aufruf fehlgeschlagen ist, statt selbstbewusst Wetterdaten abzulesen, die gar nicht da sind. Der Feldname ist für dich
bestimmt, im Server-Log auf Stufe `ERROR`:
```text
Error executing tool get_weather: 1 validation error for WeatherData
Tool 'get_weather' raised an unexpected exception
...
pydantic_core._pydantic_core.ValidationError: 1 validation error for WeatherData
humidity
Field required [type=missing, input_value={'temperature': 16.2, 'conditions': 'Overcast'}, input_type=dict]
```
Dieser Text kommt als Tool-Ergebnis mit `is_error=True` zurück. So weiß das Modell, dass der Aufruf fehlgeschlagen ist,
statt selbstbewusst Wetterdaten abzulesen, die gar nicht da sind.
Ein einfaches `dict` aus einem `-> WeatherData`-Tool zurückzugeben ist übrigens in Ordnung. Genau das hat `json.loads` erzeugt. Validiert wird der Wert, nicht der Python-Typ.
## Abschalten {#opting-out}
@ -213,6 +214,10 @@ Kein `output_schema`, keine Hülle, keine Validierung. `structured_content` ist
Das Gegenteil, `structured_output=True`, macht aus der automatischen Erkennung eine Anforderung: Ein Tool, dessen Rückgabetyp kein Schema erzeugen kann, löst beim Import eine Exception aus, statt auf Text zurückzufallen.
## Content-Blöcke und Medien {#content-blocks-and-media}
Content-Blöcke und Medien (`TextContent`, `EmbeddedResource`, `Image`, `Audio` und Verwandte – allein, als Elemente einer `list`, eines `tuple` oder einer `Sequence` oder als Zweige einer Union) sind schon für dich abgeschaltet: Sie sind zum Lesen für das Modell gedacht, also leitet die automatische Erkennung kein Schema aus ihnen ab (**[Bilder, Audio und Icons](media.md)** behandelt `Image` und `Audio`). `structured_output=True` erzwingt für die Content-Block-Klassen trotzdem eins.
## Eine Klasse ohne Type Hints {#a-class-without-type-hints}
Es gibt einen Weg, unstrukturiert zu enden, ohne es gewollt zu haben: eine Klasse zurückzugeben, die **keine Annotationen im Klassenrumpf** hat.
@ -245,6 +250,6 @@ Es gibt einen Weg, unstrukturiert zu enden, ohne es gewollt zu haben: eine Klass
* Skalare, Listen, Tupel und Unions werden in `{"result": ...}` verpackt. Modelle, `TypedDict`s, Dataclasses, annotierte Klassen und `dict[str, ...]` sind schon Objekte und bleiben, wie sie sind.
* Jedes Ergebnis trägt `content` (Text, für das Modell) **und** `structured_content` (Daten, für die Anwendung).
* Was du zurückgibst, wird gegen das Schema validiert. Eine Abweichung ist ein Tool-Fehler, kein kaputtes Ergebnis.
* `structured_output=False` nimmt ein Tool davon aus. Eine Klasse ohne Type Hints nimmt sich stillschweigend aus; achte darauf.
* `structured_output=False` nimmt ein Tool davon aus. Content-Blöcke, `Image` und `Audio` sind standardmäßig ausgenommen; eine Klasse ohne Type Hints nimmt sich stillschweigend aus, achte also darauf.
Damit hast du alles in der Hand, was ein Tool zurückmelden kann. Als Nächstes das zweite Primitiv: **[Ressourcen](resources.md)**.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [e4cc390d56573409, 8566e2b68594e9ad, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
sections: [e4cc390d56573409, f30cf8103a6e918c, 2c97b9f888398951, 048e5471dfa71aea, 3076b1e16ad95950, edbedf2a16e71311, 3d8ef8da89fa87c1, f6c0e02e6ea5a363]
tool: 1
---
# Tools {#tools}
@ -39,6 +39,8 @@ Aus diesen Type Hints erzeugt das SDK ein JSON Schema und sendet es während `to
Beide Argumente stehen in `required`, weil keines einen Standardwert hat. Das änderst du gleich. (Die `title`-Schlüssel sind Pydantic-Artefakte; die Properties, ihre Typen und `required` sind der Vertrag.)
Einen `$schema`-Schlüssel gibt es auch nicht: MCP behandelt ein Schema ohne ihn als **JSON Schema 2020-12** – genau das, was Pydantic erzeugt. Es gibt also nichts zu wählen, solange du nicht auf dem **[Low-Level-Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)** Schemas von Hand schreibst.
!!! tip
Type Hints sind hier keine Dokumentation. Sie sind **der Vertrag**. Sendet ein Client `"limit": "ten"`,
weist das SDK das zurück, bevor deine Funktion überhaupt läuft.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd]
sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd]
tool: 1
---
# URI-Templates und Pfadsicherheit {#uri-templates-and-path-safety}
@ -174,7 +174,7 @@ Sandbox-Grenze nicht kennen. Für Dateisystemzugriffe verwende `safe_join`,
um den Pfad aufzulösen und zu verifizieren, dass er innerhalb deines
Basisverzeichnisses bleibt:
```python title="server.py" hl_lines="4 14"
```python title="server.py" hl_lines="5 15"
--8<-- "docs_src/uri_templates/tutorial002.py"
```
@ -215,9 +215,11 @@ bleibt `safe_join` die Eindämmungsgrenze.
!!! tip
Kann dein Handler den Request nicht erfüllen (die Datei existiert nicht,
die ID ist unbekannt), löse eine Exception aus. Das SDK macht daraus eine
Fehler-Response. Den Unterschied zwischen einem Protokollfehler und einem
Tool-Fehler erklärt **[Fehler behandeln](handling-errors.md)**.
die ID ist unbekannt), löse `ResourceNotFoundError` aus, wie es
`read_manual` oben tut. Der Client bekommt `-32602` mit deiner Meldung
und dem URI. Eine unerwartete Exception wird stattdessen zu einem
generischen `-32603`. Siehe
**[Fehler behandeln](handling-errors.md#a-resource-that-doesnt-exist)**.
## Ressourcen auf dem Low-Level-Server {#resources-on-the-low-level-server}