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
216 changed files with 3149 additions and 1047 deletions

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, cd0e9c933350390e]
tool: 1
---
# Der Low-Level-Server {#the-low-level-server}
@ -116,6 +116,17 @@ Der `_meta`-Block ist der Identitätsstempel des Servers: Das SDK fügt ihn jede
Der Server vergleicht die beiden Felder nie. Der `Client` dieses SDK schon: Gibst du `structured_content` zurück, das das von dir deklarierte `output_schema` nicht erfüllt, löst `call_tool` einen `RuntimeError` aus, der mit `Invalid structured content returned by tool search_books` beginnt und dann den `jsonschema`-Fehler zitiert. Ein Schema zu versprechen ist billig; es einzuhalten liegt bei dir. Die ganze Stufenleiter der Rückgabetypen und Schemas steht in **[Strukturierte Ausgabe](../servers/structured-output.md)**.
## Der Dialekt ist JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12}
`input_schema` und `output_schema` sind JSON Schema, und die [MCP-Spezifikation](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) legt den Dialekt fest: Ein Schema ohne `$schema`-Schlüssel ist **JSON Schema 2020-12**. Die Schemas, die `MCPServer` generiert, verlassen sich auf diesen Standardwert (Pydantic schreibt 2020-12 und lässt den Schlüssel weg), und ein von Hand geschriebenes dict wird ebenfalls daran gemessen. Das volle 2020-12-Vokabular steht also zur Verfügung:
```python title="server.py" hl_lines="8 14-15"
--8<-- "docs_src/lowlevel/tutorial007.py"
```
* Die Wurzel von `input_schema` muss `"type": "object"` sein. Daneben erreichen `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, `$defs` mit lokalen `$ref`s und die übrigen 2020-12-Schlüsselwörter den Client genau so, wie du sie geschrieben hast.
* Ein `$schema`-Schlüssel ist nicht nötig. Füge einen nur hinzu, um einen älteren Draft zu wählen: Der `Client` dieses SDK, der `structured_content` gegen das `output_schema` eines Tools validiert, wählt seinen Validator anhand von `$schema` und verwendet 2020-12, wenn keiner vorhanden ist.
## `_meta`: für die Anwendung, nicht für das Modell {#\_meta-for-the-application-not-the-model}
`content` ist der Teil der Antwort, den das Modell liest. `structured_content` ist dieselbe Antwort als typisierte Daten. `_meta` ist der dritte Kanal: Daten, die mit dem Ergebnis für die **Client-Anwendung** mitreisen, ohne überhaupt Teil der Antwort zu sein.
@ -167,7 +178,7 @@ Der Konstruktor deckt die Methoden ab, die MCP definiert. `add_request_handler`
--8<-- "docs_src/lowlevel/tutorial006.py"
```
* Das erste Argument ist der Methoden-String. Benachrichtigungen haben ein Gegenstück, `add_notification_handler`.
* Das erste Argument ist der Methoden-String. Benachrichtigungen haben ein Gegenstück, `add_notification_handler`. Dessen Handler feuern auf stdio und auf HTTP-Verbindungen der Handshake-Generation; auf dem Streamable-HTTP-Pfad von `2026-07-28` wird der Benachrichtigungs-POST eines Clients mit `202` quittiert und nicht zugestellt, weil diese Revision keine Benachrichtigungen vom Client zum Server über HTTP definiert.
* `params_type` ist das Modell, gegen das die eingehenden `params` validiert werden, **bevor** dein Handler läuft – eigene Methoden bekommen also *doch* die Validierung, die Tools nicht bekommen. Leite von `RequestParams` ab, damit das Feld `_meta` so geparst wird wie bei jeder anderen Methode.
* Der Handler gibt ein `BaseModel`, ein `dict` oder `None` zurück. Das SDK serialisiert es in das JSON-RPC-Ergebnis.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43]
tool: 1
---
# Middleware {#middleware}
@ -53,8 +53,11 @@ Genau darum geht es. Middleware umschließt **jede** eingehende Nachricht:
* Den Verbindungsaufbau: `server/discover`, oder `initialize` und `notifications/initialized`
in einer Legacy-Session.
* Jeden Request und jede Benachrichtigung. Bei einer Benachrichtigung gilt `ctx.request_id is None`,
`call_next(ctx)` gibt `None` zurück, und was immer du zurückgibst, wird verworfen.
* Jeden Request und jede Benachrichtigung, die den Server erreichen. Bei einer Benachrichtigung gilt
`ctx.request_id is None`, `call_next(ctx)` gibt `None` zurück, und was immer du zurückgibst, wird
verworfen. (Auf dem Streamable-HTTP-Pfad der Revision `2026-07-28` wird der Benachrichtigungs-POST
eines Clients schon im Transport mit `202` quittiert und nie weitergeleitet, erreicht die Middleware
also ebenfalls nicht; diese Revision definiert keine Client-zu-Server-Benachrichtigungen über HTTP.)
* Sogar eine Methode, für die der Server keinen Handler hat: `call_next` wirft den
`MCPError(-32601, "Method not found")` *durch* deine Middleware hindurch auf dem Weg zum Client.
@ -114,8 +117,8 @@ du gar nicht an sie. Sie tut nichts, bis du einen Exporter installierst, und sie
* Eine Middleware ist `async (ctx, call_next) -> result`, übergeben als `MCPServer(middleware=[...])`
(oder an `mcp.middleware` angehängt) und beim Low-Level-`Server` an `server.middleware` angehängt.
* Sie umschließt **jede** eingehende Nachricht (`server/discover`, `initialize`, Requests,
Benachrichtigungen, unbekannte Methoden) und läuft von außen nach innen.
* Sie umschließt **jede** eingehende Nachricht, die den Server erreicht (`server/discover`,
`initialize`, Requests, Benachrichtigungen, unbekannte Methoden), und läuft von außen nach innen.
* An `ctx.request_id is None` unterscheidest du eine Benachrichtigung von einem Request.
* Wirf eine Exception, statt `call_next` aufzurufen, um eine einzelne Nachricht abzulehnen; die
Verbindung überlebt.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [ebef1e7a0df854f4, a4c687d3d627d516, 8e79141fc2985342, b345dd05b9c3c7ab, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
sections: [ebef1e7a0df854f4, 8355cfaf1f76c9d5, 8e79141fc2985342, 46bdb07c7537e8a5, 80ce41579825a6fa, 5f0fa90494de8f65, 83d10514eaa62fa5, 9190555aa39a5d28, 84a4c9d8bf14dddb, 927d71cf40b58c30]
tool: 1
---
# Der Client {#the-client}
@ -27,9 +27,10 @@ Der Server oben ist nur da, damit du etwas hast, womit du dich verbinden kannst.
* Eine Instanz von `MCPServer` (oder des Low-Level-`Server`): Verbindung **im selben Prozess**.
* Ein URL-String (`Client("http://localhost:8000/mcp")`): Streamable HTTP, der Weg für die Produktion.
* Ein **Transport**: alles, was sich mit `async with ... as (read, write)` verwenden lässt, etwa `stdio_client(...)` um einen Subprozess herum.
* Ein `StdioServerParameters`: der Befehl, der als **Subprozess** gestartet wird und mit dem über dessen stdin und stdout gesprochen wird.
* Ein **Transport**: alles, was sich mit `async with ... as (read, write)` verwenden lässt, etwa `streamable_http_client(url, http_client=...)` um deinen eigenen HTTP-Client herum.
Alles Übrige auf dieser Seite ist in allen drei Fällen identisch. Header, Subprozesse, Timeouts und das `Transport`-Protokoll haben ihre eigene Seite: **[Client-Transporte](transports.md)**.
Alles Übrige auf dieser Seite ist in allen vier Fällen identisch. Header, Subprozesse, Timeouts und das `Transport`-Protokoll haben ihre eigene Seite: **[Client-Transporte](transports.md)**.
### Was ein verbundener Client mitbringt {#whats-on-a-connected-client}
@ -85,7 +86,7 @@ Dieses Schema ist alles, was eine UI braucht, um ein Argumentformular zu rendern
`call_tool(name, arguments)` führt das Tool aus und gibt dir ein `CallToolResult` zurück.
```python title="client.py" hl_lines="26-33"
```python title="client.py" hl_lines="27-34"
--8<-- "docs_src/client/tutorial003.py"
```
@ -117,7 +118,7 @@ Ein Tool, das eine Exception auslöst, löst in deinem Client **keine** aus. Es
!!! check
Frag `lookup_book` nach `"Solaris"` (einem Titel, der nicht im Katalog steht), und die Funktion löst
`ValueError` aus. Der Aufruf kehrt trotzdem normal zurück:
`ToolError` aus. Der Aufruf kehrt trotzdem normal zurück:
```python
result.is_error # True
@ -125,9 +126,10 @@ Ein Tool, das eine Exception auslöst, löst in deinem Client **keine** aus. Es
result.structured_content # None
```
Die Meldung der Exception ist in `content` gelandet, wo das **Modell** sie lesen und es erneut versuchen kann. Das
ist Absicht: Ein Tool-Fehler ist Teil des Gesprächs, kein Absturz. Sieh dir immer `is_error` an,
bevor du `structured_content` vertraust.
Die Meldung des `ToolError` ist in `content` gelandet, wo das **Modell** sie lesen und es erneut versuchen kann. Das
ist Absicht: Ein Tool-Fehler ist Teil des Gesprächs, kein Absturz. (Wäre das Tool mit einer
anderen Exception abgestürzt, stünde in `content` nur `Error executing tool lookup_book`.) Sieh dir immer
`is_error` an, bevor du `structured_content` vertraust.
!!! warning
`is_error=True` deckt mehr ab als dein eigenes `raise`. Frag nach einem Tool, das der Server gar nicht hat

View file

@ -1,6 +1,6 @@
---
translation:
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7]
sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c]
tool: 1
---
# Client-Transporte {#client-transports}
@ -87,15 +87,15 @@ oder übergibst deinem `httpx2.AsyncClient` ein explizites `verify=ssl_context`
Ein **stdio**-Server ist ein Subprozess. Der Client startet ihn, schreibt JSON-RPC in seine stdin und liest JSON-RPC aus seiner stdout. So betreibt ein Desktop-Host einen Server auf deinem Rechner: Ein Host *ist* dieser Code plus eine UI, und **[Mit einem echten Host verbinden](../get-started/real-host.md)** zeigt dieselbe Beziehung von der Seite des Hosts, als Konfigurationsdatei.
Beschreibe den Prozess mit `StdioServerParameters`, mach daraus mit `stdio_client` einen Transport und übergib *den* an `Client`:
Beschreibe den Prozess mit `StdioServerParameters` und übergib das Objekt an `Client`:
```python title="client.py" hl_lines="4-8 12"
```python title="client.py" hl_lines="3-7 11"
--8<-- "docs_src/client_transports/tutorial004.py"
```
`Client` akzeptiert das Parameter-Objekt allein nicht. `StdioServerParameters` ist Konfiguration; `stdio_client(server)` ist der Transport, der weiß, wie er daraus einen Prozess startet. Immer einpacken.
Beim Eintreten in den Block wird der Prozess gestartet. Beim Verlassen wird der Subprozess beendet: stdin schließen, warten, abschießen, falls er hängen bleibt. Du räumst ihn nie selbst auf.
Beim Verlassen des `async with`-Blocks wird auch der Subprozess beendet: stdin schließen, warten, abschießen, falls er hängen bleibt. Du räumst ihn nie selbst auf.
Die stderr des Kindprozesses landet in deiner. Um sie woandershin zu leiten, baust du den Transport selbst mit `stdio_client` (aus `mcp`) und übergibst stattdessen diesen: `Client(stdio_client(server, errlog=log_file))`.
!!! warning
Der Kindprozess erbt **nicht** deine Umgebung. Er bekommt eine minimale Allow-List (`HOME`, `LOGNAME`,
@ -113,16 +113,16 @@ Beim Verlassen des `async with`-Blocks wird auch der Subprozess beendet: stdin s
Für `Client` ist alles oben Genannte dasselbe.
Ein **Transport** ist ein beliebiger asynchroner Kontextmanager, der ein `(read, write)`-Paar von Nachrichten-Streams liefert: formal das `Transport`-Protokoll in `mcp.client`. `Client` löst sein Argument nach Typ auf: Ein Server-Objekt verbindet im Prozess, ein `str` wird zu `streamable_http_client(url)`, und alles andere wird direkt als Transport betreten. Diese letzte Regel ist der Grund, warum `stdio_client(...)`, `streamable_http_client(...)` und `sse_client(...)` alle in denselben Platz passen – und warum du deinen eigenen schreiben kannst.
Ein **Transport** ist ein beliebiger asynchroner Kontextmanager, der ein `(read, write)`-Paar von Nachrichten-Streams liefert: formal das `Transport`-Protokoll in `mcp.client`. `Client` löst sein Argument nach Typ auf: Ein Server-Objekt verbindet im Prozess, ein `str` wird zu `streamable_http_client(url)`, ein `StdioServerParameters` wird zu `stdio_client(params)`, und alles andere wird direkt als Transport betreten. Diese letzte Regel ist der Grund, warum `stdio_client(...)`, `streamable_http_client(...)` und `sse_client(...)` alle in denselben Platz passen – und warum du deinen eigenen schreiben kannst.
## Zusammenfassung {#recap}
* `Client(mcp)` (das Server-Objekt) verbindet im Speicher. Nutze es für Tests und zum Einbetten.
* `Client("http://.../mcp")` (eine URL) verbindet über Streamable HTTP, den Produktions-Transport.
* Header, Auth, Proxys und Timeouts gehören auf einen `httpx2.AsyncClient`, den du an `streamable_http_client(url, http_client=...)` übergibst. Es gibt kein Keyword `headers=`.
* stdio ist `Client(stdio_client(StdioServerParameters(...)))`, nie das Parameter-Objekt allein.
* stdio ist `Client(StdioServerParameters(...))`. Pack es nur dann selbst in `stdio_client(...)` ein, wenn du die stderr des Kindprozesses umleiten willst.
* Der Subprozess bekommt eine Umgebung per Allow-List, nicht deine; `env=` ergänzt sie.
* Ein Transport ist alles, womit du `async with x as (read, write)` schreiben kannst. Alles, was weder Server-Objekt noch URL ist, reicht `Client` direkt an dieses Protokoll weiter.
* Ein Transport ist alles, womit du `async with x as (read, write)` schreiben kannst. Alles, was weder Server-Objekt noch URL noch `StdioServerParameters` ist, reicht `Client` direkt an dieses Protokoll weiter.
* Das Erzeugen eines `Client` wählt den Transport. `async with` öffnet ihn.
Sobald der Transport offen ist, müssen sich beide Seiten auf eine Protokollversion einigen. Normalerweise denkst du nie darüber nach; wenn doch, ist **[Protokollversionen](../protocol-versions.md)** die richtige Seite.

View file

@ -1,11 +1,11 @@
---
translation:
sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c]
sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931]
tool: 1
---
# Veraltete Features {#deprecated-features}
Die Spec 2026-07-28 mustert fünf Dinge aus. Das SDK implementiert jedes davon weiterhin, und jedes davon trägt jetzt eine **Deprecation-Warnung**.
Die Spec 2026-07-28 mustert fünf Dinge aus. Das SDK implementiert jedes davon weiterhin, und jedes davon trägt jetzt eine **Deprecation-Warnung**. Ein SDK-Helfer ist unabhängig davon veraltet und steht [am Ende](#deprecated-sdk-helpers).
Die Tabelle unten nennt jedes veraltete Feature, den Grund, warum es verschwindet, und den Ersatz, auf dem du aufbauen solltest.
@ -56,6 +56,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE
`mode="legacy"`-Verbindung von Anfang bis Ende, deren Client den passenden Callback
registriert hat.
## `ping` auf einer Legacy-Session {#ping-on-a-legacy-session}
Ein **Ping** ist ein leerer Request, den jede Seite senden kann, um zu prüfen, ob die andere noch antwortet. Die Spec 2026-07-28 entfernt ihn ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)): Jeder Request, den ein moderner Client sendet, beweist bereits, dass der Server da ist, und ein moderner Server hat keinen Kanal, um selbst einen zu senden. Beide SDK-Methoden funktionieren weiterhin auf einer Session der Handshake-Generation. Vom Client aus:
```python
async def main() -> None:
async with Client("http://localhost:8000/mcp", mode="legacy") as client:
await client.send_ping() # warns; returns an EmptyResult
```
Und vom Server aus, in jedem beliebigen Handler:
```python
@mcp.tool()
async def check_client(ctx: Context) -> str:
"""A tool that still pings the client mid-call."""
await ctx.session.send_ping() # no warning; an EmptyResult while the client is connected
return "client answered"
```
* `client.send_ping()` warnt bei jedem Aufruf mit `MCPDeprecationWarning`. Auf einer Standardverbindung (`2026-07-28`) antwortet der Server stattdessen mit `MCPError: Method not found`.
* `ctx.session.send_ping()` trägt keine Warnung. Auf einer modernen Verbindung löst es denselben Fehler wegen des fehlenden Rückkanals (back-channel) aus wie jeder andere serverseitig initiierte Request.
* Keine der beiden Seiten registriert etwas, um einen Ping zu beantworten.
## Änderungsbenachrichtigungen für Roots {#roots-change-notifications}
Ein Client der 2025er-Generation, der die Roots-Capability deklariert hat, kann dem Server mitteilen, dass sich seine Arbeitsordner geändert haben, indem er `notifications/roots/list_changed` sendet; der Server reagiert, indem er `roots/list` erneut anfordert. Die Spec 2026-07-28 entfernt die Benachrichtigung zusammen mit dem restlichen Push-artigen Roots-Ablauf. Auf dem Client ist es das Übergeben von `list_roots_callback=` (**[Client-Callbacks](client/callbacks.md)**), das `"roots": {"listChanged": true}` deklariert, und ein einziger Aufruf hält dieses Versprechen:
```python
async def open_folder(client: Client, uri: str, name: str) -> None:
"""The user opened another folder: expose it through the roots callback, then tell the server."""
workspace.append(Root(uri=FileUrl(uri), name=name))
await client.send_roots_list_changed()
```
Auf dem Server nimmt der Low-Level-`Server` den empfangenden Handler entgegen:
```python
async def roots_changed(ctx: ServerRequestContext, params: NotificationParams | None) -> None:
"""The client's roots changed: ask for the new list."""
roots = (await ctx.session.list_roots()).roots
server = Server("Bookshop", on_roots_list_changed=roots_changed)
```
* `workspace` ist die Liste, die dein `list_roots_callback` zurückgibt. `client.send_roots_list_changed()` warnt, und es braucht einen `mode="legacy"`-Client: Auf einer modernen Verbindung wird die Benachrichtigung stillschweigend verworfen. Halte die Session danach offen, denn der nachfolgende `roots/list`-Request des Servers kommt darüber an.
* `MCPServer` hat keinen Hook für die Benachrichtigung. Auf dem Low-Level-`Server` registriert `on_roots_list_changed=` den Handler (ebenfalls veraltet, und er warnt beim Konstruieren). Die Benachrichtigung trägt keine Payload, also ruft der Handler `ctx.session.list_roots()` auf, um die neue Liste zu holen.
## Die Warnung unterdrücken {#silencing-the-warning}
Tu es nicht, in neuem Code.
@ -76,23 +125,33 @@ Das ist die ganze API. Es gibt keinen Schalter pro Methode, und du willst auch k
Dreh den Filter um, und du bekommst einen Regressionstest geschenkt. Füge
`"error::mcp.MCPDeprecationWarning"` zur Einstellung `filterwarnings` in deiner
pytest-Konfiguration hinzu, und der veraltete Aufruf **wirft eine Exception**, statt zu
warnen. Ein Tool namens `old_log`, das noch `ctx.info()` aufruft, besteht nicht mehr und
meldet stattdessen:
warnen. Ein Tool namens `old_log`, das noch `ctx.info()` aufruft, besteht nicht mehr: Der
Aufruf kommt mit `is_error=True` und `Error executing tool old_log` zurück, und das
mitgeschnittene Server-Log nennt den Schuldigen:
```text
Error executing tool old_log: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
mcp.shared.exceptions.MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SEP-2577).
```
Eine Zeile pytest-Konfiguration, und ein veralteter Aufruf kann sich nie wieder in deine
Codebasis schleichen, ohne einen Test fehlschlagen zu lassen.
## Veraltete SDK-Helfer {#deprecated-sdk-helpers}
Das sind keine Spec-Änderungen, sondern nur SDK-Interna mit einem besseren Ersatz. Sie warnen mit derselben `MCPDeprecationWarning` und werden in 3.0 entfernt.
| Veraltet | Was du stattdessen tust |
|---|---|
| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()` und danach `FuncMetadata.call_fn()`. Aufgerufen hat es ohnehin nur Code, der `FuncMetadata` direkt ansteuert (etwa eine eigene `Tool`-Unterklasse). |
## Zusammenfassung {#recap}
* Die Spec 2026-07-28 erklärt **Roots**, serverseitig initiiertes **Sampling** und Protokoll-**Logging** für veraltet (alle [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), beschränkt **Progress** auf die Richtung vom Server zum Client und entfernt **`ping`**.
* Die Ersatzspalte weist dir den Weg: **[Multi-Roundtrip-Requests](handlers/multi-round-trip.md)** für Sampling und Roots, **[Logging](handlers/logging.md)** für Logging, **[Progress](handlers/progress.md)** für Progress. `ping` braucht gar nichts.
* Veraltet ist ein Hinweis: keine Änderungen auf der Leitung, alles funktioniert weiterhin gegen Sessions von vor 2026, und du bekommst eine sichtbare `MCPDeprecationWarning` (eine `UserWarning`, also standardmäßig aktiv).
* Sampling und Roots brauchen zusätzlich einen Rückkanal (back-channel), den eine 2026-07-28-Session nicht hat. Auf einer modernen Verbindung warnen sie und werfen dann eine Exception.
* Sampling und Roots brauchen zusätzlich einen Rückkanal, den eine 2026-07-28-Session nicht hat. Auf einer modernen Verbindung warnen sie und werfen dann eine Exception.
* `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` bringt die ganze Kategorie zum Schweigen; `"error::mcp.MCPDeprecationWarning"` in pytest macht daraus einen fehlschlagenden Test.
* Ein SDK-Helfer, `FuncMetadata.call_fn_with_arg_validation()`, ist separat veraltet und wird in 3.0 entfernt.
* Neuer Code sollte auf nichts davon aufbauen.
Jede andere Seite dieser Dokumentation vermittelt die aktuelle API.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
sections: [3c4f2f06b4e978b6, 51ea5fbcb0e93563, 32d8808606ffdae0, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
tool: 1
---
# Mit einem echten Host verbinden {#connect-to-a-real-host}
@ -11,7 +11,7 @@ Das heißt: Sich mit einem Host zu verbinden ist eine einzige Handlung. Du nenns
## Ein Server, jeder Host {#one-server-every-host}
```python title="server.py" hl_lines="3 33-34"
```python title="server.py" hl_lines="4 34-35"
--8<-- "docs_src/real_host/tutorial001.py"
```
@ -51,7 +51,7 @@ Es ist außerdem der Befehl, den `mcp install` für dich in die Konfiguration vo
Und ein Host ist nichts weiter als eine Anwendung mit einem MCP-Client darin. Dein eigenes
Python kann also die Rolle des Hosts übernehmen: **[Client-Transporte](../client/transports.md)**
startet genau diese Datei als Subprozess mit `stdio_client(...)`, und **[Testen](testing.md)**
startet genau diese Datei als Subprozess mit `Client(StdioServerParameters(...))`, und **[Testen](testing.md)**
verbindet sich im Speicher mit ihr, ganz ohne Prozess.
## Claude Desktop {#claude-desktop}

