Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions i18n/de/pages/advanced/low-level-server.md
Original file line number Diff line number Diff line change
@@ -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}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.

Expand Down
13 changes: 8 additions & 5 deletions i18n/de/pages/advanced/middleware.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
translation:
sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c]
sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43]
tool: 1
---
# Middleware {#middleware}
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down
18 changes: 10 additions & 8 deletions i18n/de/pages/client/index.md
Original file line number Diff line number Diff line change
@@ -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}
Expand All @@ -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}

Expand Down Expand Up @@ -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"
```

Expand Down Expand Up @@ -117,17 +118,18 @@ 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
result.content # [TextContent(type='text', text="Error executing tool lookup_book: No book titled 'Solaris' in the catalog.")]
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
Expand Down
16 changes: 8 additions & 8 deletions i18n/de/pages/client/transports.md
Original file line number Diff line number Diff line change
@@ -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}
Expand Down Expand Up @@ -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))`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This new sentence ends with in deiner without a noun, so it reads as incomplete German and obscures where the child's stderr goes. Render this as landet bei dir or explicitly name stderr in i18n/de/instructions.md, then regenerate the page.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At i18n/de/pages/client/transports.md, line 98:

<comment>This new sentence ends with `in deiner` without a noun, so it reads as incomplete German and obscures where the child's stderr goes. Render this as `landet bei dir` or explicitly name `stderr` in `i18n/de/instructions.md`, then regenerate the page.</comment>

<file context>
@@ -87,15 +87,15 @@ oder übergibst deinem `httpx2.AsyncClient` ein explizites `verify=ssl_context`
+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
</file context>


!!! warning
Der Kindprozess erbt **nicht** deine Umgebung. Er bekommt eine minimale Allow-List (`HOME`, `LOGNAME`,
Expand All @@ -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.
Loading
Loading