You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: refresh translations for recent English changes
Re-runs scripts/docs/translations.py translate for all twelve languages to
catch up with the English changes since the last refresh. Twenty-three
pages per language had sections whose English changed; only those sections
were retranslated and everything else is carried over byte for byte.
run/authorization.md in es, ru and uk was retranslated whole (--pages)
because the section-scoped run kept reproducing a stale phrase from the
previous translation.
Nothing under docs/, the per-language instructions or glossaries changed.
@@ -39,11 +39,31 @@ Nicht jeder Client rendert Apps. Die Spezifikation sagt unverblümt, was das fü
39
39
40
40
Das Modell liest `content`; der iframe ist für Menschen. Ein UI-fähiger Host füttert das Modell trotzdem mit dem Textergebnis, und ein reiner Text-Client bekommt *nur* das. Das kanonische Muster ist also: ein Tool, zwei Antworten. Sieh dir `get_time` noch einmal an:
41
41
42
-
```python title="server.py" hl_lines="23-27"
42
+
```python title="server.py" hl_lines="21-25"
43
43
--8<--"docs_src/apps/tutorial001.py"
44
44
```
45
45
46
-
`client_supports_apps(ctx)` ist nur dann `True`, wenn der Client die Extension `io.modelcontextprotocol/ui` deklariert **und**`text/html;profile=mcp-app` in seinen `mimeTypes`-Einstellungen aufgeführt hat. Das Feld ist Pflicht, ein Client, der es weglässt, zählt also nicht. Genau das deklariert `main()` in derselben Datei: die Client-Hälfte der Aushandlung – und die reichhaltige Antwort kommt zurück.
46
+
`client_supports_apps(ctx)` ist nur dann `True`, wenn der Client die Extension `io.modelcontextprotocol/ui` deklariert **und**`text/html;profile=mcp-app` in seinen `mimeTypes`-Einstellungen aufgeführt hat. Das Feld ist Pflicht, ein Client, der es weglässt, zählt also nicht. Hier ist die Client-Hälfte der Aushandlung:
47
+
48
+
```python title="client.py" hl_lines="8 12"
49
+
--8<--"docs_src/apps/tutorial001_client.py"
50
+
```
51
+
52
+
Stelle `server.py` über HTTP bereit und starte dann den Client in einem zweiten Terminal:
53
+
54
+
```console
55
+
uv run mcp run server.py --transport streamable-http
56
+
```
57
+
58
+
```console
59
+
python client.py
60
+
```
61
+
62
+
```text
63
+
2026-06-26T12:00:00Z
64
+
```
65
+
66
+
Die reichhaltige Antwort kam zurück. Lass `extensions=[APPS_SUPPORT]` im `Client`-Aufruf weg, und dasselbe Programm gibt stattdessen `The time is 2026-06-26T12:00:00Z.` aus – das ist alles, was ein reiner Text-Client je zu sehen bekommt.
47
67
48
68
!!! warning
49
69
Gib niemals einen Platzhalter wie `"[Rendered UI]"` als einzigen Inhalt zurück. Wenn der Fallback-Text nutzlos ist, ist das Tool für jeden reinen Text-Client und für das Modell selbst nutzlos. Schreib den Satz.
*`tools()` gibt `ToolBinding`s zurück. Der Server registriert jedes einzelne genau so, als hättest du selbst `mcp.add_tool(...)` aufgerufen: dieselbe Schema-Generierung, dieselbe `Context`-Injection, alles gleich.
57
57
*`settings()` ist der Wert, der unter `capabilities.extensions["com.example/stamps"]` angekündigt wird. Gib `{}` zurück (den Standardwert), um die Extension ohne Settings anzukündigen.
58
58
* Die Extension bekommt den Server nie in die Hand. Sie deklariert ihre Beiträge als Daten; `MCPServer` verarbeitet sie. Es gibt kein `self.server`, das sie verändern könnte.
59
59
60
-
Und `main()` ist der Beweis, ein In-Memory-Client direkt gegen `mcp`:
60
+
Stelle sie über HTTP bereit, und ein Client liefert den Beweis:
61
61
62
-
```python title="server.py" hl_lines="29-34"
63
-
--8<--"docs_src/extensions/tutorial003.py"
62
+
```console
63
+
uv run mcp run server.py --transport streamable-http
64
+
```
65
+
66
+
```python title="client.py" hl_lines="7-11"
67
+
--8<--"docs_src/extensions/tutorial003_client.py"
64
68
```
65
69
70
+
Jede `server.py` auf dieser Seite wird mit diesem Befehl bereitgestellt, und jede `client.py` läuft daneben mit `python client.py` aus einem zweiten Terminal.
71
+
66
72
### Eigene Methoden bedienen {#serving-your-own-methods}
67
73
68
74
Eine Extension kann **neue Request-Methoden** registrieren: eigene Verben, bedient neben denen der Spezifikation:
*`Client(..., extensions=[advertise(EXTENSION_ID)])` deklariert die Extension. Die Deklarationen werden zu `ClientCapabilities.extensions`: Auf einer 2026-07-28-Verbindung reist die Map im `_meta`-Umschlag jedes einzelnen Requests, der Server sieht sie also bei **jedem** Request; auf einer Legacy-Verbindung reist sie mit dem `initialize`-Handshake. Dem Server-Code ist das egal: `require_client_extension(ctx, ...)` und `ctx.session.check_client_capability(...)` lesen auf beiden Pfaden die richtige Quelle.
93
99
* Vendor-Methoden steigen eine Schicht tiefer zu `client.session.send_request(...)` hinab; `Client` bekommt nur für Verben der Spezifikation eigene Methoden. `send_request` akzeptiert jede `Request`-Unterklasse, der Vendor-Request geht also unverändert durch.
100
+
*`SearchRequest` und die beiden Models, die er trägt, sind der Vertrag der Extension auf der Leitung, also deklariert der Client sie für sich selbst. Eine veröffentlichte Extension würde sie in einem Paket ausliefern, das beide Seiten importieren.
@@ -109,12 +116,18 @@ Der Hook umhüllt `tools/call` und sonst nichts. Für alles, was jede Nachricht
109
116
110
117
## Eine Client-Extension verwenden {#using-a-client-extension}
111
118
112
-
Eine **Client-Extension** ist derselbe Vertrag von der konsumierenden Seite: ein Bündel clientseitigen Verhaltens hinter einem einzigen Identifier. Übergib Instanzen an `Client(extensions=[...])`und rufe Tools ganz normal auf:
119
+
Eine **Client-Extension** ist derselbe Vertrag von der konsumierenden Seite: ein Bündel clientseitigen Verhaltens hinter einem einzigen Identifier. Der Server hier beantwortet `buy` mit einem einzulösenden Beleg statt mit der Ware, und das nur für einen Client, der die Extension deklariert hat:
113
120
114
-
```python title="client.py" hl_lines="66-68"
121
+
```python title="server.py" hl_lines="22-25"
115
122
--8<--"docs_src/extensions/tutorial006.py"
116
123
```
117
124
125
+
Übergib auf dem Client Instanzen an `Client(extensions=[...])` und rufe Tools ganz normal auf:
126
+
127
+
```python title="client.py" hl_lines="33-35"
128
+
--8<--"docs_src/extensions/tutorial006_client.py"
129
+
```
130
+
118
131
`call_tool("buy", ...)` gibt ein gewöhnliches `CallToolResult` zurück, wie jeder andere Aufruf. Was die Extension geändert hat: Der Server darf `buy` jetzt mit einer `receipt`-**Ergebnisform** statt mit einem endgültigen Ergebnis beantworten, und `Receipts` bringt sie zu Ende (hier, indem sie den Beleg mit einem Folgeaufruf einlöst), bevor `call_tool` zurückkehrt. An der Aufrufstelle bewegt sich nichts.
119
132
120
133
Lass die Extension weg, und nichts davon existiert: Die Schranke des Servers weist einen Client ab, der sie nicht deklariert hat (Fehler -32021), und eine beanspruchte Form von einem Server, der die Schranke überspringt, fällt durch die Validierung, genau wie die Spezifikation es für einen unbekannten `resultType` verlangt. Standardmäßig aus, an beiden Enden der Leitung.
@@ -124,15 +137,15 @@ Um einen Identifier **ohne** clientseitiges Verhalten anzukündigen (der Server
## Eine Client-Extension schreiben {#writing-a-client-extension}
131
144
132
145
Leite von `ClientExtension` ab und überschreibe nur, was du brauchst. Drei Arten von Beiträgen, jede mit einer Standardimplementierung: `settings()`, `claims()` und `notifications()`.
* Der Identifier folgt derselben Grammatik wie auf dem Server und wird validiert, wenn die Klasse definiert wird.
@@ -153,12 +166,18 @@ Zwei stille Regeln. Claims sind nur auf 2026-07-28-Verbindungen aktiv, und die C
153
166
154
167
### Extension-Verben {#extension-verbs}
155
168
156
-
Die eigenen Request-Methoden einer Extension brauchen keine clientseitige Registrierung. Ein Vendor-Request-Typ leitet von `mcp.types.Request` ab und geht durch `client.session.send_request`, wie in [Eigene Methoden bedienen](#serving-your-own-methods). Eine Ergänzung: Wenn ein Params-Schlüssel im `Mcp-Name`-Header mitreisen muss (Extension-Spezifikationen wie Tasks verlangen das für ihre Verben), deklariert der Request-Typ `name_param`:
169
+
Die eigenen Request-Methoden einer Extension brauchen keine clientseitige Registrierung. Ein Vendor-Request-Typ leitet von `mcp.types.Request` ab und geht durch `client.session.send_request`, wie in [Eigene Methoden bedienen](#serving-your-own-methods). Nimm einen Server, dessen Extension ein einziges Verb zu einem benannten Job bedient:
Eine Ergänzung auf dem Client: Wenn ein Params-Schlüssel im `Mcp-Name`-Header mitreisen muss (Extension-Spezifikationen wie Tasks verlangen das für ihre Verben), deklariert der Request-Typ `name_param`:
Die Session spiegelt `params["jobId"]` auf jedem Sendepfad in `Mcp-Name`, und ein fehlender Wert scheitert laut, statt einen erforderlichen Header stillschweigend wegzulassen.
163
182
164
183
## Was eine Extension nicht kann {#what-an-extension-cannot-do}
@@ -36,18 +36,22 @@ Drei Dinge haben sich geändert, und sie sind die ganze Low-Level-API:
36
36
37
37
### Ausprobieren {#try-it}
38
38
39
-
Hierfür gibt es keinen Inspector: `mcp dev` und `mcp run` akzeptieren nur einen `MCPServer`. Dem In-Memory-`Client` ist das egal; er nimmt einen Low-Level-`Server` genauso wie einen `MCPServer`:
39
+
`mcp dev` und `mcp run` akzeptieren nur einen `MCPServer`, also betreibst du diesen hier selbst. Die letzte Zeile von `server.py` baut daraus eine gewöhnliche ASGI-App, und uvicorn führt sie aus:
40
40
41
-
```python title="main.py"
41
+
```console
42
+
uvicorn server:app --port 8000
43
+
```
44
+
45
+
Richte den Inspector oder einen beliebigen Client auf `http://localhost:8000/mcp`:
46
+
47
+
```python title="client.py"
42
48
import asyncio
43
49
44
50
from mcp import Client
45
51
46
-
from server import server
47
-
48
52
49
53
asyncdefmain() -> None:
50
-
asyncwith Client(server) as client:
54
+
asyncwith Client("http://localhost:8000/mcp") as client:
51
55
result =await client.call_tool("search_books", {"query": "dune", "limit": 5})
52
56
print(result.content)
53
57
@@ -64,6 +68,8 @@ Derselbe Text, den die `@mcp.tool()`-Version erzeugt hat. Zwei ehrliche Untersch
64
68
*`result.structured_content` ist `None`. Der High-Level-Server verpackt ein `-> str` für dich in `{"result": ...}`; hier baut niemand, was du nicht gebaut hast.
65
69
*`list_tools` gibt das Schema zurück, das **du** getippt hast, Zeichen für Zeichen. Die High-Level-Version hatte `"title": "Query"` auf jeder Property und ein `"title": "search_booksArguments"` an der Wurzel: Pydantic-Artefakte. Hier unten gilt: Was auf der Leitung ist, hast du dort hingelegt.
66
70
71
+
In einem Test sparst du dir uvicorn und den Port: `Client(server)` nimmt einen Low-Level-`Server` im selben Prozess genauso entgegen wie einen `MCPServer`, und **[Testen](../get-started/testing.md)** ist genau dieses Muster.
72
+
67
73
## Nichts wird für dich geprüft {#nothing-is-checked-for-you}
68
74
69
75
`MCPServer` weist ein fehlerhaftes Argument ab, bevor deine Funktion überhaupt läuft, indem er den Aufruf gegen das generierte Schema validiert (**[Tools](../servers/tools.md)**).
@@ -215,4 +221,4 @@ Jeder davon ist eine Idee, für die du jetzt das Vokabular hast; jeder hat seine
215
221
*`add_request_handler(method, params_type, handler)` bedient jede Methode. `initialize` ist reserviert.
216
222
* Die Capabilities, die ein `Server` ankündigt, leiten sich davon ab, welche Handler du registriert hast.
217
223
218
-
`Client(server)` hat beide Server identisch behandelt, weil sie dasselbe Protokoll *sind* – und genau darum geht es. Die nächste Schicht darunter ist gar keine Klasse: Es ist **[Middleware](middleware.md)**.
224
+
Der Client hat beide Server identisch behandelt, weil sie dasselbe Protokoll *sind* – und genau darum geht es. Die nächste Schicht darunter ist gar keine Klasse: Es ist **[Middleware](middleware.md)**.
@@ -19,21 +19,25 @@ Paginierung ist für den Server gedacht, dessen Ressourcenliste in Wahrheit eine
19
19
--8<--"docs_src/pagination/tutorial001.py"
20
20
```
21
21
22
-
* Auf einem Low-Level-`Server` sind Handler Konstruktorargumente, keine Dekoratoren. `on_list_resources` beantwortet jeden `resources/list`-Request; mehr Verkabelung gibt es nicht.
23
-
* Jeder paginierte Handler ist als `params: PaginatedRequestParams | None` typisiert, und das Beispiel akzeptiert beides. Über eine Verbindung übergibt dir das SDK jedoch nie `None` (ein Request ohne `params`-Member erreicht den Handler als Modell mit seinen Standardwerten). Das Signal, auf das es ankommt, ist daher `params.cursor is None`: **von vorne beginnen**.
22
+
* Auf einem Low-Level-`Server` sind Handler Konstruktorargumente, keine Dekoratoren. `on_list_resources` beantwortet jeden `resources/list`-Request; das ist schon die ganze Anbindung.
23
+
* Jeder paginierte Handler ist als `params: PaginatedRequestParams | None` typisiert, und das Beispiel akzeptiert beides. Über eine Verbindung übergibt dir das SDK allerdings nie `None` (ein Request ohne `params`-Member erreicht den Handler als Modell mit seinen Standardwerten). Das Signal, auf das es ankommt, ist daher `params.cursor is None`: **von vorne beginnen**.
24
24
* Du entscheidest, was ein Cursor *ist*. Hier ist es ein Offset, als String dargestellt. Ein Zeitstempel, ein Primärschlüssel, ein Base64-Blob: alles, was du beim Herausgeben erzeugen und beim Zurückkommen wiedererkennen kannst.
25
25
* Mit `next_cursor=None` sagst du „das war die letzte Seite“. Es gibt keine Anzahl, keine Gesamtsumme, kein `has_more`. `None` ist das ganze Signal.
26
26
27
27
!!! tip
28
28
Eine `PAGE_SIZE` von 10 macht das Beispiel lesbar. Wähle deine pro Endpunkt: Eine Liste
29
-
einzeiliger Ressourcen verträgt eine Seite mit 500 Einträgen; eine Liste fetter Prompt-Templates nicht.
29
+
einzeiliger Ressourcen verträgt eine Seite mit 500 Einträgen; eine Liste üppiger Prompt-Templates nicht.
30
30
Der Client hat dabei nichts mitzureden, und das ist Absicht.
31
31
32
32
### Ausprobieren {#try-it}
33
33
34
-
`Client(server)` verbindet sich im Speicher mit einem Low-Level-`Server` genau so, wie er sich mit einem `MCPServer` verbindet.
34
+
`mcp run` akzeptiert nur einen `MCPServer`, diesen hier stellst du also selbst bereit. Die letzte Zeile von `server.py` baut aus dem `Server` eine gewöhnliche ASGI-App, und uvicorn führt sie aus:
35
35
36
-
Rufe `list_resources()` ohne Argumente auf. Du bekommst zehn Ressourcen, `book-1` bis `book-10`, und `next_cursor` ist der String `"10"`.
36
+
```console
37
+
uvicorn server:app --port 8000
38
+
```
39
+
40
+
Richte einen beliebigen Client (**[Der Client](../client/index.md)** oder den Inspector) auf `http://localhost:8000/mcp` und rufe `list_resources()` ohne Argumente auf. Du bekommst zehn Ressourcen, `book-1` bis `book-10`, und `next_cursor` ist der String `"10"`.
37
41
38
42
Gib ihn mit `list_resources(cursor="10")` zurück, und die erste Ressource ist `book-11`, der neue `next_cursor` ist `"20"`.
39
43
@@ -43,15 +47,15 @@ Die zehnte Seite kommt mit `next_cursor` auf `None` zurück. Fertig.
43
47
44
48
Jede `list_*`-Methode auf `Client` (`list_tools`, `list_resources`, `list_resource_templates`, `list_prompts`) nimmt ein Keyword-Argument `cursor=`. Eine paginierte Liste leerzulesen ist ein einziges `while True`:
45
49
46
-
```python title="client.py" hl_lines="26-32"
50
+
```python title="client.py" hl_lines="9-15"
47
51
--8<--"docs_src/pagination/tutorial002.py"
48
52
```
49
53
50
54
*`cursor` beginnt als `None`, der erste Request trägt also keinen Cursor.
51
55
* Erweitere die Liste, **bevor** du auf `next_cursor` schaust: Auch die letzte Seite enthält Ressourcen.
52
56
*`next_cursor is None` ist der Ausstieg. Alles andere geht unverändert direkt zurück in `cursor=`.
53
57
54
-
Führe sein `main()` aus, und es gibt `100 resources` aus: zehn Seiten zu je zehn, zusammengefügt von einer Schleife, die nie wusste, dass es zehn Seiten waren.
58
+
Während uvicorn weiterhin `server.py` ausliefert, starte in einem zweiten Terminal `python client.py`. Es gibt `100 resources` aus: zehn Seiten zu je zehn, zusammengefügt von einer Schleife, die nie wusste, dass es zehn Seiten waren.
55
59
56
60
Das ist dieselbe Schleife, die **[Der Client](../client/index.md)** für jedes `list_*`-Verb zeigt, und sie kostet nichts gegenüber einem Server, der nicht paginiert: `next_cursor` ist schon in der ersten Response `None`, und die Schleife läuft genau einmal.
0 commit comments