View file

@ -1,6 +1,6 @@
---
translation:
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef]
tool: 1
---
# Testen {#testing}
@ -85,8 +85,8 @@ Das war's. Jetzt kannst du deine Tests um weitere Szenarien erweitern.
Zwei verschiedene Dinge können schiefgehen, und dieses Flag betrifft nur eines davon.
Eine Exception in einem **deiner Tools** ist kein Protokollfehler. Sie wird zu einem normalen Ergebnis mit
`is_error=True`, und das Modell liest die Meldung. `raise_exceptions` ändert daran nichts: Mit oder
ohne das Flag gibt `call_tool` dasselbe Ergebnis mit `is_error=True` zurück. Dazu gibt es eine ganze Seite:
`is_error=True` (und war es ein `ToolError`, liest das Modell deine Meldung). `raise_exceptions` ändert daran
nichts: Mit oder ohne das Flag gibt `call_tool` dasselbe Ergebnis mit `is_error=True` zurück. Dazu gibt es eine ganze Seite:
**[Fehler behandeln](../servers/handling-errors.md)**.
Ein Fehler **außerhalb** eines Tool-Bodys ist etwas anderes. Auf der Verbindung, die dir `Client(mcp)` gibt,

View file

@ -1,6 +1,6 @@
---
translation:
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8]
tool: 1
---
# Elicitation {#elicitation}
@ -89,7 +89,8 @@ Dieses Schema ist das Formular. `Field(description=...)` ist die Beschriftung; e
!!! warning
Ein Elicitation-Schema ist nicht so ausdrucksstark wie das Input-Schema eines Tools. Nur flache,
primitive Felder: `str`, `int`, `float`, `bool` oder ein `Literal` aus Strings (daraus wird ein `enum`).
Steckst du ein Modell in das Modell, löst `ctx.elicit` eine Exception aus, bevor irgendetwas an den Client geht:
Steckst du ein Modell in das Modell, löst `ctx.elicit` eine Exception aus, bevor irgendetwas an den Client geht.
Der Tool-Aufruf schlägt mit `Error executing tool <name>` fehl, und dein Server-Log nennt den Grund:
```text
TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition
@ -112,8 +113,8 @@ Eine Absage ist kein Fehler. Das Tool entscheidet, was Ablehnen bedeutet (hier:
!!! tip
Die Antwort wird gegen dein Modell validiert, bevor dein Code sie sieht. Ein Client, der
`"maybe"` für ein `bool` schickt, bringt deine Buchung nicht durcheinander: Der Aufruf schlägt mit einem
Schema-Mismatch-Fehler fehl, dein `if` läuft nie.
`"maybe"` für ein `bool` schickt, bringt deine Buchung nicht durcheinander: `ctx.elicit` löst einen
`ValueError` aus, der Aufruf schlägt fehl, und dein `if` läuft nie.
## Die Person zu einer URL schicken {#send-the-user-to-a-url}

View file

@ -1,6 +1,6 @@
---
translation:
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5]
sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5]
tool: 1
---
# Logging {#logging}
@ -55,6 +55,8 @@ Der Standardwert ist `"INFO"`.
`logging.basicConfig()` ersetzt nie Handler, die bereits existieren. Wenn du das Logging selbst konfigurierst, bevor du den Server erzeugst, gewinnt deine Konfiguration.
Du brauchst auch kein `try`/`except` in jedem Handler, nur um Fehlschläge festzuhalten. Wenn eine Tool- oder Ressourcen-Funktion eine Exception auslöst, loggt das SDK sie für dich. **[Fehler behandeln](../servers/handling-errors.md#any-other-exception)** erklärt, was geloggt wird und auf welchem Level.
## Ausprobieren {#try-it}
Starte den Server mit dem MCP Inspector:

View file

@ -1,6 +1,6 @@
---
translation:
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
tool: 1
---
# Den Server betreiben {#running-your-server}
@ -72,7 +72,7 @@ Jeder Transport hat eigene Keyword-Argumente, alle an `run()`:
* `streamable_http_path`: wo der MCP-Endpunkt liegt. Standardwert `/mcp`.
* `json_response=True`: jeden POST mit einem einzelnen JSON-Body statt eines SSE-Streams beantworten. Dieser Body hat Platz für die Response und sonst nichts. Ein Tool, das mitten im Request in den Client zurückruft (`ctx.elicit()`, Sampling), löst auf dieser Strecke daher `NoBackChannelError` aus, und Benachrichtigungen, die an den laufenden Aufruf gebunden sind (Fortschritt aus `ctx.report_progress()`, Log-Nachrichten pro Aufruf), werden verworfen; der eigenständige `GET`-Stream trägt davon unabhängige weiterhin.
* `stateless_http=True`: ein frischer Transport pro Request, kein Session-Tracking.
* `max_request_body_size`: größter akzeptierter POST-Body in Bytes. Standardwert 4 MiB; größere Requests
* `max_request_body_size`: größter akzeptierter Request-Body in Bytes. Standardwert 4 MiB; größere Requests
erhalten HTTP 413, bevor geparst oder eine Session angelegt wird. Erhöhe ihn nur, wenn legitime MCP-Nachrichten
diese Größe überschreiten.
* `event_store`, `retry_interval`, `transport_security`: Wiederaufnahme und Schutz vor DNS-Rebinding. Sie können warten, bis du anderswo als auf localhost bereitstellst; **[Bereitstellen und skalieren](deploy.md)** behandelt `transport_security`.

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}

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 4835ed1772f1d113, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, 954dc21efdb532a3]
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 8a6e351ec756904d, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, e4355f4c7cf4fb2e]
tool: 1
---
# Fehlerbehebung {#troubleshooting}
@ -81,11 +81,11 @@ async def main() -> None:
`__aexit__` ist die Trennung – deshalb gibt es kein `client.close()`, das du vergessen könntest. **[Testen](get-started/testing.md)** baut genau auf diesem Muster auf.
## `Error executing tool <name>: <message>` und `Unknown tool: <name>` {#error-executing-tool-name-message-and-unknown-tool-name}
## `Error executing tool <name>: <message>`, `Error executing tool <name>` und `Unknown tool: <name>` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name}
Du liest ein **Ergebnis**, keine Exception. `call_tool` hat nichts ausgelöst und wird das bei einem fehlschlagenden Tool auch nie tun.
Rufe `forecast` für eine Stadt auf, die der Server nicht kennt, und die Exception, die es auslöst, kommt zurück, während der Request als *erfolgreich* markiert ist:
Rufe `forecast` für eine Stadt auf, die der Server nicht kennt, und die `ToolError`, die es auslöst, kommt zurück, während der Request als *erfolgreich* markiert ist:
```python
result.is_error # True
@ -97,6 +97,8 @@ result.structured_content # None
Die Lösung liegt in deinem Client: **Prüfe `result.is_error`.** Ein `try/except` um `call_tool` fängt nichts davon ab, weil es nichts abzufangen gibt. Das ist Absicht, und es ist das Nützlichste auf dieser Seite, das du verinnerlichen solltest: Das *Modell* hat den Aufruf gewählt, also bekommt das Modell die Meldung und eine Chance, es erneut zu versuchen. Alles Weitere steht in **[Fehler behandeln](servers/handling-errors.md)**, einschließlich des `MCPError`-Pfads, der *tatsächlich* eine Exception auslöst.
Die nackte Form, `Error executing tool <name>` ohne Meldung, bedeutet, dass das Tool **abgestürzt** ist: Eine Exception, mit der es nicht gerechnet hat, ist ihm entwichen (oder sein Rückgabewert hat das Ausgabeschema nicht bestanden), und der Text dieser Exception bleibt von der Leitung fern. Der Traceback steht im **Log des Servers** auf `ERROR`, als `Tool '<name>' raised an unexpected exception`.
## `TypeError: The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool` {#typeerror-the-tool-decorator-was-used-incorrectly-did-you-forget-to-call-it-use-tool-instead-of-tool}
Du hast `@mcp.tool` statt `@mcp.tool()` geschrieben. `tool()` ist eine Dekorator-*Fabrik*: Ohne die Klammern übergibt Python deine Funktion an deren Parameter `name=`.
@ -411,7 +413,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key
## Zusammenfassung {#recap}
* `ExceptionGroup: unhandled errors in a TaskGroup` ist nie der Fehler. Lies die **letzte Zeile**; fängst du `MCPError` *innerhalb* des `async with Client(...)`-Blocks ab, entfällt die Verpackung komplett.
* `call_tool` löst bei einem fehlschlagenden Tool keine Exception aus. `Error executing tool ...` und `Unknown tool: ...` sind Ergebnisse: Prüfe `result.is_error`.
* `call_tool` löst bei einem fehlschlagenden Tool keine Exception aus. `Error executing tool ...` und `Unknown tool: ...` sind Ergebnisse: Prüfe `result.is_error`. Keine Meldung nach dem Tool-Namen heißt, es ist abgestürzt, und der Traceback steht im Server-Log.
* `Client must be used within an async context manager` -> verwende `async with`. `Use @tool() instead of @tool` -> füge die Klammern hinzu.
* `Tool already exists:` im Server-Log ist das einzige Zeichen, dass zwei gleichnamige Tools zu einem zusammengefallen sind.
* Ein 421, drei Schreibweisen: `Server returned an error response` (der Python-`Client`), `421 Misdirected Request` / `Invalid Host header` (alles andere), `Invalid Host header: <host>` (das Server-Log). Lösung: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e]
sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e]
tool: 1
---
# Was ist neu in v2 {#whats-new-in-v2}
@ -46,9 +46,9 @@ v1 gab dir drei verschachtelte Schichten: einen Transport-Kontextmanager, der ro
--8<-- "docs_src/client/tutorial001.py"
```
`Client` nimmt ein Server-Objekt (im Speicher, ohne Transport: das ist der Testansatz), eine URL (Streamable HTTP) oder einen beliebigen Transport-Kontextmanager wie `stdio_client(...)`. Das Betreten von `async with` verbindet und handelt die Protokollversion aus, welche Generation der Server auch spricht; `client.server_capabilities` und `client.protocol_version` sind danach einfach da, ebenso `client.server_info`, wenn der Server sich zu erkennen gibt (das ist jetzt `Implementation | None`, weil die Identität in der 2026er-Generation optional ist). Die Sampling- und Elicitation-Callbacks, die du in v1 registriert hast, funktionieren weiter (ihre Bodies sehen dieselbe Umbenennung der Attribute auf snake_case wie alles andere auf dieser Seite), sie beantworten jetzt außerdem die Requests-in-Results im 2026er-Stil (unten), und sie laufen nebenläufig statt nacheinander. `ClientSession` liegt für alle, die die Low-Level-Oberfläche wollen, weiterhin darunter, und `client.session` reicht sie dir; auch sie hat sich bewegt (sie läuft auf der neuen Dispatcher-Engine, und einige ihrer eigenen Signaturen haben sich geändert), lies also den **[Migrationsleitfaden](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**, bevor du hinabsteigst.
`Client` nimmt ein Server-Objekt (im Speicher, ohne Transport: das ist der Testansatz), eine URL (Streamable HTTP), ein `StdioServerParameters` (ein stdio-Subprozess) oder einen beliebigen anderen Transport-Kontextmanager wie `sse_client(...)`. Das Betreten von `async with` verbindet und handelt die Protokollversion aus, welche Generation der Server auch spricht; `client.server_capabilities` und `client.protocol_version` sind danach einfach da, ebenso `client.server_info`, wenn der Server sich zu erkennen gibt (das ist jetzt `Implementation | None`, weil die Identität in der 2026er-Generation optional ist). Die Sampling- und Elicitation-Callbacks, die du in v1 registriert hast, funktionieren weiter (ihre Bodies sehen dieselbe Umbenennung der Attribute auf snake_case wie alles andere auf dieser Seite), sie beantworten jetzt außerdem die Requests-in-Results im 2026er-Stil (unten), und sie laufen nebenläufig statt nacheinander. `ClientSession` liegt für alle, die die Low-Level-Oberfläche wollen, weiterhin darunter, und `client.session` reicht sie dir; auch sie hat sich bewegt (sie läuft auf der neuen Dispatcher-Engine, und einige ihrer eigenen Signaturen haben sich geändert), lies also den **[Migrationsleitfaden](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**, bevor du hinabsteigst.
**[Der Client](client/index.md)** stellt ihn vor, **[Client-Transporte](client/transports.md)** behandelt die drei Verbindungsformen, **[Client-Callbacks](client/callbacks.md)** die Callbacks selbst, und **[Testen](get-started/testing.md)** zeigt das In-Memory-Muster, das den Helfer `create_connected_server_and_client_session()` aus v1 ersetzt.
**[Der Client](client/index.md)** stellt ihn vor, **[Client-Transporte](client/transports.md)** behandelt die vier Verbindungsformen, **[Client-Callbacks](client/callbacks.md)** die Callbacks selbst, und **[Testen](get-started/testing.md)** zeigt das In-Memory-Muster, das den Helfer `create_connected_server_and_client_session()` aus v1 ersetzt.
### Der Low-Level-`Server` wurde neu gebaut, nicht umbenannt {#the-low-level-server-was-rebuilt-not-renamed}
@ -134,7 +134,7 @@ Bei `MCPServer(...)` geht es darum, was dein Server *ist*: sein Name, seine Inst
Die Umbenennungen machen sich selbst bemerkbar. Diese Änderungen nicht:
* **Synchrone Funktionen laufen auf einem Worker-Thread.** Ein `def`-Tool (oder eine Ressource, ein Prompt oder ein Resolver) blockiert die Event-Loop nicht mehr; der Preis dafür ist, dass sein Body nicht mehr *auf* dem Event-Loop-Thread läuft, was für threadgebundenen Code eine Rolle spielt. `async def`-Handler sind nicht betroffen. **[Migrationsleitfaden](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**.
* **Ein in einem Tool ausgelöster `MCPError` (in v1 `McpError`) ist jetzt ein Protokollfehler.** Das Modell sieht ihn nie. Jede andere Exception wird weiterhin zu einem Result mit `is_error=True`, das das Modell lesen und auf das es reagieren kann. Die Aufteilung steht in **[Fehler behandeln](servers/handling-errors.md)**.
* **Ein in einem Tool ausgelöster `MCPError` (in v1 `McpError`) ist jetzt ein Protokollfehler.** Das Modell sieht ihn nie. Jede andere Exception wird weiterhin zu einem Result mit `is_error=True`, aber nur die Meldung eines `ToolError` erreicht das Modell: Jede andere Exception lautet jetzt `Error executing tool <name>`, der Traceback steht in deinem Server-Log. Die Aufteilung steht in **[Fehler behandeln](servers/handling-errors.md)**.
* **Results werden validiert, bevor sie hinausgehen.** Ein von Hand gebautes `Tool`, dessen `input_schema` `{}` ist, lässt jetzt `tools/list` fehlschlagen (die Spezifikation verlangt `"type": "object"`). Server, die auf `@mcp.tool()` aufbauen, sehen das nie; das SDK schreibt ihre Schemas.
* **Dein Client validiert, was er empfängt.** `list_tools()` und `call_tool()` prüfen die Antwort des Servers gegen die ausgehandelte Protokollversion, sodass ein nicht ganz valider Server, den das nachsichtige Parsen von v1 tolerierte, jetzt `pydantic.ValidationError` auslöst. Wenn du dich mit Servern verbindest, die du nicht kontrollierst, rechne damit, dass du sie findest; die Details stehen im **[Migrationsleitfaden](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)**.
* **URI-Templates sind jetzt echtes RFC 6570.** `{+path}`, `{?query}` und Verwandte funktionieren, der Abgleich ist exakt statt Regex-locker, und Path Traversal in extrahierten Werten wird standardmäßig abgelehnt. Strengere Templates schlagen beim Dekorieren fehl, nicht beim ersten Request. **[URI-Templates](servers/uri-templates.md)**.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, d50fe7faead8cf68, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, 8db7116fc8ddd0ee, ebc33704fbd74262, cd0e9c933350390e]
tool: 1
---
# El Server de bajo nivel {#the-low-level-server}
@ -116,6 +116,17 @@ El bloque `_meta` es el sello de identidad del servidor: el SDK lo añade a cada
El servidor nunca compara los dos campos. El `Client` de este SDK sí: devuelve un `structured_content` que no cumpla el `output_schema` que declaraste y `call_tool` lanza un `RuntimeError` que empieza por `Invalid structured content returned by tool search_books` y sigue citando el fallo de `jsonschema`. Prometer un esquema es barato; cumplirlo depende de ti. Toda la escalera de tipos de retorno y esquemas está en **[Salida estructurada](../servers/structured-output.md)**.
## El dialecto es JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12}
`input_schema` y `output_schema` son JSON Schema, y la [especificación de MCP](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) fija el dialecto: un esquema sin clave `$schema` es **JSON Schema 2020-12**. Los esquemas que genera `MCPServer` se apoyan en ese valor por defecto (Pydantic escribe 2020-12 y omite la clave), y un dict escrito a mano también se rige por él, así que tienes disponible todo el vocabulario de 2020-12:
```python title="server.py" hl_lines="8 14-15"
--8<-- "docs_src/lowlevel/tutorial007.py"
```
* La raíz de `input_schema` debe ser `"type": "object"`. A su lado, `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, `$defs` con `$ref` locales y el resto de las palabras clave de 2020-12 llegan al cliente exactamente como las escribiste.
* No hace falta ninguna clave `$schema`. Añade una solo para optar por un draft más antiguo: el `Client` de este SDK, que valida `structured_content` contra el `output_schema` de una herramienta, elige su validador según `$schema` y usa 2020-12 cuando no hay ninguna.
## `_meta`: para la aplicación, no para el modelo {#\_meta-for-the-application-not-the-model}
`content` es la parte de la respuesta que lee el modelo. `structured_content` es la misma respuesta como datos tipados. `_meta` es el tercer canal: datos que viajan con el resultado para la **aplicación cliente**, sin formar parte de la respuesta en absoluto.
@ -167,7 +178,7 @@ El constructor cubre los métodos que MCP define. `add_request_handler` cubre to
--8<-- "docs_src/lowlevel/tutorial006.py"
```
* El primer argumento es la cadena del método. Las notificaciones tienen un gemelo, `add_notification_handler`.
* El primer argumento es la cadena del método. Las notificaciones tienen un gemelo, `add_notification_handler`. Sus handlers se disparan en stdio y en conexiones HTTP de la generación del handshake; en la ruta Streamable HTTP de `2026-07-28`, el POST de notificación de un cliente se confirma con un `202` y no se despacha, porque esa revisión no define notificaciones de cliente a servidor sobre HTTP.
* `params_type` es el modelo contra el que se validan los `params` entrantes **antes** de que se ejecute tu handler, así que los métodos personalizados *sí* reciben la validación que las herramientas no. Hereda de `RequestParams` para que el campo `_meta` se analice como el de cualquier otro método.
* El handler devuelve un `BaseModel`, un `dict` o `None`. El SDK lo serializa en el resultado JSON-RPC.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43]
tool: 1
---
# Middleware {#middleware}
@ -54,8 +54,11 @@ Ese es el punto. El middleware envuelve **cada** mensaje entrante:
* El establecimiento de la conexión: `server/discover`, o `initialize` y `notifications/initialized`
en una sesión heredada.
* Cada solicitud y cada notificación. Para una notificación, `ctx.request_id is None`,
`call_next(ctx)` devuelve `None` y lo que devuelvas se descarta.
* Cada solicitud y cada notificación que llega al servidor. Para una notificación,
`ctx.request_id is None`, `call_next(ctx)` devuelve `None` y lo que devuelvas se descarta.
(En la ruta Streamable HTTP de `2026-07-28`, el POST de notificación de un cliente se confirma
con `202` en el transporte y nunca se despacha, así que tampoco llega al middleware; esa
revisión no define notificaciones del cliente al servidor sobre HTTP.)
* Incluso un método para el que el servidor no tiene handler: `call_next` lanza el
`MCPError(-32601, "Method not found")` *a través de* tu middleware de camino al cliente.
@ -114,8 +117,8 @@ span de OpenTelemetry por cada mensaje. No lo añades y, la mayor parte del tiem
* Un middleware es `async (ctx, call_next) -> result`, se pasa como `MCPServer(middleware=[...])` (o
se añade a `mcp.middleware`), y se añade a `server.middleware` en el `Server` de bajo nivel.
* Envuelve **cada** mensaje entrante (`server/discover`, `initialize`, solicitudes, notificaciones,
métodos desconocidos) y se ejecuta de fuera hacia dentro.
* Envuelve **cada** mensaje entrante que llega al servidor (`server/discover`, `initialize`,
solicitudes, notificaciones, métodos desconocidos) y se ejecuta de fuera hacia dentro.
* `ctx.request_id is None` es la forma de distinguir una notificación de una solicitud.
* Lanza una excepción en lugar de llamar a `call_next` para rechazar un mensaje; la conexión sobrevive.
* El trazado con OpenTelemetry del propio SDK también es un middleware, ya incluido en la lista. Consulta

Some files were not shown because too many files have changed in this diff Show more