From 76241ed70c6b041d9932a6f93513339ae27af113 Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:24:14 +0000 Subject: [PATCH] docs: refresh translations for recent English changes Re-run scripts/docs/translations.py for all twelve languages. Eighteen pages per language had sections whose English changed since the initial run; only those sections were retranslated, everything else is carried over byte for byte. `status` now reports every page current, and each language site builds with no warnings. --- i18n/de/pages/advanced/low-level-server.md | 15 +++- i18n/de/pages/advanced/middleware.md | 13 ++-- i18n/de/pages/client/index.md | 18 ++--- i18n/de/pages/client/transports.md | 16 ++--- i18n/de/pages/deprecated.md | 71 +++++++++++++++++-- i18n/de/pages/get-started/real-host.md | 6 +- i18n/de/pages/get-started/testing.md | 6 +- i18n/de/pages/handlers/elicitation.md | 9 +-- i18n/de/pages/handlers/logging.md | 4 +- i18n/de/pages/run/index.md | 4 +- i18n/de/pages/servers/handling-errors.md | 61 +++++++++++----- i18n/de/pages/servers/media.md | 21 +++++- i18n/de/pages/servers/prompts.md | 51 ++++++++++++- i18n/de/pages/servers/structured-output.md | 23 +++--- i18n/de/pages/servers/tools.md | 4 +- i18n/de/pages/servers/uri-templates.md | 12 ++-- i18n/de/pages/troubleshooting.md | 10 +-- i18n/de/pages/whats-new.md | 8 +-- i18n/es/pages/advanced/low-level-server.md | 15 +++- i18n/es/pages/advanced/middleware.md | 13 ++-- i18n/es/pages/client/index.md | 18 ++--- i18n/es/pages/client/transports.md | 16 ++--- i18n/es/pages/deprecated.md | 70 ++++++++++++++++-- i18n/es/pages/get-started/real-host.md | 6 +- i18n/es/pages/get-started/testing.md | 9 +-- i18n/es/pages/handlers/elicitation.md | 9 +-- i18n/es/pages/handlers/logging.md | 4 +- i18n/es/pages/run/index.md | 4 +- i18n/es/pages/servers/handling-errors.md | 59 ++++++++++----- i18n/es/pages/servers/media.md | 21 +++++- i18n/es/pages/servers/prompts.md | 51 ++++++++++++- i18n/es/pages/servers/structured-output.md | 23 +++--- i18n/es/pages/servers/tools.md | 4 +- i18n/es/pages/servers/uri-templates.md | 12 ++-- i18n/es/pages/troubleshooting.md | 10 +-- i18n/es/pages/whats-new.md | 8 +-- i18n/fr/pages/advanced/low-level-server.md | 15 +++- i18n/fr/pages/advanced/middleware.md | 15 ++-- i18n/fr/pages/client/index.md | 18 ++--- i18n/fr/pages/client/transports.md | 16 ++--- i18n/fr/pages/deprecated.md | 70 ++++++++++++++++-- i18n/fr/pages/get-started/real-host.md | 6 +- i18n/fr/pages/get-started/testing.md | 9 +-- i18n/fr/pages/handlers/elicitation.md | 9 +-- i18n/fr/pages/handlers/logging.md | 4 +- i18n/fr/pages/run/index.md | 6 +- i18n/fr/pages/servers/handling-errors.md | 59 ++++++++++----- i18n/fr/pages/servers/media.md | 21 +++++- i18n/fr/pages/servers/prompts.md | 51 ++++++++++++- i18n/fr/pages/servers/structured-output.md | 21 +++--- i18n/fr/pages/servers/tools.md | 4 +- i18n/fr/pages/servers/uri-templates.md | 12 ++-- i18n/fr/pages/troubleshooting.md | 10 +-- i18n/fr/pages/whats-new.md | 8 +-- i18n/hi/pages/advanced/low-level-server.md | 15 +++- i18n/hi/pages/advanced/middleware.md | 11 +-- i18n/hi/pages/client/index.md | 18 ++--- i18n/hi/pages/client/transports.md | 16 ++--- i18n/hi/pages/deprecated.md | 70 ++++++++++++++++-- i18n/hi/pages/get-started/real-host.md | 10 +-- i18n/hi/pages/get-started/testing.md | 10 +-- i18n/hi/pages/handlers/elicitation.md | 9 +-- i18n/hi/pages/handlers/logging.md | 4 +- i18n/hi/pages/run/index.md | 6 +- i18n/hi/pages/servers/handling-errors.md | 61 +++++++++++----- i18n/hi/pages/servers/media.md | 21 +++++- i18n/hi/pages/servers/prompts.md | 65 ++++++++++++++--- i18n/hi/pages/servers/structured-output.md | 23 +++--- i18n/hi/pages/servers/tools.md | 4 +- i18n/hi/pages/servers/uri-templates.md | 6 +- i18n/hi/pages/troubleshooting.md | 10 +-- i18n/hi/pages/whats-new.md | 66 ++++++++--------- i18n/ja/pages/advanced/low-level-server.md | 15 +++- i18n/ja/pages/advanced/middleware.md | 6 +- i18n/ja/pages/client/index.md | 13 ++-- i18n/ja/pages/client/transports.md | 18 ++--- i18n/ja/pages/deprecated.md | 68 ++++++++++++++++-- i18n/ja/pages/get-started/real-host.md | 8 +-- i18n/ja/pages/get-started/testing.md | 4 +- i18n/ja/pages/handlers/elicitation.md | 6 +- i18n/ja/pages/handlers/logging.md | 4 +- i18n/ja/pages/run/index.md | 4 +- i18n/ja/pages/servers/handling-errors.md | 54 +++++++++----- i18n/ja/pages/servers/media.md | 21 +++++- i18n/ja/pages/servers/prompts.md | 51 ++++++++++++- i18n/ja/pages/servers/structured-output.md | 18 +++-- i18n/ja/pages/servers/tools.md | 4 +- i18n/ja/pages/servers/uri-templates.md | 6 +- i18n/ja/pages/troubleshooting.md | 10 +-- i18n/ja/pages/whats-new.md | 8 +-- i18n/ko/pages/advanced/low-level-server.md | 15 +++- i18n/ko/pages/advanced/middleware.md | 11 +-- i18n/ko/pages/client/index.md | 16 +++-- i18n/ko/pages/client/transports.md | 16 ++--- i18n/ko/pages/deprecated.md | 67 +++++++++++++++-- i18n/ko/pages/get-started/real-host.md | 8 +-- i18n/ko/pages/get-started/testing.md | 8 +-- i18n/ko/pages/handlers/elicitation.md | 7 +- i18n/ko/pages/handlers/logging.md | 4 +- i18n/ko/pages/run/index.md | 4 +- i18n/ko/pages/servers/handling-errors.md | 59 ++++++++++----- i18n/ko/pages/servers/media.md | 21 +++++- i18n/ko/pages/servers/prompts.md | 51 ++++++++++++- i18n/ko/pages/servers/structured-output.md | 18 +++-- i18n/ko/pages/servers/tools.md | 4 +- i18n/ko/pages/servers/uri-templates.md | 6 +- i18n/ko/pages/troubleshooting.md | 10 +-- i18n/ko/pages/whats-new.md | 8 +-- i18n/pt/pages/advanced/low-level-server.md | 15 +++- i18n/pt/pages/advanced/middleware.md | 13 ++-- i18n/pt/pages/client/index.md | 18 ++--- i18n/pt/pages/client/transports.md | 16 ++--- i18n/pt/pages/deprecated.md | 70 ++++++++++++++++-- i18n/pt/pages/get-started/real-host.md | 6 +- i18n/pt/pages/get-started/testing.md | 6 +- i18n/pt/pages/handlers/elicitation.md | 9 +-- i18n/pt/pages/handlers/logging.md | 4 +- i18n/pt/pages/run/index.md | 4 +- i18n/pt/pages/servers/handling-errors.md | 59 ++++++++++----- i18n/pt/pages/servers/media.md | 21 +++++- i18n/pt/pages/servers/prompts.md | 51 ++++++++++++- i18n/pt/pages/servers/structured-output.md | 23 +++--- i18n/pt/pages/servers/tools.md | 4 +- i18n/pt/pages/servers/uri-templates.md | 11 +-- i18n/pt/pages/troubleshooting.md | 10 +-- i18n/pt/pages/whats-new.md | 8 +-- i18n/ru/pages/advanced/low-level-server.md | 15 +++- i18n/ru/pages/advanced/middleware.md | 6 +- i18n/ru/pages/client/index.md | 18 ++--- i18n/ru/pages/client/transports.md | 16 ++--- i18n/ru/pages/deprecated.md | 71 +++++++++++++++++-- i18n/ru/pages/get-started/real-host.md | 8 +-- i18n/ru/pages/get-started/testing.md | 9 +-- i18n/ru/pages/handlers/elicitation.md | 9 +-- i18n/ru/pages/handlers/logging.md | 4 +- i18n/ru/pages/run/index.md | 4 +- i18n/ru/pages/servers/handling-errors.md | 61 +++++++++++----- i18n/ru/pages/servers/media.md | 21 +++++- i18n/ru/pages/servers/prompts.md | 53 +++++++++++++- i18n/ru/pages/servers/structured-output.md | 25 ++++--- i18n/ru/pages/servers/tools.md | 4 +- i18n/ru/pages/servers/uri-templates.md | 13 ++-- i18n/ru/pages/troubleshooting.md | 10 +-- i18n/ru/pages/whats-new.md | 12 ++-- i18n/tr/pages/advanced/low-level-server.md | 15 +++- i18n/tr/pages/advanced/middleware.md | 15 ++-- i18n/tr/pages/client/index.md | 18 ++--- i18n/tr/pages/client/transports.md | 16 ++--- i18n/tr/pages/deprecated.md | 69 ++++++++++++++++-- i18n/tr/pages/get-started/real-host.md | 20 +++--- i18n/tr/pages/get-started/testing.md | 8 +-- i18n/tr/pages/handlers/elicitation.md | 9 +-- i18n/tr/pages/handlers/logging.md | 4 +- i18n/tr/pages/run/index.md | 4 +- i18n/tr/pages/servers/handling-errors.md | 59 ++++++++++----- i18n/tr/pages/servers/media.md | 21 +++++- i18n/tr/pages/servers/prompts.md | 51 ++++++++++++- i18n/tr/pages/servers/structured-output.md | 21 +++--- i18n/tr/pages/servers/tools.md | 4 +- i18n/tr/pages/servers/uri-templates.md | 13 ++-- i18n/tr/pages/troubleshooting.md | 10 +-- i18n/tr/pages/whats-new.md | 8 +-- i18n/uk/pages/advanced/low-level-server.md | 15 +++- i18n/uk/pages/advanced/middleware.md | 18 +++-- i18n/uk/pages/client/index.md | 18 ++--- i18n/uk/pages/client/transports.md | 18 ++--- i18n/uk/pages/deprecated.md | 71 +++++++++++++++++-- i18n/uk/pages/get-started/real-host.md | 6 +- i18n/uk/pages/get-started/testing.md | 10 +-- i18n/uk/pages/handlers/elicitation.md | 9 +-- i18n/uk/pages/handlers/logging.md | 4 +- i18n/uk/pages/run/index.md | 6 +- i18n/uk/pages/servers/handling-errors.md | 59 ++++++++++----- i18n/uk/pages/servers/media.md | 21 +++++- i18n/uk/pages/servers/prompts.md | 51 ++++++++++++- i18n/uk/pages/servers/structured-output.md | 23 +++--- i18n/uk/pages/servers/tools.md | 4 +- i18n/uk/pages/servers/uri-templates.md | 11 +-- i18n/uk/pages/troubleshooting.md | 10 +-- i18n/uk/pages/whats-new.md | 8 +-- .../pages/advanced/low-level-server.md | 17 ++++- i18n/zh-hant/pages/advanced/middleware.md | 6 +- i18n/zh-hant/pages/client/index.md | 13 ++-- i18n/zh-hant/pages/client/transports.md | 16 ++--- i18n/zh-hant/pages/deprecated.md | 68 ++++++++++++++++-- i18n/zh-hant/pages/get-started/real-host.md | 6 +- i18n/zh-hant/pages/get-started/testing.md | 4 +- i18n/zh-hant/pages/handlers/elicitation.md | 6 +- i18n/zh-hant/pages/handlers/logging.md | 4 +- i18n/zh-hant/pages/run/index.md | 4 +- i18n/zh-hant/pages/servers/handling-errors.md | 52 ++++++++++---- i18n/zh-hant/pages/servers/media.md | 21 +++++- i18n/zh-hant/pages/servers/prompts.md | 51 ++++++++++++- .../pages/servers/structured-output.md | 18 +++-- i18n/zh-hant/pages/servers/tools.md | 4 +- i18n/zh-hant/pages/servers/uri-templates.md | 6 +- i18n/zh-hant/pages/troubleshooting.md | 10 +-- i18n/zh-hant/pages/whats-new.md | 8 +-- i18n/zh/pages/advanced/low-level-server.md | 15 +++- i18n/zh/pages/advanced/middleware.md | 6 +- i18n/zh/pages/client/index.md | 13 ++-- i18n/zh/pages/client/transports.md | 16 ++--- i18n/zh/pages/deprecated.md | 68 ++++++++++++++++-- i18n/zh/pages/get-started/real-host.md | 6 +- i18n/zh/pages/get-started/testing.md | 4 +- i18n/zh/pages/handlers/elicitation.md | 6 +- i18n/zh/pages/handlers/logging.md | 4 +- i18n/zh/pages/run/index.md | 4 +- i18n/zh/pages/servers/handling-errors.md | 52 ++++++++++---- i18n/zh/pages/servers/media.md | 21 +++++- i18n/zh/pages/servers/prompts.md | 51 ++++++++++++- i18n/zh/pages/servers/structured-output.md | 18 +++-- i18n/zh/pages/servers/tools.md | 4 +- i18n/zh/pages/servers/uri-templates.md | 8 +-- i18n/zh/pages/troubleshooting.md | 10 +-- i18n/zh/pages/whats-new.md | 8 +-- 216 files changed, 3149 insertions(+), 1047 deletions(-) diff --git a/i18n/de/pages/advanced/low-level-server.md b/i18n/de/pages/advanced/low-level-server.md index 55f07bdffd..a84bc12e4e 100644 --- a/i18n/de/pages/advanced/low-level-server.md +++ b/i18n/de/pages/advanced/low-level-server.md @@ -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. diff --git a/i18n/de/pages/advanced/middleware.md b/i18n/de/pages/advanced/middleware.md index b4879126c4..b378872a29 100644 --- a/i18n/de/pages/advanced/middleware.md +++ b/i18n/de/pages/advanced/middleware.md @@ -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. diff --git a/i18n/de/pages/client/index.md b/i18n/de/pages/client/index.md index 84de50aa17..4a5a9361c2 100644 --- a/i18n/de/pages/client/index.md +++ b/i18n/de/pages/client/index.md @@ -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 diff --git a/i18n/de/pages/client/transports.md b/i18n/de/pages/client/transports.md index b7870b5883..b605938f45 100644 --- a/i18n/de/pages/client/transports.md +++ b/i18n/de/pages/client/transports.md @@ -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. diff --git a/i18n/de/pages/deprecated.md b/i18n/de/pages/deprecated.md index 6cffeac039..0887f2a8ca 100644 --- a/i18n/de/pages/deprecated.md +++ b/i18n/de/pages/deprecated.md @@ -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. diff --git a/i18n/de/pages/get-started/real-host.md b/i18n/de/pages/get-started/real-host.md index 7501860170..7dfc017068 100644 --- a/i18n/de/pages/get-started/real-host.md +++ b/i18n/de/pages/get-started/real-host.md @@ -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} diff --git a/i18n/de/pages/get-started/testing.md b/i18n/de/pages/get-started/testing.md index f18e36953a..f550140247 100644 --- a/i18n/de/pages/get-started/testing.md +++ b/i18n/de/pages/get-started/testing.md @@ -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, diff --git a/i18n/de/pages/handlers/elicitation.md b/i18n/de/pages/handlers/elicitation.md index 69c0599d8a..28641e3eac 100644 --- a/i18n/de/pages/handlers/elicitation.md +++ b/i18n/de/pages/handlers/elicitation.md @@ -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 ` 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} diff --git a/i18n/de/pages/handlers/logging.md b/i18n/de/pages/handlers/logging.md index 4c56f0fa3e..b343ec9e0a 100644 --- a/i18n/de/pages/handlers/logging.md +++ b/i18n/de/pages/handlers/logging.md @@ -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: diff --git a/i18n/de/pages/run/index.md b/i18n/de/pages/run/index.md index 53969cf207..54e65f7ee5 100644 --- a/i18n/de/pages/run/index.md +++ b/i18n/de/pages/run/index.md @@ -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`. diff --git a/i18n/de/pages/servers/handling-errors.md b/i18n/de/pages/servers/handling-errors.md index 31e1c988da..43869e674e 100644 --- a/i18n/de/pages/servers/handling-errors.md +++ b/i18n/de/pages/servers/handling-errors.md @@ -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 ` 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)**. diff --git a/i18n/de/pages/servers/media.md b/i18n/de/pages/servers/media.md index 4b0780b32e..3303cf3fc8 100644 --- a/i18n/de/pages/servers/media.md +++ b/i18n/de/pages/servers/media.md @@ -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. diff --git a/i18n/de/pages/servers/prompts.md b/i18n/de/pages/servers/prompts.md index 4a8e6e3e7a..64983f83f8 100644 --- a/i18n/de/pages/servers/prompts.md +++ b/i18n/de/pages/servers/prompts.md @@ -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)**. diff --git a/i18n/de/pages/servers/structured-output.md b/i18n/de/pages/servers/structured-output.md index f35159545c..b1317cd312 100644 --- a/i18n/de/pages/servers/structured-output.md +++ b/i18n/de/pages/servers/structured-output.md @@ -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)**. diff --git a/i18n/de/pages/servers/tools.md b/i18n/de/pages/servers/tools.md index 70def651ca..a87204ad60 100644 --- a/i18n/de/pages/servers/tools.md +++ b/i18n/de/pages/servers/tools.md @@ -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. diff --git a/i18n/de/pages/servers/uri-templates.md b/i18n/de/pages/servers/uri-templates.md index 5160a6e07e..246842856f 100644 --- a/i18n/de/pages/servers/uri-templates.md +++ b/i18n/de/pages/servers/uri-templates.md @@ -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} diff --git a/i18n/de/pages/troubleshooting.md b/i18n/de/pages/troubleshooting.md index 3ed6eb24e5..84c61bb842 100644 --- a/i18n/de/pages/troubleshooting.md +++ b/i18n/de/pages/troubleshooting.md @@ -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 : ` und `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `, `Error executing tool ` und `Unknown tool: ` {#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 ` 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 '' 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: ` (das Server-Log). Lösung: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`. diff --git a/i18n/de/pages/whats-new.md b/i18n/de/pages/whats-new.md index 460cd4bc71..a45f5a5918 100644 --- a/i18n/de/pages/whats-new.md +++ b/i18n/de/pages/whats-new.md @@ -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 `, 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)**. diff --git a/i18n/es/pages/advanced/low-level-server.md b/i18n/es/pages/advanced/low-level-server.md index 1068b8956f..a9fe35e242 100644 --- a/i18n/es/pages/advanced/low-level-server.md +++ b/i18n/es/pages/advanced/low-level-server.md @@ -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. diff --git a/i18n/es/pages/advanced/middleware.md b/i18n/es/pages/advanced/middleware.md index 7ad8f1b7c8..1739c1f1ab 100644 --- a/i18n/es/pages/advanced/middleware.md +++ b/i18n/es/pages/advanced/middleware.md @@ -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 diff --git a/i18n/es/pages/client/index.md b/i18n/es/pages/client/index.md index 4df0e27323..f2bb09a509 100644 --- a/i18n/es/pages/client/index.md +++ b/i18n/es/pages/client/index.md @@ -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 --- # El cliente {#the-client} @@ -27,9 +27,10 @@ El servidor del principio solo está ahí para que tengas algo a lo que conectar * Una instancia de `MCPServer` (o del `Server` de bajo nivel): se conecta **en el mismo proceso**. * Una cadena con una URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, el camino de producción. -* Un **transporte**: cualquier cosa que puedas usar con `async with ... as (read, write)`, como `stdio_client(...)` envolviendo un subproceso. +* Un `StdioServerParameters`: el comando que se lanza como **subproceso**, con el que se habla a través de su stdin y su stdout. +* Un **transporte**: cualquier cosa que puedas usar con `async with ... as (read, write)`, como `streamable_http_client(url, http_client=...)` envolviendo tu propio cliente HTTP. -Todo lo demás en esta página es idéntico en los tres casos. Los encabezados, los subprocesos, los timeouts y el protocolo `Transport` tienen su propia página: **[Transportes del cliente](transports.md)**. +Todo lo demás en esta página es idéntico en los cuatro casos. Los encabezados, los subprocesos, los timeouts y el protocolo `Transport` tienen su propia página: **[Transportes del cliente](transports.md)**. ### Qué hay en un cliente conectado {#whats-on-a-connected-client} @@ -85,7 +86,7 @@ Ese esquema es todo lo que una UI necesita para renderizar un formulario de argu `call_tool(name, arguments)` ejecuta la herramienta y te devuelve un `CallToolResult`. -```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 @@ Una herramienta que lanza una excepción **no** la lanza en tu cliente. Vuelve c !!! check Pídele `"Solaris"` a `lookup_book` (un título que no está en el catálogo) y la función lanza - `ValueError`. Aun así, la llamada devuelve un resultado normal: + `ToolError`. Aun así, la llamada devuelve un resultado normal: ```python result.is_error # True @@ -125,9 +126,10 @@ Una herramienta que lanza una excepción **no** la lanza en tu cliente. Vuelve c result.structured_content # None ``` - El mensaje de la excepción acabó en `content`, donde el **modelo** puede leerlo y volver a intentarlo. Es - deliberado: un error de herramienta es parte de la conversación, no un fallo fatal. Mira siempre `is_error` - antes de confiar en `structured_content`. + El mensaje del `ToolError` acabó en `content`, donde el **modelo** puede leerlo y volver a intentarlo. Es + deliberado: un error de herramienta es parte de la conversación, no un fallo fatal. (Si la herramienta hubiera + fallado con alguna otra excepción, `content` diría solo `Error executing tool lookup_book`.) Mira siempre + `is_error` antes de confiar en `structured_content`. !!! warning `is_error=True` cubre más que tu propio `raise`. Pide una herramienta que el servidor ni siquiera tiene diff --git a/i18n/es/pages/client/transports.md b/i18n/es/pages/client/transports.md index 4ab1e720dc..4f25dc11f1 100644 --- a/i18n/es/pages/client/transports.md +++ b/i18n/es/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # Transportes del cliente {#client-transports} @@ -87,15 +87,15 @@ estándar `SSL_CERT_FILE`/`SSL_CERT_DIR` o pasa un `verify=ssl_context` explíci Un servidor **stdio** es un subproceso. El cliente lo lanza, escribe JSON-RPC en su stdin y lee JSON-RPC de su stdout. Así es como un host de escritorio ejecuta un servidor en tu máquina: un host *es* este código más una interfaz de usuario, y **[Conectar a un host real](../get-started/real-host.md)** es la misma relación vista desde el lado del host, como archivo de configuración. -Describe el proceso con `StdioServerParameters`, conviértelo en un transporte con `stdio_client` y entrega *eso* a `Client`: +Describe el proceso con `StdioServerParameters` y entrégaselo a `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` no acepta el objeto de parámetros por sí solo. `StdioServerParameters` es configuración; `stdio_client(server)` es el transporte que sabe lanzar un proceso a partir de ella. Envuélvelo siempre. +Entrar en el bloque lanza el proceso. Salir de él cierra el subproceso: cierra stdin, espera y lo mata si se queda. Nunca lo limpias tú. -Salir del bloque `async with` también cierra el subproceso: cierra stdin, espera y lo mata si se queda. Nunca lo limpias tú. +El stderr del proceso hijo va al tuyo. Para enviarlo a otro sitio, construye tú mismo el transporte con `stdio_client` (de `mcp`) y pasa eso en su lugar: `Client(stdio_client(server, errlog=log_file))`. !!! warning El proceso hijo **no** hereda tu entorno. Recibe una lista de permitidos mínima (`HOME`, `LOGNAME`, @@ -113,16 +113,16 @@ Salir del bloque `async with` también cierra el subproceso: cierra stdin, esper Para `Client`, todo lo anterior es lo mismo. -Un **transporte** es cualquier gestor de contexto asíncrono que produce un par `(read, write)` de flujos de mensajes: formalmente, el protocolo `Transport` de `mcp.client`. `Client` resuelve su argumento por tipo: un objeto de servidor se conecta dentro del proceso, un `str` se convierte en `streamable_http_client(url)` y cualquier otra cosa se entra directamente como transporte. Esa última regla es la razón por la que `stdio_client(...)`, `streamable_http_client(...)` y `sse_client(...)` encajan todos en el mismo hueco, y por la que puedes escribir el tuyo. +Un **transporte** es cualquier gestor de contexto asíncrono que produce un par `(read, write)` de flujos de mensajes: formalmente, el protocolo `Transport` de `mcp.client`. `Client` resuelve su argumento por tipo: un objeto de servidor se conecta dentro del proceso, un `str` se convierte en `streamable_http_client(url)`, un `StdioServerParameters` se convierte en `stdio_client(params)` y cualquier otra cosa se entra directamente como transporte. Esa última regla es la razón por la que `stdio_client(...)`, `streamable_http_client(...)` y `sse_client(...)` encajan todos en el mismo hueco, y por la que puedes escribir el tuyo. ## Resumen {#recap} * `Client(mcp)` (el objeto del servidor) se conecta en memoria. Úsalo para pruebas y para integración. * `Client("http://.../mcp")` (una URL) se conecta por Streamable HTTP, el transporte de producción. * Los encabezados, la autenticación, los proxies y los timeouts van en un `httpx2.AsyncClient` que pasas a `streamable_http_client(url, http_client=...)`. No existe el argumento nombrado `headers=`. -* stdio es `Client(stdio_client(StdioServerParameters(...)))`, nunca el objeto de parámetros solo. +* stdio es `Client(StdioServerParameters(...))`. Envuélvelo tú mismo en `stdio_client(...)` solo para redirigir el stderr del proceso hijo. * El subproceso recibe un entorno con lista de permitidos, no el tuyo; `env=` se añade a él. -* Un transporte es cualquier cosa con la que puedas hacer `async with x as (read, write)`. `Client` entrega directamente a ese protocolo todo lo que no sea un objeto de servidor ni una URL. +* Un transporte es cualquier cosa con la que puedas hacer `async with x as (read, write)`. `Client` entrega directamente a ese protocolo todo lo que no sea un objeto de servidor, una URL ni un `StdioServerParameters`. * Construir un `Client` elige el transporte. `async with` lo abre. Una vez abierto el transporte, los dos lados tienen que acordar una versión del protocolo. Normalmente nunca piensas en ello; cuando lo hagas, **[Versiones del protocolo](../protocol-versions.md)** es la página. diff --git a/i18n/es/pages/deprecated.md b/i18n/es/pages/deprecated.md index 1df3d683df..43beb87a8c 100644 --- a/i18n/es/pages/deprecated.md +++ b/i18n/es/pages/deprecated.md @@ -1,11 +1,11 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # Funcionalidades obsoletas {#deprecated-features} -La especificación 2026-07-28 retira cinco cosas. El SDK sigue implementando todas y cada una, y todas llevan ahora un **aviso de obsolescencia**. +La especificación 2026-07-28 retira cinco cosas. El SDK sigue implementando todas y cada una, y todas llevan ahora un **aviso de obsolescencia**. Una función auxiliar del SDK queda obsoleta por su cuenta y aparece [al final](#deprecated-sdk-helpers). La tabla siguiente nombra cada funcionalidad obsoleta, explica por qué desaparece e indica el reemplazo sobre el que construir. @@ -55,6 +55,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE intenta enviar. Estas dos funcionalidades solo funcionan de extremo a extremo en una conexión `mode="legacy"` cuyo cliente registró el callback correspondiente. +## `ping` en una sesión heredada {#ping-on-a-legacy-session} + +Un **ping** es una solicitud vacía que cualquiera de los dos lados puede enviar para comprobar que el otro sigue respondiendo. La especificación 2026-07-28 lo elimina ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)): cada solicitud que envía un cliente moderno ya demuestra que el servidor está ahí, y un servidor moderno no tiene canal por el que enviar uno. Ambos métodos del SDK siguen funcionando en una sesión de la generación del handshake. Desde el cliente: + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +Y desde el servidor, dentro de cualquier 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()` avisa con `MCPDeprecationWarning` en cada llamada. En una conexión por defecto (`2026-07-28`), el servidor responde `MCPError: Method not found` en su lugar. +* `ctx.session.send_ping()` no lleva ningún aviso. En una conexión moderna lanza el mismo error de falta de canal de retorno (back-channel) que cualquier otra solicitud iniciada por el servidor. +* Ninguno de los dos lados registra nada para responder a un ping. + +## Notificaciones de cambio de roots {#roots-change-notifications} + +Un cliente de la generación 2025 que declaró la capacidad roots puede avisar al servidor de que sus carpetas del espacio de trabajo cambiaron enviando `notifications/roots/list_changed`; el servidor responde solicitando `roots/list` de nuevo. La especificación 2026-07-28 elimina la notificación junto con el resto del flujo de roots de estilo push. En el cliente, pasar `list_roots_callback=` (**[Callbacks del cliente](client/callbacks.md)**) es lo que declara `"roots": {"listChanged": true}`, y una llamada cumple esa promesa: + +```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() +``` + +En el servidor, el `Server` de bajo nivel acepta el handler que la recibe: + +```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` es la lista que devuelve tu `list_roots_callback`. `client.send_roots_list_changed()` avisa, y necesita un cliente `mode="legacy"`: en una conexión moderna la notificación se descarta en silencio. Mantén la sesión abierta después, porque el `roots/list` posterior del servidor llega por ella. +* `MCPServer` no tiene ningún hook para la notificación. En el `Server` de bajo nivel, `on_roots_list_changed=` registra el handler (también obsoleto, y avisa en la construcción). La notificación no lleva payload, así que el handler llama a `ctx.session.list_roots()` para obtener la lista nueva. + ## Silenciar el aviso {#silencing-the-warning} No lo hagas en código nuevo. @@ -75,22 +124,33 @@ Esa es toda la API. No hay un interruptor por método, y tampoco lo quieres: la Aplica el filtro al revés y obtienes una prueba de regresión gratis. Añade `"error::mcp.MCPDeprecationWarning"` al ajuste `filterwarnings` de tu configuración de pytest y la llamada obsoleta **lanza una excepción** en lugar de avisar. Una herramienta - llamada `old_log` que todavía llama a `ctx.info()` deja de pasar y empieza a informar: + llamada `old_log` que todavía llama a `ctx.info()` deja de pasar: la llamada vuelve con + `is_error=True` y `Error executing tool old_log`, y el log capturado del servidor señala + al culpable: ```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). ``` Una línea de configuración de pytest, y una llamada obsoleta nunca podrá volver a colarse en tu código sin que falle una prueba. +## Funciones auxiliares del SDK obsoletas {#deprecated-sdk-helpers} + +No son cambios de la especificación, solo detalles internos del SDK con un reemplazo mejor. Avisan con el mismo `MCPDeprecationWarning` y se eliminarán en 3.0. + +| Obsoleto | Qué hacer en su lugar | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()` y después `FuncMetadata.call_fn()`. Solo lo llamaba el código que maneja `FuncMetadata` directamente (una subclase propia de `Tool`, por ejemplo). | + ## Resumen {#recap} * La especificación 2026-07-28 deja obsoletos los **roots**, el **muestreo** iniciado por el servidor y el **registro de logs** del protocolo (todo en [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), restringe el **progreso** a la dirección servidor a cliente y elimina **`ping`**. * La columna de reemplazos te indica el camino: **[Solicitudes de varias idas y vueltas](handlers/multi-round-trip.md)** para el muestreo y los roots, **[Registro de logs](handlers/logging.md)** para los logs, **[Progreso](handlers/progress.md)** para el progreso. `ping` no necesita nada en absoluto. * Obsoleto es solo un aviso: no hay cambios en lo que se transmite, todo sigue funcionando contra sesiones anteriores a 2026 y recibes un `MCPDeprecationWarning` visible (un `UserWarning`, así que está activo por defecto). -* El muestreo y los roots necesitan además un canal de retorno (back-channel) que una sesión 2026-07-28 no tiene. En una conexión moderna avisan y después lanzan una excepción. +* El muestreo y los roots necesitan además un canal de retorno que una sesión 2026-07-28 no tiene. En una conexión moderna avisan y después lanzan una excepción. * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` silencia toda la categoría; `"error::mcp.MCPDeprecationWarning"` en pytest la convierte en un fallo de prueba. +* Una función auxiliar del SDK, `FuncMetadata.call_fn_with_arg_validation()`, queda obsoleta por separado y se eliminará en 3.0. * El código nuevo no debería construirse sobre nada de esto. Todas las demás páginas de esta documentación enseñan la API actual. diff --git a/i18n/es/pages/get-started/real-host.md b/i18n/es/pages/get-started/real-host.md index 1e4492dda7..c2a8f24f67 100644 --- a/i18n/es/pages/get-started/real-host.md +++ b/i18n/es/pages/get-started/real-host.md @@ -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 --- # Conectarse a un host real {#connect-to-a-real-host} @@ -11,7 +11,7 @@ Esto significa que conectarse a un host es un único acto: le indicas **el coman ## Un servidor, todos los hosts {#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" ``` @@ -50,7 +50,7 @@ También es el comando que `mcp install` escribe por ti en la configuración de Y un host no es más que una aplicación con un cliente MCP dentro, así que tu propio código Python puede hacer el papel del host: **[Transportes del cliente](../client/transports.md)** lanza - este mismo archivo como subproceso con `stdio_client(...)`, y **[Pruebas](testing.md)** + este mismo archivo como subproceso con `Client(StdioServerParameters(...))`, y **[Pruebas](testing.md)** se conecta a él en memoria, sin ningún proceso. ## Claude Desktop {#claude-desktop} diff --git a/i18n/es/pages/get-started/testing.md b/i18n/es/pages/get-started/testing.md index c3d2727792..2c11eaacf2 100644 --- a/i18n/es/pages/get-started/testing.md +++ b/i18n/es/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # Pruebas {#testing} @@ -84,9 +84,10 @@ async def test_call_add_tool(client: Client): Pueden fallar dos cosas distintas, y este indicador solo afecta a una de ellas. Una excepción dentro de una de **tus herramientas** no es un fallo del protocolo. Se convierte en un -resultado normal con `is_error=True`, y el modelo lee el mensaje. `raise_exceptions` no cambia eso: -con o sin él, `call_tool` devuelve el mismo resultado con `is_error=True`. Hay una página entera -dedicada a esto: **[Manejo de errores](../servers/handling-errors.md)**. +resultado normal con `is_error=True` (y si era un `ToolError`, el modelo lee tu mensaje). +`raise_exceptions` no cambia eso: con o sin él, `call_tool` devuelve el mismo resultado con +`is_error=True`. Hay una página entera dedicada a esto: +**[Manejo de errores](../servers/handling-errors.md)**. Un fallo **fuera** del cuerpo de una herramienta es otra cosa. En la conexión que te da `Client(mcp)`, el servidor lo depura y lo convierte en un genérico `"Internal server error"` antes de diff --git a/i18n/es/pages/handlers/elicitation.md b/i18n/es/pages/handlers/elicitation.md index 927c080823..216193ff78 100644 --- a/i18n/es/pages/handlers/elicitation.md +++ b/i18n/es/pages/handlers/elicitation.md @@ -1,6 +1,6 @@ --- translation: - sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] + sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] tool: 1 --- # Elicitación {#elicitation} @@ -90,7 +90,8 @@ Ese esquema es el formulario. `Field(description=...)` es la etiqueta; un valor Un esquema de elicitación no es tan expresivo como el esquema de entrada de una herramienta. Solo campos planos y primitivos: `str`, `int`, `float`, `bool` o un `Literal` de cadenas (se convierte en un `enum`). Pon un modelo dentro del modelo y `ctx.elicit` lanza una excepción - antes de que se envíe nada al cliente: + antes de que se envíe nada al cliente. La llamada a la herramienta falla con + `Error executing tool `, y el log de tu servidor tiene el motivo: ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -113,8 +114,8 @@ Una negativa no es un error. La herramienta decide qué significa rechazar (aqu !!! tip La respuesta se valida contra tu modelo antes de que tu código la vea. Un cliente que envía - `"maybe"` para un `bool` no corrompe tu reserva: la llamada falla con un error de - discrepancia de esquema y tu `if` nunca se ejecuta. + `"maybe"` para un `bool` no corrompe tu reserva: `ctx.elicit` lanza `ValueError`, la llamada + falla y tu `if` nunca se ejecuta. ## Enviar al usuario a una URL {#send-the-user-to-a-url} diff --git a/i18n/es/pages/handlers/logging.md b/i18n/es/pages/handlers/logging.md index ad35f6a991..4d7ed89a23 100644 --- a/i18n/es/pages/handlers/logging.md +++ b/i18n/es/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # Registro de logs {#logging} @@ -55,6 +55,8 @@ El valor por defecto es `"INFO"`. `logging.basicConfig()` nunca reemplaza handlers que ya existen. Si configuras el logging tú mismo antes de crear el servidor, tu configuración gana. +Tampoco necesitas un `try`/`except` en cada handler solo para registrar los fallos. Cuando una función de herramienta o de recurso lanza una excepción, el SDK la registra por ti. **[Manejo de errores](../servers/handling-errors.md#any-other-exception)** explica qué se registra y con qué nivel. + ## Pruébalo {#try-it} Ejecuta el servidor con el MCP Inspector: diff --git a/i18n/es/pages/run/index.md b/i18n/es/pages/run/index.md index 7ad6328cf4..a8964b70e7 100644 --- a/i18n/es/pages/run/index.md +++ b/i18n/es/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # Ejecutar el servidor {#running-your-server} @@ -72,7 +72,7 @@ Cada transporte tiene sus propios argumentos nombrados, todos en `run()`: * `streamable_http_path`: dónde vive el endpoint MCP. Por defecto `/mcp`. * `json_response=True`: responde a cada POST con un único cuerpo JSON en lugar de un flujo SSE. Ese cuerpo tiene sitio para la respuesta y nada más, así que una herramienta que llama de vuelta al cliente a mitad de solicitud (`ctx.elicit()`, muestreo (sampling)) lanza `NoBackChannelError` en este tramo, y las notificaciones ligadas a la llamada en curso (el progreso de `ctx.report_progress()`, los mensajes de log por llamada) se descartan; el flujo `GET` independiente sigue llevando las que no están relacionadas. * `stateless_http=True`: un transporte nuevo por solicitud, sin seguimiento de sesión. -* `max_request_body_size`: el cuerpo POST más grande que se acepta, en bytes. Es 4 MiB por defecto; las solicitudes mayores +* `max_request_body_size`: el cuerpo de solicitud más grande que se acepta, en bytes. Es 4 MiB por defecto; las solicitudes mayores reciben HTTP 413 antes del análisis o de la creación de la sesión. Súbelo solo cuando los mensajes MCP legítimos superen ese tamaño. * `event_store`, `retry_interval`, `transport_security`: reanudabilidad y protección contra DNS rebinding. Pueden esperar hasta que despliegues en algún lugar que no sea localhost; **[Desplegar y escalar](deploy.md)** cubre `transport_security`. diff --git a/i18n/es/pages/servers/handling-errors.md b/i18n/es/pages/servers/handling-errors.md index f417a5c06c..c714a9c2de 100644 --- a/i18n/es/pages/servers/handling-errors.md +++ b/i18n/es/pages/servers/handling-errors.md @@ -1,13 +1,13 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # Manejo de errores {#handling-errors} -Una herramienta puede fallar de dos maneras, y el SDK las trata de forma muy distinta. +Una herramienta puede fallar de tres maneras, y el SDK trata cada una de forma distinta. -Lanza una excepción ordinaria y la ve el **modelo**. Lanza `MCPError` y la ve el **protocolo**. +Lanza `ToolError` y el **modelo** ve tu mensaje. Lanza `MCPError` y lo ve el **protocolo**. Lanza cualquier otra cosa y es un fallo inesperado: el modelo solo se entera de que la llamada falló, y el traceback va a tu log. Esta página trata de cómo elegir. @@ -15,11 +15,11 @@ Esta página trata de cómo elegir. Toma una herramienta que busca algo, y deja que la búsqueda falle: -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -No hay nada de MCP en esas dos líneas. `get_author` lanza un `ValueError` común y corriente, como lo haría cualquier función de Python. +`ToolError`, de `mcp.server.mcpserver.exceptions`, es la forma en que una herramienta le dice al modelo que algo salió mal. Llámala con un título que no esté en el catálogo y observa el resultado: @@ -30,13 +30,15 @@ result.structured_content # None ``` * La solicitud **tuvo éxito**. Hay un resultado; no se lanzó nada del lado de quien llama. -* `is_error` es `True`, y el mensaje de tu excepción (con el nombre de la herramienta como prefijo) está en `content`, justo donde lee el modelo. +* `is_error` es `True`, y tu mensaje (con el nombre de la herramienta como prefijo) está en `content`, justo donde lee el modelo. * `structured_content` es `None`. Una llamada fallida no tiene valor devuelto que estructurar. -Esto es un **error de herramienta**, y es el comportamiento por defecto para *cualquier* excepción que lance tu herramienta. Además, casi siempre es lo que quieres. +Esto es un **error de herramienta**, y casi siempre es lo que quieres. El modelo es quien llama a tu herramienta. Él eligió los argumentos. Así que un error de herramienta es un turno de la conversación: el modelo lee *"No book titled 'Nothing' in the catalog."*, se da cuenta de que adivinó mal el título y vuelve a llamar con uno mejor. Escribiste un `raise` y obtuviste un agente que se corrige solo. +En el servidor, un `ToolError` es una sola línea `INFO` en el log, sin traceback. Lo veías venir, así que no hay nada que investigar. + !!! tip Nunca devuelvas con `return` un mensaje de error desde una herramienta. Una cadena devuelta tiene `is_error=False`, así que para el modelo (y para toda interfaz de cliente) parece que la @@ -44,7 +46,7 @@ El modelo es quien llama a tu herramienta. Él eligió los argumentos. Así que ## Un error que el modelo no puede corregir {#an-error-the-model-cannot-fix} -Ahora cambia `ValueError` por `MCPError`. +Ahora cambia `ToolError` por `MCPError`. ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -77,10 +79,10 @@ Ahora cambia `ValueError` por `MCPError`. Los dos caminos responden a dos preguntas distintas. -* **Lanza cualquier excepción** ante un fallo de *ejecución*: lo que tu herramienta intentó hacer no funcionó. El modelo eligió la llamada, así que el modelo debería ver la consecuencia y tener la oportunidad de recuperarse. Un título mal escrito, una API externa que agotó el tiempo de espera, una fila que no existe: todos son errores de herramienta. +* **Lanza `ToolError`** ante un fallo de *ejecución*: lo que tu herramienta intentó hacer no funcionó. El modelo eligió la llamada, así que el modelo debería ver la consecuencia y tener la oportunidad de recuperarse. Un título mal escrito, una API externa que agotó el tiempo de espera, una fila que no existe: todos son errores de herramienta. * **Lanza `MCPError`** cuando debe rechazarse la *solicitud misma*: al cliente le falta una capacidad de la que depende tu herramienta, el servidor no está en condiciones de atender a nadie, quien llama se saltó un paso obligatorio. Ningún reintento del modelo arregla nada de eso, así que no se gana nada entregándole el mensaje. -Una sola pregunta lo decide: **¿podría haberlo evitado un modelo más inteligente?** Sí -> excepción ordinaria. No -> `MCPError`. +Una sola pregunta lo decide: **¿podría haberlo evitado un modelo más inteligente?** Sí -> `ToolError`. No -> `MCPError`. Según ese criterio, la segunda versión de `get_author` eligió mal: un título mejor lo arregla, así que el modelo merecía ver el mensaje. Está ahí para mostrarte el mecanismo, no para recomendarlo. @@ -89,6 +91,25 @@ Según ese criterio, la segunda versión de `get_author` eligió mal: un título opcional `data`. Lo que pongas en ellos es lo que recibe el cliente: el SDK reenvía un `MCPError` lanzado tal cual, en lugar de sanearlo. +## Cualquier otra excepción {#any-other-exception} + +Ahora quita la comprobación y deja que la búsqueda en el diccionario falle por sí sola: + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]` lanza `KeyError`. No lo tenías previsto, así que el SDK lo trata como un fallo inesperado: + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +La llamada sigue devolviendo `is_error=True`, así que el modelo sabe que falló y puede seguir adelante. Lo que no recibe es el texto de la excepción: un `KeyError` de tu código, o un montón de SQL de un driver tres bibliotecas más abajo, puede describir el funcionamiento interno de tu servidor, así que nunca sale del servidor. + +Lo recibes tú. El servidor registra el fallo inesperado en nivel `ERROR` con el traceback completo, como `Tool 'get_author' raised an unexpected exception`. Así, un log de producción en `WARNING` se mantiene en silencio ante cada `ToolError` y habla en cuanto algo está realmente roto. + ## Un recurso que no existe {#a-resource-that-doesnt-exist} Los recursos trazan la misma línea, e incluyen una excepción con nombre propio para el caso común. @@ -109,7 +130,7 @@ Cuando no pueda, lanza `ResourceNotFoundError`. El SDK lo convierte en el error } ``` -Fíjate en que aquí no hay un resultado a medias con `is_error=True`. La lectura de un recurso devuelve contenido o falla: los recursos solo tienen el camino del protocolo. Las plantillas y todo lo demás sobre recursos están en **[Recursos](resources.md)**. +Fíjate en que aquí no hay un resultado a medias con `is_error=True`. La lectura de un recurso devuelve contenido o falla: los recursos solo tienen el camino del protocolo. `ResourceError` es lo mismo para un fallo que no es "no encontrado" (`-32603`, tu mensaje), y ambos son una sola línea `INFO` en tu log. Cualquier otra excepción salvo `MCPError` es un fallo inesperado: el cliente recibe un `-32603` que solo nombra la URI, y el traceback va a tu log en nivel `ERROR`. Las plantillas y todo lo demás sobre recursos están en **[Recursos](resources.md)**. ## Errores que nunca lanzas {#errors-you-never-raise} @@ -120,20 +141,22 @@ Envíale a `get_author` un `title` que no sea una cadena y el SDK lo rechaza con Eso significa toda una clase de sentencias `raise` que no escribes: no vuelvas a validar tus propias anotaciones de tipo. !!! info - Todo lo de esta página es lo que ve un **cliente**, y el `Client` en memoria con el que - escribirás pruebas ve exactamente lo mismo. Ni siquiera `raise_exceptions=True` convierte un - error de herramienta de nuevo en un traceback: para cuando ese indicador podría actuar, tu - excepción ya es el resultado con `is_error=True`. Haz las aserciones sobre el resultado. + Todo lo que ve un **cliente** en esta página lo ve también el `Client` en memoria con el que + escribirás pruebas. Ni siquiera `raise_exceptions=True` le devuelve a quien llama la excepción + de una herramienta que falla: para cuando ese indicador podría actuar, tu excepción ya es el + resultado con `is_error=True`. Haz las aserciones sobre el resultado. Si necesitas el traceback + de un fallo inesperado, está en el log del servidor, y el `caplog` de pytest lo captura. **[Pruebas](../get-started/testing.md)** cubre el patrón. ## Resumen {#recap} -* Lanza **cualquier excepción** en una herramienta -> la llamada devuelve `is_error=True` con tu mensaje en `content`. El modelo lo lee y puede reintentar. Este es el comportamiento por defecto. +* Lanza **`ToolError`** en una herramienta -> la llamada devuelve `is_error=True` con tu mensaje en `content`. El modelo lo lee y puede reintentar. * Lanza **`MCPError`** -> la llamada misma falla con un error JSON-RPC. El modelo no ve nada; el host se encarga. `code`, `message` y `data` sobreviven intactos. -* La pregunta decisiva: *¿podría haberlo evitado un modelo más inteligente?* Sí -> excepción. No -> `MCPError`. +* La pregunta decisiva: *¿podría haberlo evitado un modelo más inteligente?* Sí -> `ToolError`. No -> `MCPError`. +* Cualquier **otra excepción** es un fallo inesperado -> `is_error=True` con solo `Error executing tool ` para el modelo, y un registro `ERROR` con el traceback para ti. * `ResourceNotFoundError` desde un handler de recurso -> el `-32602` del protocolo, con la URI en `data`. * Los argumentos incorrectos se rechazan contra el esquema antes de que se ejecute tu función; para esos no usas `raise`. -* `from mcp import MCPError`; las constantes de códigos de error vienen de `mcp.types`. +* Importaciones: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, y las constantes de códigos de error de `mcp.types`. Errores resueltos. Eso es todo lo que un servidor *expone*. Lo que cada handler puede leer, y hacer de vuelta hacia el cliente mientras se ejecuta, es la siguiente sección: **[Dentro de tu handler](../handlers/index.md)**. diff --git a/i18n/es/pages/servers/media.md b/i18n/es/pages/servers/media.md index a19451c00e..08f449074c 100644 --- a/i18n/es/pages/servers/media.md +++ b/i18n/es/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # Multimedia {#media} @@ -86,6 +86,24 @@ Un sufijo que no reconoce recurre a `application/octet-stream`. `Audio` así a partir de bytes MP3 y al cliente se le dice `mime_type="audio/wav"`, y entonces falla fielmente al decodificarlo. Cuando pases `data=`, pasa `format=`. +## Incrustar un recurso {#embedding-a-resource} + +Una herramienta también puede devolver un documento: un texto o unos bytes junto con la URI donde vive y un tipo MIME. Eso es un **`EmbeddedResource`**, otro tipo de bloque de contenido. A diferencia de un `str` simple, le dice al cliente qué es el contenido, así que el cliente puede mostrarlo como un adjunto o reconocer un recurso que ya conoce. + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines` es un recurso normal y corriente (**[Recursos](resources.md)** los cubre). La herramienta le entrega el mismo documento al modelo cuando lo pide, y llamar a `guidelines()` directamente mantiene una única fuente de verdad. +* `EmbeddedResource` y `TextResourceContents` vienen de `mcp.types`. No hay una utilidad como la de las imágenes: el bloque que construyes entra en el resultado sin cambios, y no hay `structured_content`. +* Usa la URI con la que está registrado el recurso, para que un cliente pueda saber que el adjunto y `brand://guidelines` son el mismo documento. Cualquier URI es válida, registrada o no. + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +Para contenido binario, usa `BlobResourceContents(uri=..., mime_type=..., blob=...)` con los bytes codificados en base64 en `blob`, en lugar de `TextResourceContents`. Para enviar solo un puntero que el cliente pueda leer más tarde con `resources/read`, devuelve en su lugar un `ResourceLink(name=..., uri=...)`; también es un bloque de contenido. + ## Iconos {#icons} Un `Icon` es metadatos, no contenido. No lleva la imagen; apunta a una con una URI, y el cliente puede descargarla y mostrarla junto al nombre de tu servidor, una herramienta, un recurso o un prompt. @@ -115,6 +133,7 @@ Los iconos de una herramienta están en el objeto `Tool` de `tools/list`, los de * Devuelve un `Image` o un `Audio` desde una herramienta y el cliente recibe un bloque `ImageContent` / `AudioContent`: tus bytes codificados en base64, con un tipo MIME. * Constrúyelo a partir de un `path=` y deja que el sufijo decida el tipo MIME, o a partir de `data=` en memoria más un `format=` explícito. +* Devuelve un `EmbeddedResource` para poner un documento (texto o un blob en base64, con su URI y tipo MIME) en el resultado, o un `ResourceLink` para enviar solo el puntero. * Los resultados multimedia no llevan `structured_content` ni esquema de salida. * Un `Icon` es un puntero: una URI `src` más `mime_type`, `sizes` y `theme` opcionales. * `icons=[...]` funciona en el servidor, en herramientas, en recursos y en prompts, y los clientes los encuentran en los objetos correspondientes. diff --git a/i18n/es/pages/servers/prompts.md b/i18n/es/pages/servers/prompts.md index 5004a91874..e154ff0b95 100644 --- a/i18n/es/pages/servers/prompts.md +++ b/i18n/es/pages/servers/prompts.md @@ -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 @@ La entrada de `prompts/list` ahora lleva todo lo que un cliente necesita para di ``` !!! info - Si has leído **[Herramientas](tools.md)**, ya sabes todo lo de esta página. El mismo decorador, el mismo + Si has leído **[Herramientas](tools.md)**, ya sabes todo lo visto hasta aquí. El mismo decorador, el mismo docstring como descripción, el mismo `Annotated`/`Field`. Lo único que cambia es quién lo dispara (el usuario) y adónde va el resultado (a la conversación). +## Más que texto {#more-than-text} + +`UserMessage` y `AssistantMessage` también aceptan un bloque de contenido, o un helper `Image` / `Audio`, en cualquier lugar donde aceptan un `str`. En los prompts aparecen dos casos: adjuntar un documento y adjuntar una imagen. + +### Incrustar un archivo {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* La guía de estilo es un recurso en `style://python` (**[Recursos](resources.md)** los cubre), leído de un `style-guide.md` junto a `server.py`. Pon ahí cualquier archivo Markdown. +* `EmbeddedResource(resource=TextResourceContents(...))`, ambos de `mcp.types`, lleva el archivo con su URI y su tipo MIME como primer mensaje; la solicitud que se refiere a él va después como texto plano. +* Incrustar la guía, en lugar de pegarla en el f-string, permite al cliente mostrarla como adjunto y volver a abrir `style://python` más tarde, y el modelo recibe el archivo tal cual. Para un archivo binario usa `BlobResourceContents` con un `blob` en base64. + +Renderizado, el `content` del primer mensaje es un bloque `resource`: + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### Adjuntar una imagen {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image` es el helper de **[Imágenes, audio e iconos](media.md)**. `UserMessage` lo convierte en un bloque `ImageContent` (el archivo codificado en base64, el tipo MIME deducido de `.png`) cuando se renderiza el prompt; `Audio` se convierte en un `AudioContent` del mismo modo. +* Pon cualquier PNG llamado `architecture.png` junto a `server.py`. Los argumentos de un prompt son cadenas, así que la imagen siempre viene del servidor; `component` solo aporta las palabras. + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## Cambiar la lista en tiempo de ejecución {#changing-the-list-at-runtime} + +Se pueden añadir prompts mientras hay clientes conectados, por ejemplo para que un usuario guarde una instrucción como entrada de menú propia. Registra el prompt y luego notifica: + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` registra una función exactamente como lo haría `@mcp.prompt()`, y `mcp.remove_prompt(name)` es lo inverso. `add_prompt` conserva una entrada existente con el mismo nombre en lugar de sobrescribirla, así que la herramienta elimina primero cualquier entrada anterior para que guardar equivalga a reemplazar. `prompts/list` refleja el cambio de inmediato. +* `await ctx.notify_prompts_changed()` envía `notifications/prompts/list_changed` a cada cliente `2026-07-28` que escucha en un stream `subscriptions/listen` (**[Suscripciones](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` se lo envía al cliente que hace la llamada cuando ese cliente es anterior a 2026 (**[Atender clientes heredados](../run/legacy-clients.md)**). Llama a los dos; cada uno no hace nada cuando no hay nadie a quien avisar. +* Un cliente que recibe la notificación vuelve a llamar a `prompts/list`. En el `Client` de Python eso es `async with client.listen(prompts_list_changed=True) as sub:`, que produce un evento `PromptsListChanged`. + ## Resumen {#recap} * `@mcp.prompt()` en una función la convierte en un prompt. El nombre sale de la función y la descripción del docstring. @@ -152,5 +197,7 @@ La entrada de `prompts/list` ahora lleva todo lo que un cliente necesita para di * Devuelve un `str` y se convierte en un mensaje de usuario. Devuelve una lista de `UserMessage` / `AssistantMessage` para sembrar una conversación de varios turnos. * `title=` y `Field(description=...)` son lo que un cliente pone en su interfaz. * Un argumento obligatorio que falta hace fallar toda la solicitud. No hay un resultado de error por prompt. +* Envuelve un `EmbeddedResource` o un `Image` en un `UserMessage` para adjuntar un documento o una imagen. +* Añade o quita prompts en tiempo de ejecución con `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`, y luego `await ctx.notify_prompts_changed()` y `await ctx.session.send_prompt_list_changed()`. El autocompletado en el servidor de los argumentos de un prompt (o de una plantilla de recurso) está en **[Autocompletado](completions.md)**. diff --git a/i18n/es/pages/servers/structured-output.md b/i18n/es/pages/servers/structured-output.md index ca64aa68f0..ece2c5026e 100644 --- a/i18n/es/pages/servers/structured-output.md +++ b/i18n/es/pages/servers/structured-output.md @@ -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 --- # Salida estructurada {#structured-output} @@ -105,7 +105,7 @@ No todas las formas merecen una clase. Un `TypedDict` produce el mismo esquema: --8<-- "docs_src/structured_output/tutorial003.py" ``` -Un `TypedDict` es un `dict` normal en tiempo de ejecución, así que eso es lo que construyes y devuelves. El esquema, la validación y `structured_content` son idénticos a los de la versión con `BaseModel` (salvo las descripciones, para las que `TypedDict` no tiene sitio). +Un `TypedDict` es un `dict` normal en tiempo de ejecución, así que eso es lo que construyes y devuelves. El esquema, la validación y `structured_content` siguen las mismas reglas que la versión con `BaseModel`: añade un docstring a la clase o `Annotated[..., Field(description=...)]` y se convierten en las descripciones, y una clave `NotRequired` que dejes fuera del dict se queda fuera de `structured_content`. ## Una dataclass {#a-dataclass} @@ -187,18 +187,19 @@ No lo notas mientras construyes el valor a mano: Pydantic ya se aseguró de que La anotación promete `WeatherData`. La respuesta del servicio externo dejó de enviar `humidity`. !!! check - Llama a `get_weather` y no le entrega al cliente en silencio un objeto medio vacío. La llamada falla, - y las primeras líneas del error nombran el campo: + Llama a `get_weather` y no le entrega al cliente en silencio un objeto medio vacío. La llamada falla: + el cliente recibe `is_error=True` con `Error executing tool get_weather`, así que el modelo sabe que la + llamada falló en lugar de leer con toda confianza un tiempo que no existe. El nombre del campo es para ti, + en el log del servidor con nivel `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] ``` - Ese texto vuelve como resultado de la herramienta con `is_error=True`, así que el modelo sabe que la - llamada falló en lugar de leer con toda confianza un tiempo que no existe. - Por cierto, devolver un `dict` normal desde una herramienta `-> WeatherData` está bien. Es exactamente lo que produjo `json.loads`. La validación se aplica al valor, no al tipo de Python. ## Desactivarlo {#opting-out} @@ -213,6 +214,10 @@ Sin `output_schema`, sin envoltorio, sin validación. `structured_content` es `N Lo contrario, `structured_output=True`, convierte la detección automática en un requisito: una herramienta cuyo tipo de retorno no pueda producir un esquema lanza una excepción al importar el módulo en lugar de recurrir al texto. +## Bloques de contenido y medios {#content-blocks-and-media} + +Los bloques de contenido y los medios (`TextContent`, `EmbeddedResource`, `Image`, `Audio` y compañía, ya sea solos, como elementos de un `list`, `tuple` o `Sequence`, o como ramas de una unión) quedan excluidos sin que hagas nada: son para que los lea el modelo, así que la detección automática no deriva ningún esquema de ellos (**[Imágenes, audio e iconos](media.md)** se ocupa de `Image` y `Audio`). `structured_output=True` sigue forzando uno para las clases de bloques de contenido. + ## Una clase sin anotaciones de tipo {#a-class-without-type-hints} Hay una forma de acabar sin salida estructurada sin haberlo pedido: devolver una clase que **no tiene anotaciones en su cuerpo**. @@ -245,6 +250,6 @@ Hay una forma de acabar sin salida estructurada sin haberlo pedido: devolver una * Los escalares, las listas, las tuplas y las uniones se envuelven en `{"result": ...}`. Los modelos, los `TypedDict`, las dataclasses, las clases con anotaciones y `dict[str, ...]` ya son objetos y se quedan como están. * Cada resultado lleva `content` (texto, para el modelo) **y** `structured_content` (datos, para la aplicación). * Lo que devuelves se valida contra el esquema. Una discrepancia es un error de herramienta, no un resultado corrupto. -* `structured_output=False` excluye una herramienta. Una clase sin anotaciones de tipo queda excluida en silencio; vigílalo. +* `structured_output=False` excluye una herramienta. Los bloques de contenido, `Image` y `Audio` quedan excluidos por defecto; una clase sin anotaciones de tipo queda excluida en silencio, así que vigílalo. Ahora dominas todo lo que una herramienta puede responder. A continuación, la segunda primitiva: **[Recursos](resources.md)**. diff --git a/i18n/es/pages/servers/tools.md b/i18n/es/pages/servers/tools.md index d475e95b57..0ea76b9d47 100644 --- a/i18n/es/pages/servers/tools.md +++ b/i18n/es/pages/servers/tools.md @@ -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 --- # Herramientas {#tools} @@ -39,6 +39,8 @@ A partir de esas anotaciones de tipo, el SDK genera un JSON Schema y lo envía a Ambos argumentos están en `required` porque ninguno tiene valor por defecto. Lo arreglarás en un momento. (Las claves `title` son artefactos de Pydantic; las propiedades, sus tipos y `required` son el contrato.) +Tampoco hay una clave `$schema`: MCP trata un esquema que no la tiene como **JSON Schema 2020-12**, que es lo que genera Pydantic, así que no hay nada que elegir hasta que escribas esquemas a mano en el **[Server de bajo nivel](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**. + !!! tip Aquí las anotaciones de tipo no son documentación. Son **el contrato**. Si un cliente envía `"limit": "ten"`, el SDK lo rechaza antes de que tu función llegue a ejecutarse. diff --git a/i18n/es/pages/servers/uri-templates.md b/i18n/es/pages/servers/uri-templates.md index c3f0f8d221..f84709f4ba 100644 --- a/i18n/es/pages/servers/uri-templates.md +++ b/i18n/es/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # Plantillas de URI y seguridad de rutas {#uri-templates-and-path-safety} @@ -164,7 +164,7 @@ Las comprobaciones integradas detienen los casos comunes, pero no pueden conocer de tu entorno aislado. Para acceder al sistema de archivos, usa `safe_join` para resolver la ruta y verificar que se mantiene dentro de tu directorio base: -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` @@ -204,10 +204,10 @@ Estas comprobaciones son un prefiltro heurístico; para el acceso al sistema de `safe_join` sigue siendo el límite de contención. !!! tip - Si tu handler no puede satisfacer la solicitud (el archivo no existe, - el id es desconocido), lanza una excepción. El SDK la convierte en una - respuesta de error. Consulta **[Manejo de errores](handling-errors.md)** para ver la diferencia entre un - error de protocolo y un error de herramienta. + Si tu handler no puede satisfacer la solicitud (el archivo no existe, el id es desconocido), lanza + `ResourceNotFoundError` como hace `read_manual` arriba. El cliente recibe `-32602` con tu mensaje + y la URI. Una excepción inesperada se convierte, en cambio, en un `-32603` genérico. Consulta + **[Manejo de errores](handling-errors.md#a-resource-that-doesnt-exist)**. ## Recursos en el Server de bajo nivel {#resources-on-the-low-level-server} diff --git a/i18n/es/pages/troubleshooting.md b/i18n/es/pages/troubleshooting.md index 15dd877a30..3779ff7237 100644 --- a/i18n/es/pages/troubleshooting.md +++ b/i18n/es/pages/troubleshooting.md @@ -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 --- # Solución de problemas {#troubleshooting} @@ -81,11 +81,11 @@ async def main() -> None: `__aexit__` es la desconexión, y por eso no hay ningún `client.close()` que olvidar. **[Pruebas](get-started/testing.md)** se basa exactamente en este patrón. -## `Error executing tool : ` y `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `, `Error executing tool ` y `Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} Estás leyendo un **resultado**, no una excepción. `call_tool` no lanzó nada, y nunca lo hará para una herramienta que falla. -Llama a `forecast` con una ciudad que el servidor no conoce y la excepción que lanza vuelve con la solicitud marcada como *correcta*: +Llama a `forecast` con una ciudad que el servidor no conoce y el `ToolError` que lanza vuelve con la solicitud marcada como *correcta*: ```python result.is_error # True @@ -97,6 +97,8 @@ result.structured_content # None La solución está en tu cliente: **comprueba `result.is_error`**. Un `try/except` alrededor de `call_tool` no captura ninguno de estos casos, porque no hay nada que capturar. Es deliberado, y es lo más útil de esta página que puedes interiorizar: el *modelo* eligió la llamada, así que el modelo recibe el mensaje y la oportunidad de intentarlo de nuevo. **[Manejo de errores](servers/handling-errors.md)** tiene todos los detalles, incluida la vía de `MCPError` que *sí* lanza. +La forma escueta, `Error executing tool ` sin mensaje, significa que la herramienta **se cayó**: se le escapó una excepción que no había previsto (o su valor devuelto no pasó el esquema de salida), y el texto de esa excepción no se transmite. El traceback está en el **log del servidor** con nivel `ERROR`, como `Tool '' 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} Escribiste `@mcp.tool` en lugar de `@mcp.tool()`. `tool()` es una *fábrica* de decoradores: sin los paréntesis, Python le pasa tu función a su parámetro `name=`. @@ -409,7 +411,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## Resumen {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup` nunca es el error. Lee la **última línea**; capturar `MCPError` *dentro* del bloque `async with Client(...)` evita el envoltorio por completo. -* `call_tool` no lanza nada para una herramienta que falla. `Error executing tool ...` y `Unknown tool: ...` son resultados: comprueba `result.is_error`. +* `call_tool` no lanza nada para una herramienta que falla. `Error executing tool ...` y `Unknown tool: ...` son resultados: comprueba `result.is_error`. Si no hay mensaje después del nombre de la herramienta, se cayó, y el traceback está en el log del servidor. * `Client must be used within an async context manager` -> usa `async with`. `Use @tool() instead of @tool` -> añade los paréntesis. * `Tool already exists:` en el log del servidor es la única señal de que dos herramientas con el mismo nombre se fundieron en una. * Un 421, tres formas de escribirlo: `Server returned an error response` (el `Client` de python), `421 Misdirected Request` / `Invalid Host header` (todo lo demás), `Invalid Host header: ` (el log del servidor). Solución: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`. diff --git a/i18n/es/pages/whats-new.md b/i18n/es/pages/whats-new.md index 407a4af802..417f1eedbc 100644 --- a/i18n/es/pages/whats-new.md +++ b/i18n/es/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # Novedades de la v2 {#whats-new-in-v2} @@ -47,9 +47,9 @@ La v1 te entregaba tres capas anidadas: un gestor de contexto de transporte que --8<-- "docs_src/client/tutorial001.py" ``` -`Client` acepta un objeto servidor (en memoria, sin transporte: la historia de las pruebas), una URL (Streamable HTTP) o cualquier gestor de contexto de transporte como `stdio_client(...)`. Entrar en `async with` conecta y negocia la versión del protocolo, sea cual sea la generación que hable el servidor; `client.server_capabilities` y `client.protocol_version` simplemente están ahí después, y `client.server_info` también cuando el servidor se identifica (ahora es `Implementation | None`, porque la identidad en la generación 2026 es opcional). Los callbacks de muestreo y elicitación que registraste en la v1 siguen funcionando (sus cuerpos ven el mismo renombramiento de atributos a snake_case que todo lo demás en esta página), ahora también responden a las solicitudes dentro de resultados al estilo 2026 (más abajo) y se ejecutan concurrentemente en lugar de una a una. `ClientSession` sigue debajo para quien quiera la superficie de bajo nivel, y `client.session` te la entrega; también cambió (corre sobre el nuevo motor de despacho, y algunas de sus propias firmas cambiaron), así que lee la **[Guía de migración](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** antes de bajar a ese nivel. +`Client` acepta un objeto servidor (en memoria, sin transporte: la historia de las pruebas), una URL (Streamable HTTP), un `StdioServerParameters` (un subproceso stdio) o cualquier otro gestor de contexto de transporte como `sse_client(...)`. Entrar en `async with` conecta y negocia la versión del protocolo, sea cual sea la generación que hable el servidor; `client.server_capabilities` y `client.protocol_version` simplemente están ahí después, y `client.server_info` también cuando el servidor se identifica (ahora es `Implementation | None`, porque la identidad en la generación 2026 es opcional). Los callbacks de muestreo y elicitación que registraste en la v1 siguen funcionando (sus cuerpos ven el mismo renombramiento de atributos a snake_case que todo lo demás en esta página), ahora también responden a las solicitudes dentro de resultados al estilo 2026 (más abajo) y se ejecutan concurrentemente en lugar de una a una. `ClientSession` sigue debajo para quien quiera la superficie de bajo nivel, y `client.session` te la entrega; también cambió (se ejecuta sobre el nuevo motor de despacho, y algunas de sus propias firmas cambiaron), así que lee la **[Guía de migración](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** antes de bajar a ese nivel. -**[El Client](client/index.md)** lo presenta, **[Transportes del cliente](client/transports.md)** cubre las tres formas de conexión, **[Callbacks del cliente](client/callbacks.md)** cubre los callbacks en sí y **[Pruebas](get-started/testing.md)** muestra el patrón en memoria que sustituye al helper `create_connected_server_and_client_session()` de la v1. +**[El Client](client/index.md)** lo presenta, **[Transportes del cliente](client/transports.md)** cubre las cuatro formas de conexión, **[Callbacks del cliente](client/callbacks.md)** cubre los callbacks en sí y **[Pruebas](get-started/testing.md)** muestra el patrón en memoria que sustituye al helper `create_connected_server_and_client_session()` de la v1. ### El `Server` de bajo nivel se reconstruyó, no se renombró {#the-low-level-server-was-rebuilt-not-renamed} @@ -135,7 +135,7 @@ En esos tipos, cada atributo de Python es ahora snake_case: `result.is_error`, ` Los renombramientos se anuncian solos. Estos no: * **Las funciones síncronas se ejecutan en un hilo de trabajo.** Una herramienta `def` (o recurso, prompt o resolutor) ya no bloquea el bucle de eventos; la contrapartida es que su cuerpo ya no se ejecuta *en* el hilo del bucle de eventos, lo que importa para código afín a un hilo. Los handlers `async def` no cambian. **[Guía de migración](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**. -* **`MCPError` (el `McpError` de la v1) lanzado dentro de una herramienta es ahora un error de protocolo.** El modelo nunca lo ve. Cualquier otra excepción sigue convirtiéndose en un resultado `is_error=True` que el modelo puede leer y al que puede reaccionar. **[Manejo de errores](servers/handling-errors.md)** explica la división. +* **`MCPError` (el `McpError` de la v1) lanzado dentro de una herramienta es ahora un error de protocolo.** El modelo nunca lo ve. Cualquier otra excepción sigue convirtiéndose en un resultado `is_error=True`, pero solo el mensaje de un `ToolError` llega al modelo: cualquier otra excepción se lee ahora como `Error executing tool `, con el traceback en el log de tu servidor. **[Manejo de errores](servers/handling-errors.md)** explica la división. * **Los resultados se validan antes de salir.** Un `Tool` construido a mano cuyo `input_schema` sea `{}` ahora falla en `tools/list` (la especificación exige `"type": "object"`). Los servidores construidos sobre `@mcp.tool()` nunca ven esto; el SDK escribe sus esquemas. * **Tu cliente valida lo que recibe.** `list_tools()` y `call_tool()` comprueban la respuesta del servidor contra la versión del protocolo negociada, así que un servidor no del todo válido que el análisis permisivo de la v1 toleraba ahora lanza `pydantic.ValidationError`. Si te conectas a servidores que no controlas, cuenta con ser tú quien los descubra; la **[Guía de migración](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)** tiene los detalles. * **Las plantillas de URI son ahora RFC 6570 de verdad.** `{+path}`, `{?query}` y compañía funcionan, la coincidencia es exacta en lugar de laxa por regex, y el path traversal en los valores extraídos se rechaza por defecto. Las plantillas más estrictas fallan al decorar, no en la primera solicitud. **[Plantillas de URI](servers/uri-templates.md)**. diff --git a/i18n/fr/pages/advanced/low-level-server.md b/i18n/fr/pages/advanced/low-level-server.md index c8ce7837c7..4b2d150c97 100644 --- a/i18n/fr/pages/advanced/low-level-server.md +++ b/i18n/fr/pages/advanced/low-level-server.md @@ -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 --- # Le Server de bas niveau {#the-low-level-server} @@ -116,6 +116,17 @@ Le bloc `_meta` est la marque d’identité du serveur : le SDK l’ajoute à ch Le serveur ne compare jamais les deux champs. Le `Client` de ce SDK, si : renvoyez un `structured_content` qui ne satisfait pas le `output_schema` que vous avez déclaré et `call_tool` lève une `RuntimeError` qui commence par `Invalid structured content returned by tool search_books` puis cite l’échec de `jsonschema`. Promettre un schéma ne coûte rien ; le tenir vous incombe. Toute l’échelle des types de retour et des schémas est dans **[Sortie structurée](../servers/structured-output.md)**. +## Le dialecte est JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12} + +`input_schema` et `output_schema` sont du JSON Schema, et la [spécification MCP](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) fixe le dialecte : un schéma sans clé `$schema` est du **JSON Schema 2020-12**. Les schémas que génère `MCPServer` s’appuient sur cette valeur par défaut (Pydantic écrit du 2020-12 et omet la clé), et un dict écrit à la main y est tenu lui aussi ; tout le vocabulaire 2020-12 est donc disponible : + +```python title="server.py" hl_lines="8 14-15" +--8<-- "docs_src/lowlevel/tutorial007.py" +``` + +* La racine de `input_schema` doit être `"type": "object"`. À côté, `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, `$defs` avec des `$ref` locaux et le reste des mots-clés 2020-12 parviennent au client exactement tels qu’écrits. +* Aucune clé `$schema` n’est nécessaire. N’en ajoutez une que pour opter pour une version (draft) antérieure : le `Client` de ce SDK, qui valide `structured_content` par rapport au `output_schema` d’un outil, choisit son validateur d’après `$schema` et utilise 2020-12 en son absence. + ## `_meta` : pour l’application, pas pour le modèle {#\_meta-for-the-application-not-the-model} `content` est la partie de la réponse que lit le modèle. `structured_content` est la même réponse sous forme de données typées. `_meta` est le troisième canal : des données qui voyagent avec le résultat à destination de l’**application cliente**, sans faire partie de la réponse du tout. @@ -167,7 +178,7 @@ Le constructeur couvre les méthodes que MCP définit. `add_request_handler` cou --8<-- "docs_src/lowlevel/tutorial006.py" ``` -* Le premier argument est la chaîne de la méthode. Les notifications ont un jumeau, `add_notification_handler`. +* Le premier argument est la chaîne de la méthode. Les notifications ont un jumeau, `add_notification_handler`. Ses gestionnaires se déclenchent sur stdio et sur les connexions HTTP de la génération à poignée de main (handshake) ; sur le chemin Streamable HTTP en version `2026-07-28`, le POST de notification d’un client reçoit un accusé de réception `202` et n’est pas distribué, car cette révision ne définit aucune notification du client vers le serveur sur HTTP. * `params_type` est le modèle par rapport auquel les `params` entrants sont validés **avant** l’exécution de votre gestionnaire ; les méthodes personnalisées *ont* donc droit à la validation dont les outils sont privés. Dérivez de `RequestParams` pour que le champ `_meta` s’analyse comme celui de toute autre méthode. * Le gestionnaire renvoie un `BaseModel`, un `dict` ou `None`. Le SDK le sérialise dans le résultat JSON-RPC. diff --git a/i18n/fr/pages/advanced/middleware.md b/i18n/fr/pages/advanced/middleware.md index 5268c1c96b..6bd22a924a 100644 --- a/i18n/fr/pages/advanced/middleware.md +++ b/i18n/fr/pages/advanced/middleware.md @@ -1,6 +1,6 @@ --- translation: - sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c] + sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43] tool: 1 --- # Middleware {#middleware} @@ -56,8 +56,12 @@ C’est tout l’intérêt. Le middleware enveloppe **chaque** message entrant : * La mise en place de la connexion : `server/discover`, ou `initialize` et `notifications/initialized` sur une session historique. -* Chaque requête et chaque notification. Pour une notification, `ctx.request_id is None`, - `call_next(ctx)` renvoie `None`, et tout ce que vous renvoyez est ignoré. +* Chaque requête et chaque notification qui atteint le serveur. Pour une notification, + `ctx.request_id is None`, `call_next(ctx)` renvoie `None`, et tout ce que vous renvoyez est + ignoré. (Sur le chemin Streamable HTTP en version `2026-07-28`, le POST de notification d’un + client reçoit un accusé de réception `202` au niveau du transport et n’est jamais distribué ; + il n’atteint donc pas non plus le middleware. Cette révision ne définit aucune notification du + client vers le serveur sur HTTP.) * Même une méthode pour laquelle le serveur n’a pas de gestionnaire : `call_next` lève `MCPError(-32601, "Method not found")` *à travers* votre middleware en route vers le client. @@ -119,8 +123,9 @@ page : **[OpenTelemetry](../run/opentelemetry.md)**. * Un middleware est `async (ctx, call_next) -> result`, passé via `MCPServer(middleware=[...])` (ou ajouté à `mcp.middleware`), et ajouté à `server.middleware` sur le `Server` bas niveau. -* Il enveloppe **chaque** message entrant (`server/discover`, `initialize`, requêtes, - notifications, méthodes inconnues) et s’exécute de l’extérieur vers l’intérieur. +* Il enveloppe **chaque** message entrant qui atteint le serveur (`server/discover`, + `initialize`, requêtes, notifications, méthodes inconnues) et s’exécute de l’extérieur vers + l’intérieur. * `ctx.request_id is None` est ce qui distingue une notification d’une requête. * Levez une exception au lieu d’appeler `call_next` pour refuser un message ; la connexion survit. diff --git a/i18n/fr/pages/client/index.md b/i18n/fr/pages/client/index.md index ea89751ea7..3a85f88440 100644 --- a/i18n/fr/pages/client/index.md +++ b/i18n/fr/pages/client/index.md @@ -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 --- # Le client {#the-client} @@ -27,9 +27,10 @@ Le serveur en haut n’est là que pour vous donner quelque chose à quoi vous c * Une instance de `MCPServer` (ou du `Server` bas niveau) : connexion **dans le processus**. * Une chaîne d’URL (`Client("http://localhost:8000/mcp")`) : Streamable HTTP, la voie de production. -* Un **transport** : tout ce sur quoi vous pouvez faire `async with ... as (read, write)`, comme `stdio_client(...)` qui enveloppe un sous-processus. +* Un `StdioServerParameters` : la commande à lancer en **sous-processus**, avec laquelle le client dialogue via son stdin et son stdout. +* Un **transport** : tout ce sur quoi vous pouvez faire `async with ... as (read, write)`, comme `streamable_http_client(url, http_client=...)` autour de votre propre client HTTP. -Tout le reste de cette page est identique pour les trois. Les en-têtes, les sous-processus, les délais d’expiration et le protocole `Transport` ont leur propre page : **[Transports côté client](transports.md)**. +Tout le reste de cette page est identique pour les quatre. Les en-têtes, les sous-processus, les délais d’expiration et le protocole `Transport` ont leur propre page : **[Transports côté client](transports.md)**. ### Ce que porte un client connecté {#whats-on-a-connected-client} @@ -85,7 +86,7 @@ Ce schéma est tout ce dont une interface a besoin pour afficher un formulaire d `call_tool(name, arguments)` exécute l’outil et vous renvoie un `CallToolResult`. -```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 @@ Un outil qui lève une exception ne lève **rien** dans votre client. Il revient !!! check Demandez `"Solaris"` à `lookup_book` (un titre qui n’est pas au catalogue) et la fonction lève - `ValueError`. L’appel revient pourtant normalement : + `ToolError`. L’appel revient pourtant normalement : ```python result.is_error # True @@ -125,9 +126,10 @@ Un outil qui lève une exception ne lève **rien** dans votre client. Il revient result.structured_content # None ``` - Le message de l’exception a atterri dans `content`, où le **modèle** peut le lire et réessayer. C’est - délibéré : une erreur d’outil fait partie de la conversation, ce n’est pas un plantage. Regardez toujours `is_error` - avant de faire confiance à `structured_content`. + Le message de la `ToolError` a atterri dans `content`, où le **modèle** peut le lire et réessayer. C’est + délibéré : une erreur d’outil fait partie de la conversation, ce n’est pas un plantage. (Si l’outil avait planté avec + une autre exception, `content` dirait seulement `Error executing tool lookup_book`.) Regardez toujours + `is_error` avant de faire confiance à `structured_content`. !!! warning `is_error=True` couvre plus que vos propres `raise`. Demandez un outil que le serveur n’a même pas diff --git a/i18n/fr/pages/client/transports.md b/i18n/fr/pages/client/transports.md index da2801b705..dd644ff6fe 100644 --- a/i18n/fr/pages/client/transports.md +++ b/i18n/fr/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # Transports côté client {#client-transports} @@ -87,15 +87,15 @@ ou passez un `verify=ssl_context` explicite à votre `httpx2.AsyncClient` Un serveur **stdio** est un sous-processus. Le client le lance, écrit du JSON-RPC sur son stdin et lit du JSON-RPC depuis son stdout. C’est ainsi qu’un hôte de bureau exécute un serveur sur votre machine : un hôte *est* ce code plus une interface utilisateur, et **[Se connecter à un véritable hôte](../get-started/real-host.md)** montre la même relation vue du côté de l’hôte, sous forme de fichier de configuration. -Décrivez le processus avec `StdioServerParameters`, transformez-le en transport avec `stdio_client`, et passez *cela* à `Client` : +Décrivez le processus avec `StdioServerParameters` et passez-le à `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` n’accepte pas l’objet de paramètres seul. `StdioServerParameters` est de la configuration ; `stdio_client(server)` est le transport qui sait lancer un processus à partir de celle-ci. Enveloppez toujours. +Entrer dans le bloc lance le processus. En sortir arrête le sous-processus : fermeture de stdin, attente, arrêt forcé s’il traîne. Vous ne le nettoyez jamais vous-même. -Quitter le bloc `async with` arrête aussi le sous-processus : fermeture de stdin, attente, arrêt forcé s’il traîne. Vous ne le nettoyez jamais vous-même. +Le stderr du processus enfant va vers le vôtre. Pour l’envoyer ailleurs, construisez le transport vous-même avec `stdio_client` (du module `mcp`) et passez-le à la place : `Client(stdio_client(server, errlog=log_file))`. !!! warning Le processus enfant n’hérite **pas** de votre environnement. Il reçoit une liste d’autorisation minimale (`HOME`, `LOGNAME`, @@ -113,16 +113,16 @@ Quitter le bloc `async with` arrête aussi le sous-processus : fermeture de stdi Pour `Client`, tout ce qui précède est une seule et même chose. -Un **transport** est n’importe quel gestionnaire de contexte asynchrone qui produit une paire `(read, write)` de flux de messages : formellement, le protocole `Transport` de `mcp.client`. `Client` résout son argument selon son type : un objet serveur se connecte dans le processus, une `str` devient `streamable_http_client(url)`, et tout le reste est ouvert directement comme transport. C’est cette dernière règle qui explique pourquoi `stdio_client(...)`, `streamable_http_client(...)` et `sse_client(...)` s’insèrent tous au même emplacement, et pourquoi vous pouvez écrire le vôtre. +Un **transport** est n’importe quel gestionnaire de contexte asynchrone qui produit une paire `(read, write)` de flux de messages : formellement, le protocole `Transport` de `mcp.client`. `Client` résout son argument selon son type : un objet serveur se connecte dans le processus, une `str` devient `streamable_http_client(url)`, un `StdioServerParameters` devient `stdio_client(params)`, et tout le reste est ouvert directement comme transport. C’est cette dernière règle qui explique pourquoi `stdio_client(...)`, `streamable_http_client(...)` et `sse_client(...)` s’insèrent tous au même emplacement, et pourquoi vous pouvez écrire le vôtre. ## Récapitulatif {#recap} * `Client(mcp)` (l’objet serveur) se connecte en mémoire. Utilisez-le pour les tests et pour l’intégration. * `Client("http://.../mcp")` (une URL) se connecte via Streamable HTTP, le transport de production. * Les en-têtes, l’authentification, les proxys et les délais d’expiration vont sur un `httpx2.AsyncClient` que vous passez à `streamable_http_client(url, http_client=...)`. Il n’y a pas de mot-clé `headers=`. -* stdio s’écrit `Client(stdio_client(StdioServerParameters(...)))`, jamais l’objet de paramètres seul. +* stdio s’écrit `Client(StdioServerParameters(...))`. Ne l’enveloppez vous-même dans `stdio_client(...)` que pour rediriger le stderr du processus enfant. * Le sous-processus reçoit un environnement sous liste d’autorisation, pas le vôtre ; `env=` s’y ajoute. -* Un transport est tout ce sur quoi vous pouvez faire `async with x as (read, write)`. `Client` transmet directement à ce protocole tout ce qui n’est ni un objet serveur ni une URL. +* Un transport est tout ce sur quoi vous pouvez faire `async with x as (read, write)`. `Client` transmet directement à ce protocole tout ce qui n’est ni un objet serveur, ni une URL, ni un `StdioServerParameters`. * Construire un `Client` choisit le transport. `async with` l’ouvre. Une fois le transport ouvert, les deux côtés doivent s’accorder sur une version du protocole. En temps normal, vous n’y pensez jamais ; le jour où vous devez y penser, la page à consulter est **[Versions du protocole](../protocol-versions.md)**. diff --git a/i18n/fr/pages/deprecated.md b/i18n/fr/pages/deprecated.md index 6172f7baf7..9a25f9165f 100644 --- a/i18n/fr/pages/deprecated.md +++ b/i18n/fr/pages/deprecated.md @@ -1,11 +1,11 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # Fonctionnalités obsolètes {#deprecated-features} -La spécification 2026-07-28 retire cinq éléments. Le SDK les implémente toujours tous, et chacun d’eux porte désormais un **avertissement d’obsolescence**. +La spécification 2026-07-28 retire cinq éléments. Le SDK les implémente toujours tous, et chacun d’eux porte désormais un **avertissement d’obsolescence**. Un utilitaire du SDK est obsolète pour des raisons qui lui sont propres ; il figure [à la fin](#deprecated-sdk-helpers). Le tableau ci-dessous nomme chaque fonctionnalité obsolète, la raison de sa disparition et le remplacement sur lequel vous appuyer. @@ -56,6 +56,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE une connexion `mode="legacy"` dont le client a enregistré la fonction de rappel correspondante. +## `ping` sur une session historique {#ping-on-a-legacy-session} + +Un **ping** est une requête vide que chaque côté peut envoyer pour vérifier que l’autre répond toujours. La spécification 2026-07-28 le supprime ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)) : chaque requête qu’envoie un client moderne prouve déjà que le serveur est là, et un serveur moderne n’a aucun canal pour en envoyer un. Les deux méthodes du SDK fonctionnent toujours sur une session de la génération poignée de main (handshake). Depuis le client : + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +Et depuis le serveur, dans n’importe quel gestionnaire (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()` avertit avec `MCPDeprecationWarning` à chaque appel. Sur une connexion par défaut (`2026-07-28`), le serveur répond à la place `MCPError: Method not found`. +* `ctx.session.send_ping()` ne porte aucun avertissement. Sur une connexion moderne, elle lève la même erreur d’absence de canal de retour (back-channel) que toute autre requête à l’initiative du serveur. +* Aucun des deux côtés n’enregistre quoi que ce soit pour répondre à un ping. + +## Notifications de changement des racines {#roots-change-notifications} + +Un client de génération 2025 qui a déclaré la capacité des racines peut signaler au serveur que les dossiers de son espace de travail ont changé en envoyant `notifications/roots/list_changed` ; le serveur répond en redemandant `roots/list`. La spécification 2026-07-28 supprime la notification avec le reste du flux des racines en mode push. Côté client, c’est le passage de `list_roots_callback=` (**[Fonctions de rappel du client](client/callbacks.md)**) qui déclare `"roots": {"listChanged": true}`, et un seul appel tient cette promesse : + +```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() +``` + +Côté serveur, c’est le `Server` de bas niveau qui accueille le gestionnaire de réception : + +```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` est la liste que renvoie votre `list_roots_callback`. `client.send_roots_list_changed()` avertit, et il lui faut un client `mode="legacy"` : sur une connexion moderne, la notification est abandonnée silencieusement. Gardez ensuite la session ouverte, car le `roots/list` de suivi du serveur arrive dessus. +* `MCPServer` n’a aucun hook pour la notification. Sur le `Server` de bas niveau, `on_roots_list_changed=` enregistre le gestionnaire (obsolète lui aussi, il avertit à la construction). La notification ne porte aucune charge utile, donc le gestionnaire appelle `ctx.session.list_roots()` pour obtenir la nouvelle liste. + ## Faire taire l’avertissement {#silencing-the-warning} Dans du nouveau code, ne le faites pas. @@ -76,22 +125,33 @@ C’est toute l’API. Il n’y a pas d’interrupteur par méthode, et vous n Inversez le filtre et vous obtenez gratuitement un test de non-régression. Ajoutez `"error::mcp.MCPDeprecationWarning"` au réglage `filterwarnings` de votre configuration pytest et l’appel obsolète **lève une exception** au lieu d’avertir. Un outil nommé - `old_log` qui appelle encore `ctx.info()` cesse de passer et se met à signaler : + `old_log` qui appelle encore `ctx.info()` cesse de passer : l’appel revient avec + `is_error=True` et `Error executing tool old_log`, et le journal du serveur capturé + désigne le coupable : ```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). ``` Une ligne de configuration pytest, et un appel obsolète ne peut plus jamais se glisser de nouveau dans votre base de code sans faire échouer un test. +## Utilitaires du SDK obsolètes {#deprecated-sdk-helpers} + +Il ne s’agit pas de changements de la spécification, seulement de rouages internes du SDK qui ont un meilleur remplacement. Ils avertissent avec le même `MCPDeprecationWarning` et seront supprimés dans la version 3.0. + +| Obsolète | Ce que vous faites à la place | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()` puis `FuncMetadata.call_fn()`. Seul du code qui pilote directement `FuncMetadata` (une sous-classe personnalisée de `Tool`, par exemple) l’a jamais appelée. | + ## Récapitulatif {#recap} * La spécification 2026-07-28 rend obsolètes les **racines**, l’**échantillonnage** à l’initiative du serveur et la **journalisation** par le protocole (toutes via la [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), restreint la **progression** au sens serveur vers client et supprime **`ping`**. * La colonne des remplacements vous oriente : **[Requêtes à plusieurs allers-retours](handlers/multi-round-trip.md)** pour l’échantillonnage et les racines, **[Journalisation](handlers/logging.md)** pour la journalisation, **[Progression](handlers/progress.md)** pour la progression. `ping` n’a besoin de rien du tout. * L’obsolescence est indicative : aucun changement sur la liaison, tout continue de fonctionner sur les sessions d’avant 2026, et vous obtenez un `MCPDeprecationWarning` visible (un `UserWarning`, donc actif par défaut). -* L’échantillonnage et les racines ont en plus besoin d’un canal de retour (back-channel) qu’une session 2026-07-28 n’a pas. Sur une connexion moderne, ils avertissent puis lèvent une exception. +* L’échantillonnage et les racines ont en plus besoin d’un canal de retour qu’une session 2026-07-28 n’a pas. Sur une connexion moderne, ils avertissent puis lèvent une exception. * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` fait taire toute la catégorie ; `"error::mcp.MCPDeprecationWarning"` dans pytest la transforme en échec de test. +* Un utilitaire du SDK, `FuncMetadata.call_fn_with_arg_validation()`, est obsolète séparément, pour suppression dans la version 3.0. * Aucun nouveau code ne devrait s’appuyer sur l’une de ces fonctionnalités. Toutes les autres pages de cette documentation enseignent l’API actuelle. diff --git a/i18n/fr/pages/get-started/real-host.md b/i18n/fr/pages/get-started/real-host.md index f74124fba0..b1285b0410 100644 --- a/i18n/fr/pages/get-started/real-host.md +++ b/i18n/fr/pages/get-started/real-host.md @@ -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 --- # Se connecter à un véritable hôte {#connect-to-a-real-host} @@ -11,7 +11,7 @@ Se connecter à un hôte se résume donc à un seul geste : vous lui indiquez ** ## Un serveur, tous les hôtes {#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 @@ C’est aussi la commande que `mcp install` écrit pour vous dans la configurati Et un hôte n’est rien de plus qu’une application contenant un client MCP ; votre propre code Python peut donc jouer le rôle de l’hôte : **[Transports du client](../client/transports.md)** - lance ce même fichier comme sous-processus avec `stdio_client(...)`, et **[Tests](testing.md)** + lance ce même fichier comme sous-processus avec `Client(StdioServerParameters(...))`, et **[Tests](testing.md)** s’y connecte en mémoire, sans aucun processus. ## Claude Desktop {#claude-desktop} diff --git a/i18n/fr/pages/get-started/testing.md b/i18n/fr/pages/get-started/testing.md index e7b187985f..c6464addb5 100644 --- a/i18n/fr/pages/get-started/testing.md +++ b/i18n/fr/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # Tests {#testing} @@ -85,9 +85,10 @@ Et voilà. Vous pouvez maintenant étendre vos tests pour couvrir davantage de s Deux choses différentes peuvent mal tourner, et cet indicateur n’en concerne qu’une seule. Une exception dans l’un de **vos outils** n’est pas un échec du protocole. Elle devient un résultat -normal avec `is_error=True`, et le modèle lit le message. `raise_exceptions` n’y change rien : avec -ou sans lui, `call_tool` renvoie le même résultat `is_error=True`. Une page entière y est -consacrée : **[Gérer les erreurs](../servers/handling-errors.md)**. +normal avec `is_error=True` (et s’il s’agissait d’une `ToolError`, le modèle lit votre message). +`raise_exceptions` n’y change rien : avec ou sans lui, `call_tool` renvoie le même résultat +`is_error=True`. Une page entière y est consacrée : +**[Gérer les erreurs](../servers/handling-errors.md)**. Un échec **en dehors** du corps d’un outil est différent. Sur la connexion que vous donne `Client(mcp)`, le serveur le neutralise en un `"Internal server error"` générique avant que le diff --git a/i18n/fr/pages/handlers/elicitation.md b/i18n/fr/pages/handlers/elicitation.md index 58faf19400..2f2568cdb9 100644 --- a/i18n/fr/pages/handlers/elicitation.md +++ b/i18n/fr/pages/handlers/elicitation.md @@ -1,6 +1,6 @@ --- translation: - sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] + sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] tool: 1 --- # Élicitation {#elicitation} @@ -89,7 +89,8 @@ Ce schéma, c’est le formulaire. `Field(description=...)` est le libellé ; un !!! warning Un schéma d’élicitation n’est pas aussi expressif que le schéma d’entrée d’un outil. Des champs plats et primitifs uniquement : `str`, `int`, `float`, `bool`, ou un `Literal` de chaînes (il devient un `enum`). - Mettez un modèle dans le modèle et `ctx.elicit` lève une exception avant que quoi que ce soit ne soit envoyé au client : + Mettez un modèle dans le modèle et `ctx.elicit` lève une exception avant que quoi que ce soit ne soit envoyé au client. + L’appel d’outil échoue avec `Error executing tool `, et le journal de votre serveur en donne la raison : ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -112,8 +113,8 @@ Un refus n’est pas une erreur. L’outil décide de ce que signifie décliner !!! tip La réponse est validée par rapport à votre modèle avant que votre code ne la voie. Un client qui envoie - `"maybe"` pour un `bool` ne corrompt pas votre réservation : l’appel échoue avec une - erreur de non-conformité au schéma, votre `if` ne s’exécute jamais. + `"maybe"` pour un `bool` ne corrompt pas votre réservation : `ctx.elicit` lève `ValueError`, l’appel + échoue, et votre `if` ne s’exécute jamais. ## Envoyer l’utilisateur vers une URL {#send-the-user-to-a-url} diff --git a/i18n/fr/pages/handlers/logging.md b/i18n/fr/pages/handlers/logging.md index 0030d4c3fa..c90a15846d 100644 --- a/i18n/fr/pages/handlers/logging.md +++ b/i18n/fr/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # Journalisation {#logging} @@ -54,6 +54,8 @@ La valeur par défaut est `"INFO"`. `logging.basicConfig()` ne remplace jamais des gestionnaires de journalisation qui existent déjà. Si vous configurez la journalisation vous-même avant de créer le serveur, votre configuration l’emporte. +Vous n’avez pas non plus besoin d’un `try`/`except` dans chaque gestionnaire simplement pour consigner les échecs. Lorsqu’une fonction d’outil ou de ressource lève une exception, le SDK la journalise pour vous. **[Gérer les erreurs](../servers/handling-errors.md#any-other-exception)** explique ce qui est journalisé et à quel niveau. + ## Essayer {#try-it} Lancez le serveur avec le MCP Inspector : diff --git a/i18n/fr/pages/run/index.md b/i18n/fr/pages/run/index.md index 16f2419d6e..27adf13521 100644 --- a/i18n/fr/pages/run/index.md +++ b/i18n/fr/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # Exécuter votre serveur {#running-your-server} @@ -70,9 +70,9 @@ Chaque transport a ses propres arguments nommés, tous sur `run()` : * `host` / `port` : où écouter. Valeurs par défaut `127.0.0.1` et `8000`. * `streamable_http_path` : où se trouve le point de terminaison MCP. Valeur par défaut `/mcp`. -* `json_response=True` : répondre à chaque POST par un corps JSON unique au lieu d’un flux SSE. Ce corps a de la place pour la réponse et rien d’autre : un outil qui rappelle le client en cours de requête (`ctx.elicit()`, échantillonnage) lève donc `NoBackChannelError` sur ce tronçon, et les notifications liées à l’appel en cours (la progression de `ctx.report_progress()`, les messages de journal par appel) sont abandonnées ; le flux `GET` autonome transporte toujours celles qui n’y sont pas liées. +* `json_response=True` : répondre à chaque POST par un corps JSON unique au lieu d’un flux SSE. Ce corps a de la place pour la réponse et rien d’autre : un outil qui rappelle le client en cours de requête (`ctx.elicit()`, échantillonnage (sampling)) lève donc `NoBackChannelError` sur ce tronçon, et les notifications liées à l’appel en cours (la progression de `ctx.report_progress()`, les messages de journal par appel) sont abandonnées ; le flux `GET` autonome transporte toujours celles qui n’y sont pas liées. * `stateless_http=True` : un transport neuf par requête, sans suivi de session. -* `max_request_body_size` : la taille maximale acceptée pour le corps d’un POST, en octets. Vaut 4 Mio par défaut ; les requêtes plus grandes +* `max_request_body_size` : taille maximale acceptée pour le corps d’une requête, en octets. Vaut 4 Mio par défaut ; les requêtes plus grandes reçoivent un HTTP 413 avant toute analyse ou création de session. Ne l’augmentez que lorsque des messages MCP légitimes dépassent cette taille. * `event_store`, `retry_interval`, `transport_security` : reprise après coupure et protection contre le DNS rebinding. Ils peuvent attendre, jusqu’à ce que vous déployiez ailleurs que sur localhost ; **[Déployer et passer à l’échelle](deploy.md)** couvre `transport_security`. diff --git a/i18n/fr/pages/servers/handling-errors.md b/i18n/fr/pages/servers/handling-errors.md index 8fdef7fc0b..619841f2a1 100644 --- a/i18n/fr/pages/servers/handling-errors.md +++ b/i18n/fr/pages/servers/handling-errors.md @@ -1,13 +1,13 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # Gérer les erreurs {#handling-errors} -Un outil (tool) peut échouer de deux manières, et le SDK les traite très différemment. +Un outil (tool) peut échouer de trois manières, et le SDK traite chacune différemment. -Levez une exception ordinaire et c’est le **modèle** qui la voit. Levez `MCPError` et c’est le **protocole** qui la voit. +Levez `ToolError` et c’est le **modèle** qui voit votre message. Levez `MCPError` et c’est le **protocole** qui le voit. Levez quoi que ce soit d’autre et c’est un plantage : le modèle apprend seulement que l’appel a échoué, et votre journal reçoit le traceback. Cette page vous aide à choisir. @@ -15,11 +15,11 @@ Cette page vous aide à choisir. Prenez un outil qui effectue une recherche, et laissez cette recherche échouer : -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -Ces deux lignes n’ont rien de spécifique à MCP. `get_author` lève une simple `ValueError`, comme le ferait n’importe quelle fonction Python. +`ToolError`, qui vient de `mcp.server.mcpserver.exceptions`, est le moyen pour un outil de dire au modèle que quelque chose s’est mal passé. Appelez-le avec un titre absent du catalogue et regardez le résultat : @@ -30,13 +30,15 @@ result.structured_content # None ``` * La requête a **réussi**. Il y a un résultat ; rien n’a été levé côté appelant. -* `is_error` vaut `True`, et le message de votre exception (préfixé du nom de l’outil) se trouve dans `content`, exactement là où le modèle lit. +* `is_error` vaut `True`, et votre message (préfixé du nom de l’outil) se trouve dans `content`, exactement là où le modèle lit. * `structured_content` vaut `None`. Un appel en échec n’a aucune valeur de retour à structurer. -C’est une **erreur d’outil** (tool error), et c’est le comportement par défaut pour *toute* exception que lève votre outil. C’est aussi presque toujours ce que vous voulez. +C’est une **erreur d’outil** (tool error), et c’est presque toujours ce que vous voulez. C’est le modèle qui appelle votre outil. C’est lui qui a choisi les arguments. Une erreur d’outil est donc un tour de conversation : le modèle lit *« No book titled 'Nothing' in the catalog. »*, comprend qu’il s’est trompé de titre et rappelle l’outil avec un meilleur. Vous avez écrit un seul `raise` et obtenu un agent qui se corrige tout seul. +Côté serveur, une `ToolError` se résume à une ligne `INFO` dans le journal, sans traceback. Vous l’aviez vue venir, il n’y a donc rien à examiner. + !!! tip N’utilisez jamais `return` pour renvoyer un message d’erreur depuis un outil. Une chaîne renvoyée a `is_error=False` : pour le modèle (et pour toute interface cliente), l’outil semble avoir @@ -44,7 +46,7 @@ C’est le modèle qui appelle votre outil. C’est lui qui a choisi les argumen ## Une erreur que le modèle ne peut pas corriger {#an-error-the-model-cannot-fix} -Remplacez maintenant `ValueError` par `MCPError`. +Remplacez maintenant `ToolError` par `MCPError`. ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -77,10 +79,10 @@ Remplacez maintenant `ValueError` par `MCPError`. Les deux voies répondent à deux questions différentes. -* **Levez n’importe quelle exception** pour un échec d’*exécution* : ce que votre outil a tenté de faire n’a pas fonctionné. Le modèle a choisi l’appel, il devrait donc en voir la conséquence et avoir une chance de se rattraper. Un titre mal orthographié, une API amont qui a expiré, une ligne qui n’existe pas : autant d’erreurs d’outil. +* **Levez `ToolError`** pour un échec d’*exécution* : ce que votre outil a tenté de faire n’a pas fonctionné. Le modèle a choisi l’appel, il devrait donc en voir la conséquence et avoir une chance de se rattraper. Un titre mal orthographié, une API amont qui a expiré, une ligne qui n’existe pas : autant d’erreurs d’outil. * **Levez `MCPError`** quand c’est la *requête elle-même* qui doit être rejetée : il manque au client une capacité dont dépend votre outil, le serveur n’est pas en état de servir qui que ce soit, l’appelant a sauté une étape obligatoire. Aucune nouvelle tentative du modèle ne corrige cela, il n’y a donc rien à gagner à lui transmettre le message. -Une seule question tranche : **un modèle plus malin aurait-il pu éviter cela ?** Oui -> exception ordinaire. Non -> `MCPError`. +Une seule question tranche : **un modèle plus malin aurait-il pu éviter cela ?** Oui -> `ToolError`. Non -> `MCPError`. Selon ce critère, la seconde version de `get_author` a fait le mauvais choix : un meilleur titre règle le problème, le modèle méritait donc de voir le message. Elle est là pour vous montrer le mécanisme, pas pour le recommander. @@ -89,6 +91,25 @@ Selon ce critère, la seconde version de `get_author` a fait le mauvais choix : utile `data` facultative. Ce que vous y mettez est ce que le client reçoit : le SDK transmet telle quelle une `MCPError` levée au lieu de l’assainir. +## Toute autre exception {#any-other-exception} + +Retirez maintenant la vérification et laissez la recherche dans le dictionnaire échouer d’elle-même : + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]` lève `KeyError`. Vous ne l’aviez pas prévue, le SDK la traite donc comme un plantage : + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +L’appel renvoie toujours `is_error=True`, le modèle sait donc qu’il a échoué et peut passer à autre chose. Ce qu’il n’obtient pas, c’est le texte de l’exception : une `KeyError` venue de votre code, ou une pile de SQL remontée d’un pilote trois bibliothèques plus bas, peut décrire les entrailles de votre serveur, si bien que ce texte ne quitte jamais le serveur. + +C’est vous qui le recevez. Le serveur journalise le plantage au niveau `ERROR` avec le traceback complet, sous l’intitulé `Tool 'get_author' raised an unexpected exception`. Un journal de production réglé sur `WARNING` reste donc silencieux à chaque `ToolError` et se manifeste dès que quelque chose est réellement cassé. + ## Une ressource qui n’existe pas {#a-resource-that-doesnt-exist} Les ressources tracent la même frontière, et fournissent une exception dédiée pour le cas courant. @@ -109,7 +130,7 @@ Quand elle ne le peut pas, levez `ResourceNotFoundError`. Le SDK la transforme e } ``` -Remarquez qu’il n’y a pas ici de demi-résultat `is_error=True`. La lecture d’une ressource renvoie un contenu ou échoue : les ressources n’ont que la voie du protocole. Les modèles et tout ce qui concerne les ressources se trouvent dans **[Ressources](resources.md)**. +Remarquez qu’il n’y a pas ici de demi-résultat `is_error=True`. La lecture d’une ressource renvoie un contenu ou échoue : les ressources n’ont que la voie du protocole. `ResourceError` est l’équivalent pour un échec qui n’est pas « introuvable » (`-32603`, votre message), et les deux se résument à une ligne `INFO` dans votre journal. Toute autre exception hormis `MCPError` est un plantage : le client reçoit `-32603` ne mentionnant que l’URI, et le traceback va dans votre journal au niveau `ERROR`. Les modèles et tout ce qui concerne les ressources se trouvent dans **[Ressources](resources.md)**. ## Les erreurs que vous ne levez jamais {#errors-you-never-raise} @@ -120,19 +141,21 @@ Envoyez à `get_author` un `title` qui n’est pas une chaîne et le SDK le reje Cela représente toute une catégorie d’instructions `raise` que vous n’écrivez pas : ne revalidez pas vos propres annotations de type. !!! info - Tout ce que décrit cette page est ce qu’un **client** voit, et le `Client` en mémoire avec lequel - vous écrirez vos tests voit exactement la même chose. Même `raise_exceptions=True` ne retransforme - pas une erreur d’outil en traceback : au moment où ce drapeau pourrait agir, votre exception est déjà - devenue le résultat `is_error=True`. Faites vos assertions sur le résultat. **[Tests](../get-started/testing.md)** présente ce schéma. + Tout ce qu’un **client** voit sur cette page, le `Client` en mémoire avec lequel vous écrirez vos + tests le voit aussi. Même `raise_exceptions=True` ne rend pas à l’appelant l’exception d’un outil + en échec : au moment où ce drapeau pourrait agir, votre exception est déjà devenue le résultat + `is_error=True`. Faites vos assertions sur le résultat. Si vous avez besoin du traceback d’un plantage, + il est dans le journal du serveur, et le `caplog` de pytest le capture. **[Tests](../get-started/testing.md)** présente ce schéma. ## Récapitulatif {#recap} -* Levez **n’importe quelle exception** dans un outil -> l’appel renvoie `is_error=True` avec votre message dans `content`. Le modèle le lit et peut réessayer. C’est le comportement par défaut. +* Levez **`ToolError`** dans un outil -> l’appel renvoie `is_error=True` avec votre message dans `content`. Le modèle le lit et peut réessayer. * Levez **`MCPError`** -> l’appel lui-même échoue avec une erreur JSON-RPC. Le modèle ne voit rien ; c’est l’hôte qui s’en occupe. `code`, `message` et `data` arrivent intacts. -* La question qui tranche : *un modèle plus malin aurait-il pu éviter cela ?* Oui -> exception. Non -> `MCPError`. +* La question qui tranche : *un modèle plus malin aurait-il pu éviter cela ?* Oui -> `ToolError`. Non -> `MCPError`. +* Toute **autre exception** est un plantage -> `is_error=True` avec seulement `Error executing tool ` pour le modèle, et un enregistrement `ERROR` avec le traceback pour vous. * `ResourceNotFoundError` depuis un gestionnaire (handler) de ressource -> le `-32602` du protocole, avec l’URI dans `data`. * Les mauvais arguments sont rejetés d’après le schéma avant que votre fonction ne s’exécute ; vous n’avez pas de `raise` à écrire pour eux. -* `from mcp import MCPError` ; les constantes de codes d’erreur viennent de `mcp.types`. +* Imports : `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, et les constantes de codes d’erreur depuis `mcp.types`. Les erreurs sont gérées. C’est tout ce qu’un serveur *expose*. Ce que chaque gestionnaire peut lire, et faire en retour auprès du client pendant qu’il s’exécute, fait l’objet de la section suivante : **[Dans votre gestionnaire](../handlers/index.md)**. diff --git a/i18n/fr/pages/servers/media.md b/i18n/fr/pages/servers/media.md index 4990021881..7f93d0f417 100644 --- a/i18n/fr/pages/servers/media.md +++ b/i18n/fr/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # Médias {#media} @@ -86,6 +86,24 @@ Une extension qu’il ne reconnaît pas se rabat sur `application/octet-stream`. `Audio` à partir d’octets MP3 de cette façon et le client reçoit `mime_type="audio/wav"`, puis échoue consciencieusement à le décoder. Quand vous passez `data=`, passez `format=`. +## Embarquer une ressource {#embedding-a-resource} + +Un outil peut aussi renvoyer un document : du texte ou des octets, accompagnés de l’URI où il réside et d’un type MIME. C’est une **`EmbeddedResource`**, une autre sorte de bloc de contenu. Contrairement à un simple `str`, elle indique au client ce qu’est le contenu, si bien que le client peut l’afficher comme pièce jointe ou reconnaître une ressource qu’il connaît déjà. + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines` est une ressource ordinaire (la page **[Ressources](resources.md)** les traite). L’outil remet le même document au modèle sur demande, et appeler `guidelines()` directement conserve une source de vérité unique. +* `EmbeddedResource` et `TextResourceContents` viennent de `mcp.types`. Il n’y a pas d’utilitaire comme pour les images : le bloc que vous construisez va tel quel dans le résultat, et il n’y a pas de `structured_content`. +* Utilisez l’URI sous lequel la ressource est enregistrée, pour qu’un client puisse savoir que la pièce jointe et `brand://guidelines` sont le même document. N’importe quel URI est valide, enregistré ou non. + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +Pour du contenu binaire, utilisez `BlobResourceContents(uri=..., mime_type=..., blob=...)` avec les octets encodés en base64 dans `blob`, à la place de `TextResourceContents`. Pour n’envoyer qu’un pointeur que le client pourra lire plus tard via `resources/read`, renvoyez plutôt un `ResourceLink(name=..., uri=...)` ; c’est aussi un bloc de contenu. + ## Icônes {#icons} Une `Icon` est une métadonnée, pas du contenu. Elle ne transporte pas l’image ; elle en désigne une par un URI, et un client peut la récupérer et l’afficher à côté du nom de votre serveur, d’un outil, d’une ressource ou d’un prompt. @@ -115,6 +133,7 @@ Les icônes d’un outil sont sur l’objet `Tool` issu de `tools/list`, celles * Renvoyez une `Image` ou un `Audio` depuis un outil et le client reçoit un bloc `ImageContent` / `AudioContent` : vos octets encodés en base64, avec un type MIME. * Construisez-en un à partir d’un `path=` et laissez l’extension décider du type MIME, ou à partir de `data=` en mémoire plus un `format=` explicite. +* Renvoyez une `EmbeddedResource` pour placer un document (du texte ou un blob base64, avec son URI et son type MIME) dans le résultat, ou un `ResourceLink` pour n’envoyer que le pointeur. * Les résultats média ne portent ni `structured_content` ni schéma de sortie. * Une `Icon` est un pointeur : un URI `src` plus, en option, `mime_type`, `sizes` et `theme`. * `icons=[...]` fonctionne sur le serveur, sur les outils, sur les ressources et sur les prompts, et les clients les retrouvent sur les objets correspondants. diff --git a/i18n/fr/pages/servers/prompts.md b/i18n/fr/pages/servers/prompts.md index 90cf36b8a2..ffec9b7362 100644 --- a/i18n/fr/pages/servers/prompts.md +++ b/i18n/fr/pages/servers/prompts.md @@ -1,6 +1,6 @@ --- translation: - sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094] + sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd] tool: 1 --- # Prompts {#prompts} @@ -139,10 +139,55 @@ L’entrée `prompts/list` contient désormais tout ce dont un client a besoin p ``` !!! info - Si vous avez lu **[Outils](tools.md)**, vous connaissez déjà tout ce que contient cette page. Même décorateur, même + Si vous avez lu **[Outils](tools.md)**, vous connaissez déjà tout jusqu’ici. Même décorateur, même docstring servant de description, mêmes `Annotated`/`Field`. Seuls changent qui le déclenche (l’utilisateur) et où va le résultat (dans la conversation). +## Au-delà du texte {#more-than-text} + +`UserMessage` et `AssistantMessage` acceptent aussi un bloc de contenu, ou un utilitaire `Image` / `Audio`, partout où ils acceptent une `str`. Deux cas se présentent dans les prompts : joindre un document et joindre une image. + +### Incorporer un fichier {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* Le guide de style est une ressource à l’adresse `style://python` (**[Ressources](resources.md)** traite de celles-ci), lue depuis un fichier `style-guide.md` placé à côté de `server.py`. Mettez-y n’importe quel fichier Markdown. +* `EmbeddedResource(resource=TextResourceContents(...))`, tous deux issus de `mcp.types`, transporte le fichier avec son URI et son type MIME comme premier message ; la demande qui y fait référence suit sous forme de texte brut. +* Incorporer le guide, plutôt que de le coller dans la f-string, permet au client de l’afficher comme pièce jointe et de rouvrir `style://python` plus tard, et le modèle reçoit le fichier tel quel. Pour un fichier binaire, utilisez `BlobResourceContents` avec un `blob` en base64. + +Une fois rendu, le `content` du premier message est un bloc `resource` : + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### Joindre une image {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image` est l’utilitaire de **[Images, audio et icônes](media.md)**. `UserMessage` le convertit en bloc `ImageContent` (le fichier encodé en base64, le type MIME deviné d’après `.png`) au moment du rendu du prompt ; `Audio` devient un `AudioContent` de la même façon. +* Placez n’importe quel PNG nommé `architecture.png` à côté de `server.py`. Les arguments d’un prompt sont des chaînes, l’image vient donc toujours du serveur ; `component` ne fournit que les mots. + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## Modifier la liste à l’exécution {#changing-the-list-at-runtime} + +Des prompts peuvent être ajoutés pendant que des clients sont connectés, par exemple pour permettre à un utilisateur d’enregistrer une instruction comme entrée de menu bien à lui. Enregistrez le prompt, puis notifiez : + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` enregistre une fonction exactement comme le ferait `@mcp.prompt()`, et `mcp.remove_prompt(name)` fait l’inverse. `add_prompt` conserve une entrée existante du même nom au lieu de l’écraser ; l’outil supprime donc d’abord toute ancienne entrée pour que l’enregistrement soit un remplacement. `prompts/list` reflète le changement immédiatement. +* `await ctx.notify_prompts_changed()` envoie `notifications/prompts/list_changed` à chaque client `2026-07-28` à l’écoute sur un flux `subscriptions/listen` (**[Abonnements](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` l’envoie au client appelant lorsque celui-ci est antérieur à 2026 (**[Prendre en charge les clients historiques](../run/legacy-clients.md)**). Appelez les deux ; chacun ne fait rien quand il n’y a personne à prévenir. +* Un client qui reçoit la notification appelle de nouveau `prompts/list`. Dans le `Client` Python, c’est `async with client.listen(prompts_list_changed=True) as sub:`, qui produit un événement `PromptsListChanged`. + ## Récapitulatif {#recap} * `@mcp.prompt()` sur une fonction en fait un prompt. Le nom vient de la fonction, la description de la docstring. @@ -151,5 +196,7 @@ L’entrée `prompts/list` contient désormais tout ce dont un client a besoin p * Renvoyez une `str` et elle devient un seul message utilisateur. Renvoyez une liste de `UserMessage` / `AssistantMessage` pour amorcer une conversation à plusieurs tours. * `title=` et `Field(description=...)` sont ce qu’un client affiche dans son interface. * Un argument obligatoire manquant fait échouer toute la requête. Il n’y a pas de résultat d’erreur par prompt. +* Enveloppez un `EmbeddedResource` ou une `Image` dans un `UserMessage` pour joindre un document ou une image. +* Ajoutez ou supprimez des prompts à l’exécution avec `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`, puis `await ctx.notify_prompts_changed()` et `await ctx.session.send_prompt_list_changed()`. L’autocomplétion côté serveur des arguments d’un prompt (ou d’un modèle de ressource), c’est **[Complétions](completions.md)**. diff --git a/i18n/fr/pages/servers/structured-output.md b/i18n/fr/pages/servers/structured-output.md index a74c85eda7..a97262194f 100644 --- a/i18n/fr/pages/servers/structured-output.md +++ b/i18n/fr/pages/servers/structured-output.md @@ -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 --- # Sortie structurée {#structured-output} @@ -105,7 +105,7 @@ Toutes les formes ne méritent pas une classe. Un `TypedDict` produit le même s --8<-- "docs_src/structured_output/tutorial003.py" ``` -Un `TypedDict` est un simple `dict` à l’exécution : c’est donc ce que vous construisez et renvoyez. Le schéma, la validation et `structured_content` sont identiques à la version `BaseModel` (à l’exception des descriptions, pour lesquelles `TypedDict` n’a pas de place). +Un `TypedDict` est un simple `dict` à l’exécution : c’est donc ce que vous construisez et renvoyez. Le schéma, la validation et `structured_content` suivent les mêmes règles que la version `BaseModel` : ajoutez une docstring de classe ou `Annotated[..., Field(description=...)]` et elles deviennent les descriptions, et une clé `NotRequired` que vous omettez du dict reste absente de `structured_content`. ## Une dataclass {#a-dataclass} @@ -188,17 +188,18 @@ L’annotation promet un `WeatherData`. La réponse en amont a cessé d’envoye !!! check Appelez `get_weather` : il ne remet pas discrètement au client un objet à moitié vide. L’appel - échoue, et les premières lignes de l’erreur nomment le champ : + échoue : le client reçoit `is_error=True` avec `Error executing tool get_weather`, de sorte que le + modèle sait que l’appel a échoué au lieu de lire avec assurance une météo qui n’existe pas. Le nom + du champ est pour vous, dans le journal du serveur au niveau `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] ``` - Ce texte revient comme résultat de l’outil avec `is_error=True` : le modèle sait donc que l’appel - a échoué au lieu de lire avec assurance une météo qui n’existe pas. - Au passage, renvoyer un simple `dict` depuis un outil `-> WeatherData` ne pose aucun problème. C’est exactement ce que `json.loads` a produit. La validation porte sur la valeur, pas sur le type Python. ## Désactiver la sortie structurée {#opting-out} @@ -213,6 +214,10 @@ Aucun `output_schema`, aucune enveloppe, aucune validation. `structured_content` L’inverse, `structured_output=True`, transforme la détection automatique en exigence : un outil dont le type de retour ne peut pas produire de schéma lève une exception à l’import au lieu de se rabattre sur du texte. +## Blocs de contenu et médias {#content-blocks-and-media} + +Les blocs de contenu et les médias (`TextContent`, `EmbeddedResource`, `Image`, `Audio` et consorts, seuls, comme éléments d’une `list`, d’un `tuple` ou d’une `Sequence`, ou comme branches d’une union) sont désactivés pour vous : ils sont destinés à être lus par le modèle, la détection automatique n’en tire donc aucun schéma (la page **[Images, audio et icônes](media.md)** traite de `Image` et `Audio`). `structured_output=True` en impose tout de même un pour les classes de blocs de contenu. + ## Une classe sans annotations de type {#a-class-without-type-hints} Il existe une façon de se retrouver sans sortie structurée sans l’avoir demandé : renvoyer une classe qui n’a **aucune annotation dans son corps**. @@ -245,6 +250,6 @@ Il existe une façon de se retrouver sans sortie structurée sans l’avoir dema * Les scalaires, listes, tuples et unions sont enveloppés dans `{"result": ...}`. Les modèles, les `TypedDict`, les dataclasses, les classes annotées et `dict[str, ...]` sont déjà des objets et restent tels quels. * Chaque résultat porte `content` (du texte, pour le modèle) **et** `structured_content` (des données, pour l’application). * Ce que vous renvoyez est validé par rapport au schéma. Une incohérence est une erreur d’outil, pas un résultat corrompu. -* `structured_output=False` désactive la sortie structurée d’un outil. Une classe sans annotations de type la désactive silencieusement ; surveillez ce cas. +* `structured_output=False` désactive la sortie structurée d’un outil. Les blocs de contenu, `Image` et `Audio` la désactivent par défaut ; une classe sans annotations de type la désactive silencieusement, surveillez donc ce cas. Vous maîtrisez désormais tout ce qu’un outil peut répondre. Ensuite, la deuxième primitive : **[Ressources](resources.md)**. diff --git a/i18n/fr/pages/servers/tools.md b/i18n/fr/pages/servers/tools.md index af7d4bece0..5ad522392d 100644 --- a/i18n/fr/pages/servers/tools.md +++ b/i18n/fr/pages/servers/tools.md @@ -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 --- # Outils {#tools} @@ -39,6 +39,8 @@ Regardez ce que vous avez écrit. Pas de schémas, pas de JSON, pas de protocole Les deux arguments figurent dans `required` parce qu’aucun n’a de valeur par défaut. Vous allez corriger cela dans un instant. (Les clés `title` sont des artefacts de Pydantic ; les propriétés, leurs types et `required` constituent le contrat.) +Il n’y a pas non plus de clé `$schema` : MCP traite un schéma qui en est dépourvu comme du **JSON Schema 2020-12**, ce qui est justement ce que génère Pydantic. Il n’y a donc rien à choisir tant que vous n’écrivez pas vos schémas à la main avec le **[Server de bas niveau](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**. + !!! tip Ici, les annotations de type ne sont pas de la documentation. Elles sont **le contrat**. Si un client envoie `"limit": "ten"`, le SDK le rejette avant même que votre fonction ne s’exécute. diff --git a/i18n/fr/pages/servers/uri-templates.md b/i18n/fr/pages/servers/uri-templates.md index 434dbb3697..1f65c75f1a 100644 --- a/i18n/fr/pages/servers/uri-templates.md +++ b/i18n/fr/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # Modèles d’URI et sûreté des chemins {#uri-templates-and-path-safety} @@ -180,7 +180,7 @@ connaître la frontière de votre bac à sable. Pour l’accès au système de fichiers, utilisez `safe_join` pour résoudre le chemin et vérifier qu’il reste à l’intérieur de votre répertoire de base : -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` @@ -224,9 +224,11 @@ de fichiers, `safe_join` reste la frontière de confinement. !!! tip Si votre gestionnaire ne peut pas satisfaire la requête (le fichier - n’existe pas, l’identifiant est inconnu), levez une exception. Le SDK - la transforme en réponse d’erreur. Consultez **[Gérer les erreurs](handling-errors.md)** pour la - différence entre une erreur de protocole et une erreur d’outil. + n’existe pas, l’identifiant est inconnu), levez `ResourceNotFoundError` + comme le fait `read_manual` ci-dessus. Le client reçoit `-32602` avec + votre message et l’URI. Une exception inattendue devient, elle, une + erreur générique `-32603`. Consultez + **[Gérer les erreurs](handling-errors.md#a-resource-that-doesnt-exist)**. ## Les ressources sur le Server de bas niveau {#resources-on-the-low-level-server} diff --git a/i18n/fr/pages/troubleshooting.md b/i18n/fr/pages/troubleshooting.md index e43b544b91..c928c6b74d 100644 --- a/i18n/fr/pages/troubleshooting.md +++ b/i18n/fr/pages/troubleshooting.md @@ -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 --- # Dépannage {#troubleshooting} @@ -81,11 +81,11 @@ async def main() -> None: `__aexit__` est la déconnexion, c’est pourquoi il n’y a pas de `client.close()` à oublier. **[Tests](get-started/testing.md)** repose exactement sur ce modèle. -## `Error executing tool : ` et `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `, `Error executing tool ` et `Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} Vous lisez un **résultat**, pas une exception. `call_tool` n’a pas levé d’exception, et ne le fera jamais pour un outil qui échoue. -Appelez `forecast` pour une ville que le serveur ne connaît pas, et l’exception qu’il lève revient avec la requête marquée comme *réussie* : +Appelez `forecast` pour une ville que le serveur ne connaît pas, et l’exception `ToolError` qu’il lève revient avec la requête marquée comme *réussie* : ```python result.is_error # True @@ -97,6 +97,8 @@ result.structured_content # None Le correctif est dans votre client : **vérifiez `result.is_error`**. Un `try/except` autour de `call_tool` n’intercepte rien de tout cela, parce qu’il n’y a rien à intercepter. C’est voulu, et c’est la chose la plus utile de cette page à intégrer : c’est le *modèle* qui a choisi l’appel, donc c’est le modèle qui reçoit le message et une chance de réessayer. Tous les détails sont dans **[Gérer les erreurs](servers/handling-errors.md)**, y compris le chemin `MCPError` qui, lui, *lève* bien une exception. +La forme nue, `Error executing tool ` sans message, signifie que l’outil a **planté** : une exception qu’il n’avait pas prévue lui a échappé (ou sa valeur de retour n’a pas satisfait le schéma de sortie), et le texte de cette exception est tenu à l’écart de la liaison. Le traceback est dans le **journal du serveur** au niveau `ERROR`, sous la forme `Tool '' 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} Vous avez écrit `@mcp.tool` au lieu de `@mcp.tool()`. `tool()` est une *fabrique* de décorateurs : sans les parenthèses, Python passe votre fonction à son paramètre `name=`. @@ -412,7 +414,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## Récapitulatif {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup` n’est jamais l’erreur. Lisez la **dernière ligne** ; intercepter `MCPError` *à l’intérieur* du bloc `async with Client(...)` évite entièrement l’enveloppe. -* `call_tool` ne lève pas d’exception pour un outil qui échoue. `Error executing tool ...` et `Unknown tool: ...` sont des résultats : vérifiez `result.is_error`. +* `call_tool` ne lève pas d’exception pour un outil qui échoue. `Error executing tool ...` et `Unknown tool: ...` sont des résultats : vérifiez `result.is_error`. L’absence de message après le nom de l’outil signifie qu’il a planté, et le traceback est dans le journal du serveur. * `Client must be used within an async context manager` -> utilisez `async with`. `Use @tool() instead of @tool` -> ajoutez les parenthèses. * `Tool already exists:` dans le journal du serveur est le seul signe que deux outils de même nom se sont fondus en un seul. * Un 421, trois formulations : `Server returned an error response` (le `Client` python), `421 Misdirected Request` / `Invalid Host header` (tout le reste), `Invalid Host header: ` (le journal du serveur). Correctif : `transport_security=TransportSecuritySettings(allowed_hosts=[...])`. diff --git a/i18n/fr/pages/whats-new.md b/i18n/fr/pages/whats-new.md index a6e26e0127..e8a36aacb1 100644 --- a/i18n/fr/pages/whats-new.md +++ b/i18n/fr/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # Nouveautés de la v2 {#whats-new-in-v2} @@ -47,9 +47,9 @@ La v1 vous donnait trois couches imbriquées : un gestionnaire de contexte de tr --8<-- "docs_src/client/tutorial001.py" ``` -`Client` accepte un objet serveur (en mémoire, sans transport : c’est la solution pour les tests), une URL (Streamable HTTP) ou n’importe quel gestionnaire de contexte de transport comme `stdio_client(...)`. Entrer dans `async with` établit la connexion et négocie la version du protocole, quelle que soit la génération que parle le serveur ; `client.server_capabilities` et `client.protocol_version` sont simplement disponibles ensuite, et `client.server_info` aussi lorsque le serveur s’identifie (c’est désormais `Implementation | None`, puisque l’identité est optionnelle dans la génération 2026). Les fonctions de rappel (callbacks) d’échantillonnage et d’élicitation que vous aviez enregistrées en v1 fonctionnent toujours (leur corps voit le même renommage d’attributs en snake_case que tout le reste de cette page), elles répondent désormais aussi aux requêtes-dans-les-résultats de style 2026 (ci-dessous), et elles s’exécutent de façon concurrente plutôt qu’une à la fois. `ClientSession` reste en dessous pour qui veut la surface bas niveau, et `client.session` vous la donne ; elle a bougé elle aussi (elle tourne sur le nouveau moteur de répartition, et certaines de ses propres signatures ont changé), alors lisez le **[Guide de migration](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** avant de descendre à ce niveau. +`Client` accepte un objet serveur (en mémoire, sans transport : c’est la solution pour les tests), une URL (Streamable HTTP), un `StdioServerParameters` (un sous-processus stdio) ou n’importe quel autre gestionnaire de contexte de transport comme `sse_client(...)`. Entrer dans `async with` établit la connexion et négocie la version du protocole, quelle que soit la génération que parle le serveur ; `client.server_capabilities` et `client.protocol_version` sont simplement disponibles ensuite, et `client.server_info` aussi lorsque le serveur s’identifie (c’est désormais `Implementation | None`, puisque l’identité est optionnelle dans la génération 2026). Les fonctions de rappel (callbacks) d’échantillonnage et d’élicitation que vous aviez enregistrées en v1 fonctionnent toujours (leur corps voit le même renommage d’attributs en snake_case que tout le reste de cette page), elles répondent désormais aussi aux requêtes-dans-les-résultats de style 2026 (ci-dessous), et elles s’exécutent de façon concurrente plutôt qu’une à la fois. `ClientSession` reste en dessous pour qui veut la surface bas niveau, et `client.session` vous la donne ; elle a bougé elle aussi (elle tourne sur le nouveau moteur de répartition, et certaines de ses propres signatures ont changé), alors lisez le **[Guide de migration](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** avant de descendre à ce niveau. -**[Le Client](client/index.md)** le présente, **[Transports du client](client/transports.md)** couvre les trois formes de connexion, **[Fonctions de rappel du client](client/callbacks.md)** couvre les fonctions de rappel elles-mêmes, et **[Tests](get-started/testing.md)** montre le modèle en mémoire qui remplace l’utilitaire `create_connected_server_and_client_session()` de la v1. +**[Le Client](client/index.md)** le présente, **[Transports du client](client/transports.md)** couvre les quatre formes de connexion, **[Fonctions de rappel du client](client/callbacks.md)** couvre les fonctions de rappel elles-mêmes, et **[Tests](get-started/testing.md)** montre le modèle en mémoire qui remplace l’utilitaire `create_connected_server_and_client_session()` de la v1. ### Le `Server` bas niveau a été reconstruit, pas renommé {#the-low-level-server-was-rebuilt-not-renamed} @@ -135,7 +135,7 @@ Sur ces types, chaque attribut Python est désormais en snake_case : `result.is_ Les renommages s’annoncent d’eux-mêmes. Ceux-ci, non : * **Les fonctions synchrones s’exécutent sur un thread de travail.** Un outil `def` (ou une ressource, un prompt ou un résolveur) ne bloque plus la boucle d’événements ; la contrepartie est que son corps ne s’exécute plus *sur* le thread de la boucle d’événements, ce qui compte pour le code lié à un thread particulier. Les gestionnaires `async def` ne sont pas touchés. **[Guide de migration](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**. -* **Une `MCPError` (la `McpError` de la v1) levée dans un outil est désormais une erreur de protocole.** Le modèle ne la voit jamais. Toute autre exception devient toujours un résultat `is_error=True` que le modèle peut lire et auquel il peut réagir. **[Gérer les erreurs](servers/handling-errors.md)** détaille la distinction. +* **Une `MCPError` (la `McpError` de la v1) levée dans un outil est désormais une erreur de protocole.** Le modèle ne la voit jamais. Toute autre exception devient toujours un résultat `is_error=True`, mais seul le message d’une `ToolError` atteint le modèle : toute autre exception se lit désormais `Error executing tool `, avec la trace d’appels dans le journal de votre serveur. **[Gérer les erreurs](servers/handling-errors.md)** détaille la distinction. * **Les résultats sont validés avant de partir.** Un `Tool` construit à la main dont le `input_schema` vaut `{}` fait désormais échouer `tools/list` (la spécification exige `"type": "object"`). Les serveurs construits avec `@mcp.tool()` ne voient jamais cela ; le SDK écrit leurs schémas. * **Votre client valide ce qu’il reçoit.** `list_tools()` et `call_tool()` vérifient la réponse du serveur par rapport à la version du protocole négociée, si bien qu’un serveur pas tout à fait valide que l’analyse indulgente de la v1 tolérait lève désormais `pydantic.ValidationError`. Si vous vous connectez à des serveurs que vous ne contrôlez pas, attendez-vous à être celui qui les découvre ; le **[Guide de migration](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)** a les détails. * **Les modèles d’URI suivent désormais vraiment la RFC 6570.** `{+path}`, `{?query}` et leurs semblables fonctionnent, la correspondance est exacte au lieu d’être approximative façon regex, et la traversée de répertoires dans les valeurs extraites est rejetée par défaut. Les modèles plus stricts échouent au moment de la décoration, pas à la première requête. **[Modèles d’URI](servers/uri-templates.md)**. diff --git a/i18n/hi/pages/advanced/low-level-server.md b/i18n/hi/pages/advanced/low-level-server.md index 8f9d894e7d..d2032b78a4 100644 --- a/i18n/hi/pages/advanced/low-level-server.md +++ b/i18n/hi/pages/advanced/low-level-server.md @@ -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 --- # Low-level Server {#the-low-level-server} @@ -116,6 +116,17 @@ asyncio.run(main()) Server इन दोनों fields की कभी तुलना नहीं करता। इस SDK का `Client` करता है: ऐसा `structured_content` लौटाएँ जो आपके declare किए `output_schema` पर खरा न उतरे, और `call_tool` एक `RuntimeError` raise करता है जो `Invalid structured content returned by tool search_books` से शुरू होता है और आगे `jsonschema` की failure उद्धृत करता है। Schema का वादा करना सस्ता है; उसे निभाना आपकी ज़िम्मेदारी है। Return types और schemas की पूरी सीढ़ी **[Structured Output](../servers/structured-output.md)** में है। +## Dialect JSON Schema 2020-12 है {#the-dialect-is-json-schema-2020-12} + +`input_schema` और `output_schema` JSON Schema हैं, और dialect [MCP specification](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) तय करती है: जिस schema में `$schema` key नहीं है वह **JSON Schema 2020-12** है। `MCPServer` जो schemas generate करता है वे इसी default पर टिके हैं (Pydantic 2020-12 लिखता है और key छोड़ देता है), और हाथ से लिखे dict पर भी यही लागू होता है, इसलिए 2020-12 की पूरी vocabulary उपलब्ध है: + +```python title="server.py" hl_lines="8 14-15" +--8<-- "docs_src/lowlevel/tutorial007.py" +``` + +* `input_schema` का root `"type": "object"` होना ज़रूरी है। उसके साथ `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, local `$ref`s वाले `$defs` और बाकी 2020-12 keywords client तक ठीक वैसे ही पहुँचते हैं जैसे लिखे गए। +* `$schema` key की ज़रूरत नहीं है। इसे सिर्फ़ किसी पुराने draft को चुनने के लिए जोड़ें: इस SDK का `Client`, जो `structured_content` को tool के `output_schema` से validate करता है, अपना validator `$schema` से चुनता है और कोई न होने पर 2020-12 इस्तेमाल करता है। + ## `_meta`: application के लिए, model के लिए नहीं {#\_meta-for-the-application-not-the-model} `content` जवाब का वह हिस्सा है जिसे model पढ़ता है। `structured_content` वही जवाब typed data के रूप में है। `_meta` तीसरा channel है: ऐसा data जो result के साथ **client application** के लिए चलता है, जवाब का हिस्सा बने बिना। @@ -167,7 +178,7 @@ Constructor उन methods को cover करता है जिन्हे --8<-- "docs_src/lowlevel/tutorial006.py" ``` -* पहला argument method string है। Notifications के लिए इसका जुड़वाँ है, `add_notification_handler`। +* पहला argument method string है। Notifications के लिए इसका जुड़वाँ है, `add_notification_handler`। इसके handlers stdio पर और handshake पीढ़ी के HTTP connections पर चलते हैं; `2026-07-28` के streamable-HTTP रास्ते पर client की notification POST को `202` से acknowledge किया जाता है और dispatch नहीं किया जाता, क्योंकि वह revision HTTP पर client-से-server कोई notification define नहीं करता। * `params_type` वह model है जिससे आने वाले `params` आपका handler चलने से **पहले** validate होते हैं, इसलिए custom methods को वह validation **मिलती** है जो tools को नहीं मिलती। `RequestParams` को subclass करें ताकि `_meta` field हर दूसरे method की तरह parse हो। * Handler `BaseModel`, `dict`, या `None` लौटाता है। SDK इसे JSON-RPC result में serialise कर देता है। diff --git a/i18n/hi/pages/advanced/middleware.md b/i18n/hi/pages/advanced/middleware.md index 85699d6c15..3bddd001af 100644 --- a/i18n/hi/pages/advanced/middleware.md +++ b/i18n/hi/pages/advanced/middleware.md @@ -1,6 +1,6 @@ --- translation: - sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c] + sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43] tool: 1 --- # Middleware {#middleware} @@ -52,8 +52,11 @@ client ने connection तैयार करने के लिए भेज यही असली बात है। middleware **हर** inbound message को wrap करता है: * connection setup: `server/discover`, या legacy session पर `initialize` और `notifications/initialized`। -* हर request और हर notification। notification के लिए `ctx.request_id is None` होता है, +* हर request और हर notification जो server तक पहुँचे। notification के लिए `ctx.request_id is None` होता है, `call_next(ctx)` `None` लौटाता है, और आप जो भी लौटाएँ वह फेंक दिया जाता है। + (`2026-07-28` वाले streamable-HTTP path पर client का notification POST transport पर ही `202` से + acknowledge हो जाता है और कभी dispatch नहीं होता, इसलिए वह middleware तक भी नहीं पहुँचता; वह revision + HTTP पर कोई client-to-server notifications define ही नहीं करता।) * वह method भी जिसके लिए server के पास कोई handler नहीं है: `call_next` `MCPError(-32601, "Method not found")` को client की ओर जाते हुए आपके middleware के **बीच से** raise करता है। @@ -109,8 +112,8 @@ SDK ठीक एक middleware साथ देता है, और वह प * middleware `async (ctx, call_next) -> result` है, जिसे `MCPServer(middleware=[...])` के रूप में पास किया जाता है (या `mcp.middleware` में append किया जाता है), और low-level `Server` पर `server.middleware` में append किया जाता है। -* यह **हर** inbound message को wrap करता है (`server/discover`, `initialize`, requests, notifications, - अनजान methods) और outermost-first चलता है। +* यह server तक पहुँचने वाले **हर** inbound message को wrap करता है (`server/discover`, `initialize`, requests, + notifications, अनजान methods) और outermost-first चलता है। * `ctx.request_id is None` से आप notification और request में फ़र्क करते हैं। * एक message को अस्वीकार करने के लिए `call_next` call करने के बजाय raise करें; connection बचा रहता है। * SDK का अपना OpenTelemetry tracing भी एक middleware है, जो पहले से list में है। देखें diff --git a/i18n/hi/pages/client/index.md b/i18n/hi/pages/client/index.md index 16f14a10b5..bc2a393fe7 100644 --- a/i18n/hi/pages/client/index.md +++ b/i18n/hi/pages/client/index.md @@ -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 --- # Client {#the-client} @@ -27,9 +27,10 @@ translation: * `MCPServer` (या low-level `Server`) instance: **in-process** connect होता है। * URL string (`Client("http://localhost:8000/mcp")`): Streamable HTTP, production वाला रास्ता। -* **transport**: कोई भी चीज़ जिसे आप `async with ... as (read, write)` कर सकें, जैसे subprocess को wrap करने वाला `stdio_client(...)`। +* `StdioServerParameters`: वह command जो **subprocess** के रूप में launch होता है, और जिससे उसके stdin और stdout के ज़रिए बात होती है। +* **transport**: कोई भी चीज़ जिसे आप `async with ... as (read, write)` कर सकें, जैसे आपके अपने HTTP client के ऊपर `streamable_http_client(url, http_client=...)`। -इस page की बाकी हर चीज़ तीनों में एक जैसी है। Headers, subprocesses, timeouts और `Transport` protocol का अपना अलग page है: **[Client transports](transports.md)**। +इस page की बाकी हर चीज़ चारों में एक जैसी है। Headers, subprocesses, timeouts और `Transport` protocol का अपना अलग page है: **[Client transports](transports.md)**। ### connected client पर क्या है {#whats-on-a-connected-client} @@ -85,7 +86,7 @@ UI को argument form दिखाने के लिए, और model को `call_tool(name, arguments)` tool चलाता है और आपको `CallToolResult` वापस देता है। -```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 @@ result.is_error # False !!! check `lookup_book` से `"Solaris"` माँगें (ऐसा title जो catalog में नहीं है) और function - `ValueError` raise करता है। call फिर भी सामान्य रूप से लौटता है: + `ToolError` raise करता है। call फिर भी सामान्य रूप से लौटता है: ```python result.is_error # True @@ -125,9 +126,10 @@ result.is_error # False result.structured_content # None ``` - exception का message `content` में पहुँचा, जहाँ **model** उसे पढ़कर दोबारा कोशिश कर सकता है। यह - जानबूझकर है: tool error बातचीत का हिस्सा है, crash नहीं। `structured_content` पर भरोसा करने से - पहले हमेशा `is_error` देखें। + `ToolError` का message `content` में पहुँचा, जहाँ **model** उसे पढ़कर दोबारा कोशिश कर सकता है। यह + जानबूझकर है: tool error बातचीत का हिस्सा है, crash नहीं। (अगर tool किसी और exception से crash + हुआ होता, तो `content` में सिर्फ़ `Error executing tool lookup_book` लिखा होता।) `structured_content` + पर भरोसा करने से पहले हमेशा `is_error` देखें। !!! warning `is_error=True` सिर्फ़ आपके अपने `raise` तक सीमित नहीं है। ऐसा tool माँगें जो server के पास है ही नहीं diff --git a/i18n/hi/pages/client/transports.md b/i18n/hi/pages/client/transports.md index 1a0a85f217..e6cb756bd6 100644 --- a/i18n/hi/pages/client/transports.md +++ b/i18n/hi/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # Client transports {#client-transports} @@ -87,15 +87,15 @@ environment variables set करें या अपने `httpx2.AsyncClient` **stdio** server एक subprocess है। client उसे launch करता है, उसके stdin पर JSON-RPC लिखता है और उसके stdout से JSON-RPC पढ़ता है। desktop host आपकी machine पर server इसी तरह चलाता है: host यही code **है**, बस ऊपर एक UI के साथ, और **[असली host से जुड़ें](../get-started/real-host.md)** यही रिश्ता host की तरफ़ से, एक config file के रूप में दिखाता है। -process को `StdioServerParameters` से बताएँ, `stdio_client` से उसे transport में बदलें, और **वही** `Client` को दें: +process को `StdioServerParameters` से बताएँ और उसे `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` अकेले parameters object को स्वीकार नहीं करता। `StdioServerParameters` configuration है; `stdio_client(server)` वह transport है जो उससे process spawn करना जानता है। हमेशा wrap करें। +block में enter करते ही process spawn हो जाता है। बाहर निकलने पर subprocess बंद हो जाता है: stdin बंद, इंतज़ार, और अटका रहे तो kill। आपको उसे खुद कभी साफ़ नहीं करना पड़ता। -`async with` block से बाहर निकलने पर subprocess भी बंद हो जाता है: stdin बंद, इंतज़ार, और अटका रहे तो kill। आपको उसे खुद कभी साफ़ नहीं करना पड़ता। +child का stderr आपके stderr पर जाता है। उसे कहीं और भेजना हो तो transport खुद `stdio_client` (`mcp` से) के साथ बनाएँ और वही पास करें: `Client(stdio_client(server, errlog=log_file))`। !!! warning child आपका environment inherit **नहीं** करता। उसे एक minimal allow-list मिलती है (POSIX पर `HOME`, `LOGNAME`, @@ -113,16 +113,16 @@ process को `StdioServerParameters` से बताएँ, `stdio_client` `Client` के लिए ऊपर की सभी चीज़ें एक ही हैं। -**transport** कोई भी async context manager है जो message streams का `(read, write)` जोड़ा yield करता है: औपचारिक रूप से, `mcp.client` का `Transport` protocol। `Client` अपने argument को type से resolve करता है: server object in-process जुड़ता है, `str` `streamable_http_client(url)` बन जाता है, और बाकी सब कुछ सीधे transport के रूप में enter किया जाता है। यही आख़िरी नियम वजह है कि `stdio_client(...)`, `streamable_http_client(...)` और `sse_client(...)` सब उसी एक slot में बैठते हैं, और यही वजह है कि आप अपना खुद का भी लिख सकते हैं। +**transport** कोई भी async context manager है जो message streams का `(read, write)` जोड़ा yield करता है: औपचारिक रूप से, `mcp.client` का `Transport` protocol। `Client` अपने argument को type से resolve करता है: server object in-process जुड़ता है, `str` `streamable_http_client(url)` बन जाता है, `StdioServerParameters` `stdio_client(params)` बन जाता है, और बाकी सब कुछ सीधे transport के रूप में enter किया जाता है। यही आख़िरी नियम वजह है कि `stdio_client(...)`, `streamable_http_client(...)` और `sse_client(...)` सब उसी एक slot में बैठते हैं, और यही वजह है कि आप अपना खुद का भी लिख सकते हैं। ## सारांश {#recap} * `Client(mcp)` (server object) memory में जुड़ता है। इसे tests और embedding के लिए इस्तेमाल करें। * `Client("http://.../mcp")` (URL) Streamable HTTP पर जुड़ता है, जो production transport है। * Headers, auth, proxies और timeouts उस `httpx2.AsyncClient` पर होने चाहिए जो आप `streamable_http_client(url, http_client=...)` को पास करते हैं। कोई `headers=` keyword नहीं है। -* stdio है `Client(stdio_client(StdioServerParameters(...)))`, अकेला parameters object कभी नहीं। +* stdio है `Client(StdioServerParameters(...))`। इसे खुद `stdio_client(...)` में सिर्फ़ तब wrap करें जब child का stderr कहीं और भेजना हो। * subprocess को allow-list वाला environment मिलता है, आपका नहीं; `env=` उसमें जोड़ता है। -* transport वह हर चीज़ है जिस पर आप `async with x as (read, write)` कर सकें। जो कुछ server object या URL नहीं है, `Client` उसे सीधे उसी protocol को सौंप देता है। +* transport वह हर चीज़ है जिस पर आप `async with x as (read, write)` कर सकें। जो कुछ server object, URL या `StdioServerParameters` नहीं है, `Client` उसे सीधे उसी protocol को सौंप देता है। * `Client` बनाने से transport चुना जाता है। `async with` उसे खोलता है। transport खुल जाने के बाद दोनों पक्षों को protocol version पर सहमत होना होता है। आम तौर पर आपको इस बारे में सोचना ही नहीं पड़ता; जब पड़े, तो **[Protocol versions](../protocol-versions.md)** वह page है। diff --git a/i18n/hi/pages/deprecated.md b/i18n/hi/pages/deprecated.md index c6fdf3290d..d986c75aec 100644 --- a/i18n/hi/pages/deprecated.md +++ b/i18n/hi/pages/deprecated.md @@ -1,13 +1,13 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # Deprecated features {#deprecated-features} -2026-07-28 spec पाँच चीज़ों को retire करता है। SDK अब भी इनमें से हर एक को implement करता है, और अब हर एक पर **deprecation warning** लगी है। +2026-07-28 spec पाँच चीज़ों को retire करता है। SDK अब भी इनमें से हर एक को implement करता है, और अब हर एक पर **deprecation warning** लगी है। एक SDK helper अपनी अलग वजह से deprecated है और [आख़िर में](#deprecated-sdk-helpers) दिया गया है। -नीचे दी गई table हर deprecated feature का नाम, उसके हटने की वजह, और उसकी जगह किस पर build करना है, यह बताती है। +नीचे दी गई table हर deprecated feature का नाम, उसके हटने की वजह, और उसकी जगह किस replacement पर build करना है, यह बताती है। ## क्या deprecated है {#what-is-deprecated} @@ -55,6 +55,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE कोशिश करता है। ये दोनों end-to-end सिर्फ़ ऐसे `mode="legacy"` connection पर काम करते हैं जिसके client ने matching callback register किया हो। +## legacy session पर `ping` {#ping-on-a-legacy-session} + +**ping** एक खाली request है जिसे कोई भी पक्ष यह जाँचने के लिए भेज सकता है कि दूसरा अब भी जवाब दे रहा है। 2026-07-28 spec इसे हटा देता है ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)): modern client की भेजी हर request पहले ही साबित कर देती है कि server मौजूद है, और modern server के पास इसे भेजने का कोई channel नहीं है। दोनों SDK methods handshake वाली पीढ़ी के session पर अब भी काम करते हैं। client से: + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +और server से, किसी भी 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()` हर call पर `MCPDeprecationWarning` के साथ warn करता है। default (`2026-07-28`) connection पर server इसके बजाय `MCPError: Method not found` जवाब देता है। +* `ctx.session.send_ping()` पर कोई warning नहीं है। modern connection पर यह वही no-back-channel error raise करता है जो कोई भी दूसरी server-initiated request करती है। +* ping का जवाब देने के लिए कोई भी पक्ष कुछ register नहीं करता। + +## roots में बदलाव के notifications {#roots-change-notifications} + +roots capability declare करने वाला 2025 पीढ़ी का client `notifications/roots/list_changed` भेजकर server को बता सकता है कि उसके workspace folders बदल गए हैं; जवाब में server दोबारा `roots/list` की request करता है। 2026-07-28 spec बाकी push-style roots flow के साथ इस notification को भी हटा देता है। client पर `list_roots_callback=` देना (**[Client callbacks](client/callbacks.md)**) ही `"roots": {"listChanged": true}` declare करता है, और एक call वह वादा निभाता है: + +```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() +``` + +server पर, इसे पाने वाला handler low-level `Server` लेता है: + +```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` वह list है जो आपका `list_roots_callback` लौटाता है। `client.send_roots_list_changed()` warn करता है, और इसे `mode="legacy"` client चाहिए: modern connection पर notification चुपचाप drop हो जाता है। इसके बाद session खुला रखें, क्योंकि server की follow-up `roots/list` उसी पर आती है। +* `MCPServer` के पास इस notification के लिए कोई hook नहीं है। low-level `Server` पर `on_roots_list_changed=` handler register करता है (यह भी deprecated है, और construction के समय warn करता है)। notification में कोई payload नहीं होता, इसलिए handler नई list के लिए `ctx.session.list_roots()` call करता है। + ## warning को चुप कराना {#silencing-the-warning} नए code में ऐसा न करें। @@ -75,15 +124,25 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning) filter को उल्टा चलाएँ और आपको मुफ़्त में regression test मिलता है। अपनी pytest configuration की `filterwarnings` setting में `"error::mcp.MCPDeprecationWarning"` जोड़ें और deprecated call warn करने के बजाय **raise** करता है। `old_log` नाम का tool - जो अब भी `ctx.info()` call करता है, pass होना बंद कर देता है और यह report करने लगता है: + जो अब भी `ctx.info()` call करता है, pass होना बंद कर देता है: call `is_error=True` और + `Error executing tool old_log` के साथ वापस आता है, और capture किया गया server log + असली दोषी का नाम बताता है: ```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). ``` pytest configuration की एक line, और कोई deprecated call बिना test fail किए आपके codebase में चुपके से वापस नहीं आ सकता। +## Deprecated SDK helpers {#deprecated-sdk-helpers} + +ये spec के बदलाव नहीं हैं, सिर्फ़ SDK के अंदरूनी हिस्से हैं जिनका बेहतर replacement मौजूद है। ये उसी `MCPDeprecationWarning` के साथ warn करते हैं और 3.0 में हटा दिए जाएँगे। + +| Deprecated | इसके बजाय क्या करें | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()` और फिर `FuncMetadata.call_fn()`। इसे सिर्फ़ वही code call करता था जो `FuncMetadata` को सीधे चलाता है (जैसे कोई custom `Tool` subclass)। | + ## सारांश {#recap} * 2026-07-28 spec **roots**, server-initiated **sampling**, और protocol **logging** को deprecate करता है (तीनों [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), **progress** को server-से-client तक सीमित करता है, और **`ping`** को हटा देता है। @@ -91,6 +150,7 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning) * Deprecated होना बस सलाह भर है: wire में कोई बदलाव नहीं, 2026 से पहले के sessions पर सब कुछ काम करता रहता है, और आपको साफ़ दिखने वाली `MCPDeprecationWarning` मिलती है (यह `UserWarning` है, इसलिए default रूप से चालू है)। * sampling और roots को इसके अलावा back-channel चाहिए जो 2026-07-28 session के पास नहीं है। modern connection पर ये warn करते हैं और फिर raise करते हैं। * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` पूरी category को चुप कराता है; pytest में `"error::mcp.MCPDeprecationWarning"` इसे test failure में बदल देता है। +* एक SDK helper, `FuncMetadata.call_fn_with_arg_validation()`, अलग से deprecated है और 3.0 में हटाया जाएगा। * नया code इनमें से किसी पर भी नहीं बनना चाहिए। इन docs का बाकी हर page मौजूदा API सिखाता है। diff --git a/i18n/hi/pages/get-started/real-host.md b/i18n/hi/pages/get-started/real-host.md index 0f3aca69ab..ade7abe281 100644 --- a/i18n/hi/pages/get-started/real-host.md +++ b/i18n/hi/pages/get-started/real-host.md @@ -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 --- # असली host से connect करना {#connect-to-a-real-host} @@ -11,13 +11,13 @@ translation: ## एक server, हर 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" ``` दो tools और एक resource, एक ही file में। इस file की तीन बातें नीचे के हर host के लिए मायने रखती हैं: -* बिना arguments के `mcp.run()` एक **stdio** server शुरू करता है: यह block होता है, stdin पर protocol messages पढ़ता है और stdout पर लिखता है। इस page का हर host यही transport बोलता है। host आपकी file को child process के रूप में शुरू करता है और उन दोनों pipes का मालिक होता है, इसीलिए connect करना हमेशा बस "यह रहा command" ही होता है। आप कभी port नहीं चुनते, और किसी port पर कुछ listen नहीं करता। +* बिना arguments के `mcp.run()` **stdio** server शुरू करता है: यह block होता है, stdin पर protocol messages पढ़ता है और stdout पर लिखता है। इस page का हर host यही transport बोलता है। host आपकी file को child process के रूप में शुरू करता है और उन दोनों pipes का मालिक होता है, इसीलिए connect करना हमेशा बस "यह रहा command" ही होता है। आप कभी port नहीं चुनते, और किसी port पर कुछ listen नहीं करता। * `run()` `if __name__ == "__main__":` के नीचे है। नीचे की हर चीज़ इस file को execute करने के बजाय **import** करती है, इसलिए बिना guard वाला `run()` module के load होते ही server शुरू कर देता। * server object module-level global है जिसका नाम `mcp` है। `mcp run` इसी नाम को ढूँढता है (`server` और `app` भी चलते हैं)। कोई और नाम रखें तो उसे साफ़-साफ़ बताना होगा: `mcp run server.py:bookshop`। @@ -31,7 +31,7 @@ translation: uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py ``` -सबके लिए एक ही command, क्योंकि `uv run --with` उसी वक़्त SDK को एक नए environment में resolve कर देता है: यह किसी भी directory से चलता है और इसे न कोई project चाहिए, न activate करने के लिए कोई virtual environment। यहाँ यह बात कहीं और से ज़्यादा मायने रखती है, क्योंकि host आपके server को आपके shell से नहीं, बल्कि **अपनी** working directory से, लगभग खाली environment के साथ launch करता है। +सबके लिए एक ही command, क्योंकि `uv run --with` उसी वक़्त SDK को नए environment में resolve कर देता है: यह किसी भी directory से चलता है और इसे न कोई project चाहिए, न activate करने के लिए कोई virtual environment। यहाँ यह बात कहीं और से ज़्यादा मायने रखती है, क्योंकि host आपके server को आपके shell से नहीं, बल्कि **अपनी** working directory से, लगभग खाली environment के साथ launch करता है। यही वह command है जो `mcp install` आपके लिए Claude Desktop के config में लिखता है (नीचे देखें), इसलिए जो आप हाथ से लिखते हैं और जो tool बनाता है, दोनों मेल खाते हैं, सिवाय उस exact version pin के जो tool जोड़ता है। @@ -50,7 +50,7 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py और host किसी application से ज़्यादा कुछ नहीं जिसके अंदर MCP client हो, इसलिए आपका अपना Python भी host की भूमिका निभा सकता है: **[Client transports](../client/transports.md)** इसी - file को `stdio_client(...)` से subprocess के रूप में launch करता है, और **[Testing](testing.md)** + file को `Client(StdioServerParameters(...))` से subprocess के रूप में launch करता है, और **[Testing](testing.md)** बिना किसी process के, memory में ही उससे connect करता है। ## Claude Desktop {#claude-desktop} diff --git a/i18n/hi/pages/get-started/testing.md b/i18n/hi/pages/get-started/testing.md index 85721f0ef4..2e72aa19c7 100644 --- a/i18n/hi/pages/get-started/testing.md +++ b/i18n/hi/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # Testing {#testing} @@ -84,12 +84,12 @@ async def test_call_add_tool(client: Client): दो अलग-अलग चीज़ें गड़बड़ हो सकती हैं, और यह flag उनमें से सिर्फ़ एक को छूता है। -**आपके tools** में से किसी के अंदर हुआ exception protocol failure नहीं है। वह `is_error=True` वाला सामान्य result बन जाता है, और model उसका message पढ़ता है। `raise_exceptions` इसमें कुछ नहीं बदलता: इसके साथ या इसके बिना, `call_tool` वही `is_error=True` वाला result लौटाता है। इस पर एक पूरा page है: -**[Errors संभालना](../servers/handling-errors.md)**। +**आपके tools** में से किसी के अंदर हुआ exception protocol failure नहीं है। वह `is_error=True` वाला सामान्य result बन जाता है (और अगर वह `ToolError` था, तो model आपका message पढ़ता है)। `raise_exceptions` इसमें कुछ नहीं बदलता: इसके साथ या इसके बिना, `call_tool` वही `is_error=True` वाला result लौटाता है। इस पर पूरा एक page है: +**[errors संभालना](../servers/handling-errors.md)**। -Tool body के **बाहर** की failure अलग है। `Client(mcp)` जो connection देता है, उस पर server इसे client तक पहुँचने से पहले एक सामान्य `"Internal server error"` में sanitise कर देता है। किसी अनपेक्षित crash की बारीकियाँ remote caller तक कभी leak नहीं होनी चाहिए। Test में आप ठीक यही **नहीं** चाहते, और `raise_exceptions=True` यही बदलता है: आपके test को sanitise किया हुआ message नहीं, बल्कि असली message दिखता है। +tool body के **बाहर** का failure अलग है। `Client(mcp)` जो connection देता है, उस पर server इसे client तक पहुँचने से पहले एक सामान्य `"Internal server error"` में sanitise कर देता है। किसी अनपेक्षित crash की बारीकियाँ remote caller तक कभी leak नहीं होनी चाहिए। test में आप ठीक यही **नहीं** चाहते, और `raise_exceptions=True` यही बदलता है: आपके test को sanitise किया हुआ message नहीं, बल्कि असली message दिखता है। -Tests में इसे चालू रहने दें। Production code में इसका कोई मतलब नहीं है। +tests में इसे चालू रहने दें। production code में इसका कोई मतलब नहीं है। ## Default रूप से in-process {#in-process-by-default} diff --git a/i18n/hi/pages/handlers/elicitation.md b/i18n/hi/pages/handlers/elicitation.md index 0a47697d87..0b8c7a3ac9 100644 --- a/i18n/hi/pages/handlers/elicitation.md +++ b/i18n/hi/pages/handlers/elicitation.md @@ -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 @@ client को आपका message मिलता है और उसके !!! warning elicitation schema tool के input schema जितना expressive नहीं होता। सिर्फ़ flat, primitive fields: `str`, `int`, `float`, `bool`, या strings का `Literal` (यह `enum` बन जाता है)। - model के अंदर model रखें और `ctx.elicit` client को कुछ भी भेजे जाने से पहले ही raise कर देता है: + model के अंदर model रखें और `ctx.elicit` client को कुछ भी भेजे जाने से पहले ही raise कर देता है। + tool call `Error executing tool ` के साथ fail हो जाता है, और वजह आपके server log में मिलती है: ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -112,8 +113,8 @@ client को आपका message मिलता है और उसके !!! tip जवाब आपके code तक पहुँचने से पहले आपके model के विरुद्ध validate होता है। जो client - `bool` के लिए `"maybe"` भेजता है, वह आपकी booking को खराब नहीं करता: call - schema-mismatch error के साथ fail हो जाता है, आपका `if` कभी नहीं चलता। + `bool` के लिए `"maybe"` भेजता है, वह आपकी booking को खराब नहीं करता: `ctx.elicit` + `ValueError` raise करता है, call fail हो जाता है, और आपका `if` कभी नहीं चलता। ## user को URL पर भेजना {#send-the-user-to-a-url} diff --git a/i18n/hi/pages/handlers/logging.md b/i18n/hi/pages/handlers/logging.md index 5914c20dcd..402fd53830 100644 --- a/i18n/hi/pages/handlers/logging.md +++ b/i18n/hi/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # Logging {#logging} @@ -54,6 +54,8 @@ default `"INFO"` है। `logging.basicConfig()` पहले से मौजूद handlers को कभी नहीं बदलता। अगर आप server बनाने से पहले खुद logging configure करते हैं, तो आपका configuration ही चलता है। +सिर्फ़ failures दर्ज करने के लिए हर handler में `try`/`except` लगाने की भी ज़रूरत नहीं है। जब कोई tool या resource function raise करता है, तो SDK उसे आपके लिए log कर देता है। क्या log होता है और किस level पर, यह **[errors संभालना](../servers/handling-errors.md#any-other-exception)** में बताया गया है। + ## इसे आज़माएँ {#try-it} server को MCP Inspector के साथ चलाएँ: diff --git a/i18n/hi/pages/run/index.md b/i18n/hi/pages/run/index.md index 6ecb764082..7eaaee37a2 100644 --- a/i18n/hi/pages/run/index.md +++ b/i18n/hi/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # अपना server चलाना {#running-your-server} @@ -72,14 +72,14 @@ Inspector ठीक वही करता है जो असली host क * `streamable_http_path`: MCP endpoint कहाँ रहता है। Default `/mcp`। * `json_response=True`: हर POST का जवाब SSE stream के बजाय एक अकेली JSON body से देना। उस body में सिर्फ़ response की जगह है, और कुछ नहीं, इसलिए जो tool request के बीच में client को वापस call करता है (`ctx.elicit()`, sampling), वह इस leg पर `NoBackChannelError` raise करता है, और चल रही call से जुड़े notifications (`ctx.report_progress()` का progress, per-call log messages) छोड़ दिए जाते हैं; standalone `GET` stream असंबंधित notifications अब भी ले जाती है। * `stateless_http=True`: हर request के लिए नया transport, कोई session tracking नहीं। -* `max_request_body_size`: स्वीकार की जाने वाली सबसे बड़ी POST body, bytes में। Default 4 MiB है; इससे बड़ी requests +* `max_request_body_size`: स्वीकार की जाने वाली सबसे बड़ी request body, bytes में। Default 4 MiB है; इससे बड़ी requests को parsing या session बनने से पहले ही HTTP 413 मिलता है। इसे तभी बढ़ाएँ जब जायज़ MCP messages उस आकार से बड़े हों। * `event_store`, `retry_interval`, `transport_security`: resumability और DNS-rebinding से सुरक्षा। ये इंतज़ार कर सकते हैं, जब तक आप localhost के अलावा कहीं deploy न करें; `transport_security` की जानकारी **[Deploy & scale](deploy.md)** में है। !!! warning Transport options `run()` को जाते हैं, `MCPServer(...)` को **नहीं**। Constructor बताता है कि - आपका server **क्या है**: name, version, instructions. `run()` बताता है कि वह कैसे serve होता है। इसे + आपका server **क्या है**: name, version, instructions। `run()` बताता है कि वह कैसे serve होता है। इसे उल्टा करेंगे तो MCP के शामिल होने से पहले ही Python जवाब दे देता है: ```text diff --git a/i18n/hi/pages/servers/handling-errors.md b/i18n/hi/pages/servers/handling-errors.md index defd15dd15..fa5caed99b 100644 --- a/i18n/hi/pages/servers/handling-errors.md +++ b/i18n/hi/pages/servers/handling-errors.md @@ -1,25 +1,25 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # errors संभालना {#handling-errors} -tool दो तरीकों से fail हो सकता है, और SDK दोनों के साथ बहुत अलग बर्ताव करता है। +tool तीन तरीकों से fail हो सकता है, और SDK हर एक के साथ अलग बर्ताव करता है। -साधारण exception raise करें तो उसे **model** देखता है। `MCPError` raise करें तो उसे **protocol** देखता है। +`ToolError` raise करें तो आपका message **model** देखता है। `MCPError` raise करें तो उसे **protocol** देखता है। कुछ और raise करें तो वह crash है: model को सिर्फ़ इतना पता चलता है कि call fail हुआ, और traceback आपके log में जाता है। -यह page इन दोनों में से चुनने के बारे में है। +यह page इनमें से चुनने के बारे में है। ## ऐसा error जिसे model ठीक कर सकता है {#an-error-the-model-can-fix} ऐसा tool लें जो कुछ खोजता है, और खोज को नाकाम होने दें: -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -उन दो lines में MCP जैसा कुछ नहीं है। `get_author` सादा `ValueError` raise करता है, जैसे कोई भी Python function करता। +`mcp.server.mcpserver.exceptions` से आने वाला `ToolError` वह तरीका है जिससे tool model को बताता है कि कुछ गड़बड़ हुई। इसे ऐसे title से call करें जो catalog में नहीं है और result देखें: @@ -30,13 +30,15 @@ result.structured_content # None ``` * request **सफल रही**। result मौजूद है; caller की तरफ़ कुछ raise नहीं हुआ। -* `is_error` `True` है, और आपके exception का message (आगे tool का नाम लगा हुआ) `content` में है, ठीक वहीं जहाँ model पढ़ता है। +* `is_error` `True` है, और आपका message (आगे tool का नाम लगा हुआ) `content` में है, ठीक वहीं जहाँ model पढ़ता है। * `structured_content` `None` है। fail हुए call के पास structure करने को कोई return value नहीं होती। -यह **tool error** है, और आपका tool **कोई भी** exception raise करे, default यही है। और लगभग हमेशा आप यही चाहते भी हैं। +यह **tool error** है, और लगभग हमेशा आप यही चाहते हैं। आपके tool को call करने वाला model ही है। arguments उसी ने चुने। इसलिए tool error बातचीत का एक turn है: model *"No book titled 'Nothing' in the catalog."* पढ़ता है, समझ जाता है कि उसने title का गलत अंदाज़ा लगाया, और बेहतर title के साथ फिर call करता है। आपने एक `raise` लिखा और बदले में खुद को सुधारने वाला agent मिल गया। +server पर `ToolError` log में बस एक `INFO` line है, बिना traceback के। इसका आपको पहले से अंदाज़ा था, इसलिए जाँचने को कुछ नहीं है। + !!! tip tool से कभी error message `return` न करें। लौटाई गई string का `is_error=False` होता है, इसलिए model को (और हर client UI को) लगता है कि tool ठीक चला और वही string जवाब थी। @@ -44,7 +46,7 @@ result.structured_content # None ## ऐसा error जिसे model ठीक नहीं कर सकता {#an-error-the-model-cannot-fix} -अब `ValueError` की जगह `MCPError` रखें। +अब `ToolError` की जगह `MCPError` रखें। ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -77,10 +79,10 @@ result.structured_content # None दोनों रास्ते दो अलग-अलग सवालों का जवाब देते हैं। -* **कोई भी exception raise करें** जब नाकामी **execution** की हो: आपके tool ने जो करने की कोशिश की, वह नहीं हुआ। call model ने चुना था, इसलिए नतीजा भी model को दिखना चाहिए और उसे संभलने का मौका मिलना चाहिए। गलत वर्तनी वाला title, timeout हो गया upstream API, ऐसी row जो मौजूद नहीं: सब tool errors। +* **`ToolError` raise करें** जब नाकामी **execution** की हो: आपके tool ने जो करने की कोशिश की, वह नहीं हुआ। call model ने चुना था, इसलिए नतीजा भी model को दिखना चाहिए और उसे संभलने का मौका मिलना चाहिए। गलत वर्तनी वाला title, timeout हो गया upstream API, ऐसी row जो मौजूद नहीं: सब tool errors। * **`MCPError` raise करें** जब **request खुद** ठुकराई जानी चाहिए: client के पास वह capability नहीं जिस पर आपका tool निर्भर है, server किसी को भी serve करने की हालत में नहीं है, caller ने कोई ज़रूरी चरण छोड़ दिया। model का कोई retry इनमें से किसी को ठीक नहीं करता, इसलिए उसे message थमाने से कुछ हासिल नहीं। -एक सवाल से फ़ैसला हो जाता है: **क्या ज़्यादा समझदार model इससे बच सकता था?** हाँ -> साधारण exception। नहीं -> `MCPError`। +एक सवाल से फ़ैसला हो जाता है: **क्या ज़्यादा समझदार model इससे बच सकता था?** हाँ -> `ToolError`। नहीं -> `MCPError`। इस कसौटी पर `get_author` के दूसरे version ने गलत चुनाव किया: बेहतर title से बात बन जाती है, इसलिए model message देखने का हक़दार था। वह version आपको mechanism दिखाने के लिए है, उसकी सिफ़ारिश करने के लिए नहीं। @@ -89,6 +91,25 @@ result.structured_content # None `data` payload लेता है। इनमें आप जो भी रखें, client को वही मिलता है: SDK raise किए गए `MCPError` को sanitise करने के बजाय जस का तस आगे भेज देता है। +## कोई और exception {#any-other-exception} + +अब check हटा दें और dictionary lookup को अपने आप fail होने दें: + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]` `KeyError` raise करता है। आपने इसकी कोई योजना नहीं बनाई थी, इसलिए SDK इसे crash मानता है: + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +call अब भी `is_error=True` लौटाता है, इसलिए model जानता है कि वह fail हुआ और आगे बढ़ सकता है। जो उसे नहीं मिलता वह है exception का text: आपके code का `KeyError`, या तीन libraries नीचे के किसी driver से आया SQL का ढेर, आपके server की अंदरूनी बातें बयान कर सकता है, इसलिए वह server से बाहर कभी नहीं जाता। + +वह आपको मिलता है। server crash को पूरे traceback के साथ `ERROR` पर log करता है, `Tool 'get_author' raised an unexpected exception` के रूप में। इसलिए `WARNING` पर चलने वाला production log हर `ToolError` के दौरान चुप रहता है और जैसे ही कुछ सच में टूटता है, बोल उठता है। + ## ऐसा resource जो मौजूद नहीं है {#a-resource-that-doesnt-exist} resources भी यही रेखा खींचते हैं, और आम मामले के लिए एक नाम वाला exception साथ देते हैं। @@ -109,7 +130,7 @@ resources भी यही रेखा खींचते हैं, और आ } ``` -ध्यान दें, यहाँ कोई `is_error=True` वाला आधा-अधूरा result नहीं है। resource read या तो contents लौटाता है या fail होता है: resources के पास सिर्फ़ protocol वाला रास्ता है। templates और resources के बारे में बाकी सब कुछ **[Resources](resources.md)** में है। +ध्यान दें, यहाँ कोई `is_error=True` वाला आधा-अधूरा result नहीं है। resource read या तो contents लौटाता है या fail होता है: resources के पास सिर्फ़ protocol वाला रास्ता है। `ResourceError` वही चीज़ है ऐसी नाकामी के लिए जो "not found" नहीं है (`-32603`, आपका message), और दोनों आपके log में एक `INFO` line हैं। `MCPError` को छोड़कर कोई भी और exception crash है: client को `-32603` मिलता है जिसमें सिर्फ़ URI का नाम होता है, और traceback `ERROR` पर आपके log में जाता है। templates और resources के बारे में बाकी सब कुछ **[Resources](resources.md)** में है। ## ऐसे errors जो आप कभी raise नहीं करते {#errors-you-never-raise} @@ -120,19 +141,21 @@ resources भी यही रेखा खींचते हैं, और आ इसका मतलब है `raise` statements की एक पूरी श्रेणी जो आपको लिखनी नहीं पड़ती: अपने ही type hints को दोबारा validate न करें। !!! info - इस page पर सब कुछ वही है जो **client** को दिखता है, और जिस in-memory `Client` से आप - tests लिखेंगे, उसे भी ठीक यही दिखता है। `raise_exceptions=True` भी tool error को वापस - traceback में नहीं बदलता: जब तक वह flag कुछ कर पाता, आपका exception पहले ही - `is_error=True` result बन चुका होता है। result पर assert करें। **[Testing](../get-started/testing.md)** में यह pattern बताया गया है। + इस page पर जो कुछ **client** को दिखता है, वह उस in-memory `Client` को भी दिखता है जिससे आप + tests लिखेंगे। `raise_exceptions=True` भी fail होते tool का exception caller को वापस नहीं + थमाता: जब तक वह flag कुछ कर पाता, आपका exception पहले ही `is_error=True` result बन चुका + होता है। result पर assert करें। crash का traceback चाहिए तो वह server के log में है, और + pytest का `caplog` उसे capture कर लेता है। **[Testing](../get-started/testing.md)** में यह pattern बताया गया है। ## सारांश {#recap} -* tool में **कोई भी exception** raise करें -> call `is_error=True` लौटाता है, `content` में आपके message के साथ। model उसे पढ़ता है और retry कर सकता है। यही default है। +* tool में **`ToolError`** raise करें -> call `is_error=True` लौटाता है, `content` में आपके message के साथ। model उसे पढ़ता है और retry कर सकता है। * **`MCPError`** raise करें -> call खुद JSON-RPC error के साथ fail हो जाता है। model को कुछ नहीं दिखता; host इससे निपटता है। `code`, `message`, और `data` जस के तस बचे रहते हैं। -* फ़ैसला करने वाला सवाल: **क्या ज़्यादा समझदार model इससे बच सकता था?** हाँ -> exception। नहीं -> `MCPError`। +* फ़ैसला करने वाला सवाल: **क्या ज़्यादा समझदार model इससे बच सकता था?** हाँ -> `ToolError`। नहीं -> `MCPError`। +* कोई भी **और exception** crash है -> model के लिए `is_error=True` जिसमें सिर्फ़ `Error executing tool `, और आपके लिए traceback वाला `ERROR` record। * resource handler से `ResourceNotFoundError` -> protocol का `-32602`, `data` में URI के साथ। * गलत arguments आपका function चलने से पहले ही schema के आधार पर ठुकरा दिए जाते हैं; उनके लिए आप `raise` नहीं करते। -* `from mcp import MCPError`; error-code constants `mcp.types` से आते हैं। +* Imports: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, और error-code constants `mcp.types` से। errors संभल गए। server जो कुछ **expose** करता है, वह सब यही है। हर handler क्या पढ़ सकता है, और चलते-चलते client के साथ वापस क्या कर सकता है, यह अगला section है: **[आपके handler के अंदर](../handlers/index.md)**। diff --git a/i18n/hi/pages/servers/media.md b/i18n/hi/pages/servers/media.md index 7f2373379b..2e81727c0f 100644 --- a/i18n/hi/pages/servers/media.md +++ b/i18n/hi/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # Media {#media} @@ -86,6 +86,24 @@ result.structured_content # None MP3 bytes से `Audio` बनाएँ तो client को `mime_type="audio/wav"` बताया जाता है, और फिर वह ईमानदारी से उसे decode करने में नाकाम रहता है। जब `data=` दें, तो `format=` भी दें। +## resource embed करना {#embedding-a-resource} + +tool एक document भी लौटा सकता है: कुछ text या bytes, साथ में वह URI जहाँ वह रहता है और एक MIME type। यह **`EmbeddedResource`** है, content block की एक और किस्म। सादे `str` के उलट यह client को बताता है कि content क्या है, ताकि client उसे attachment की तरह दिखा सके या ऐसे resource को पहचान सके जिसे वह पहले से जानता है। + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines` एक साधारण resource है (इनके बारे में **[Resources](resources.md)** बताता है)। माँगे जाने पर tool वही document model को सौंपता है, और `guidelines()` को सीधे call करने से सच का एक ही स्रोत बना रहता है। +* `EmbeddedResource` और `TextResourceContents` `mcp.types` से आते हैं। images जैसा कोई helper यहाँ नहीं है: जो block आप बनाते हैं वह बिना छुए result में जाता है, और कोई `structured_content` नहीं होता। +* वही URI इस्तेमाल करें जिसके तहत resource register है, ताकि client बता सके कि attachment और `brand://guidelines` एक ही document हैं। कोई भी URI मान्य है, register हो या न हो। + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +binary content के लिए `TextResourceContents` की जगह `BlobResourceContents(uri=..., mime_type=..., blob=...)` इस्तेमाल करें, जिसमें bytes base64-encoded होकर `blob` में जाते हैं। सिर्फ़ एक pointer भेजना हो, जिसे client बाद में `resources/read` कर सके, तो उसकी जगह `ResourceLink(name=..., uri=...)` लौटाएँ; यह भी content block ही है। + ## Icons {#icons} `Icon` metadata है, content नहीं। इसमें image नहीं होती; यह URI से किसी image की ओर इशारा करता है, और client उसे fetch करके आपके server के नाम, किसी tool, resource या prompt के बगल में दिखा सकता है। @@ -115,6 +133,7 @@ tool के icons `tools/list` से मिले `Tool` object पर हो * tool से `Image` या `Audio` लौटाएँ तो client को `ImageContent` / `AudioContent` block मिलता है: आपके bytes base64-encoded, MIME type के साथ। * इसे `path=` से बनाएँ और suffix को MIME type तय करने दें, या in-memory `data=` और स्पष्ट `format=` से बनाएँ। +* result में कोई document (text या base64 blob, उसके URI और MIME type के साथ) डालने के लिए `EmbeddedResource` लौटाएँ, या सिर्फ़ pointer भेजने के लिए `ResourceLink`। * media results में न `structured_content` होता है, न output schema। * `Icon` एक pointer है: `src` URI और साथ में optional `mime_type`, `sizes` और `theme`। * `icons=[...]` server पर, tools पर, resources पर और prompts पर काम करता है, और clients इन्हें संबंधित objects पर पाते हैं। diff --git a/i18n/hi/pages/servers/prompts.md b/i18n/hi/pages/servers/prompts.md index c270e30175..d49efceb6b 100644 --- a/i18n/hi/pages/servers/prompts.md +++ b/i18n/hi/pages/servers/prompts.md @@ -1,6 +1,6 @@ --- translation: - sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094] + sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd] tool: 1 --- # Prompts {#prompts} @@ -112,9 +112,9 @@ Code review एक message है। Debugging session एक बातचीत आख़िरी message पर ध्यान दें। `assistant` turn पहले से भरना ही वह तरीका है जिससे आप model के **अगले** जवाब की दिशा तय करते हैं, बिना user से वह निर्देश खुद type करवाए। -## Titles और argument descriptions {#titles-and-argument-descriptions} +## titles और argument descriptions {#titles-and-argument-descriptions} -`review_code` function का नाम है, label नहीं। Client को button पर लगाने के लिए कुछ बेहतर दें, और हर argument का description लिखें ताकि form खुद ही समझ में आ जाए: +`review_code` function का नाम है, label नहीं। client को button पर लगाने के लिए कुछ बेहतर दें, और हर argument का description लिखें ताकि form खुद ही समझ में आ जाए: ```python title="server.py" hl_lines="10-13" --8<-- "docs_src/prompts/tutorial003.py" @@ -139,17 +139,64 @@ Code review एक message है। Debugging session एक बातचीत ``` !!! info - अगर आपने **[Tools](tools.md)** पढ़ लिया है, तो इस page की हर बात आप पहले से जानते हैं। वही decorator, वही + अगर आपने **[Tools](tools.md)** पढ़ लिया है, तो यहाँ तक की हर बात आप पहले से जानते हैं। वही decorator, वही docstring-as-description, वही `Annotated`/`Field`। बदलता सिर्फ़ इतना है कि इसे trigger कौन करता है (user) और result कहाँ जाता है (बातचीत में)। +## सिर्फ़ text ही नहीं {#more-than-text} + +`UserMessage` और `AssistantMessage` जहाँ भी `str` लेते हैं, वहाँ content block या `Image` / `Audio` helper भी ले लेते हैं। prompts में दो मामले सामने आते हैं: document जोड़ना और तस्वीर जोड़ना। + +### file embed करना {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* style guide `style://python` पर एक resource है (**[Resources](resources.md)** में इनकी बात है), जो `server.py` के बगल में रखी `style-guide.md` से पढ़ा जाता है। वहाँ कोई भी Markdown file रख दें। +* `EmbeddedResource(resource=TextResourceContents(...))`, दोनों `mcp.types` से, file को उसके URI और MIME type के साथ पहले message के रूप में ले जाता है; उसका ज़िक्र करने वाली request उसके बाद plain text के रूप में आती है। +* guide को f-string में चिपकाने के बजाय embed करने से client उसे attachment की तरह दिखा सकता है और बाद में `style://python` फिर से खोल सकता है, और model को file ज्यों की त्यों मिलती है। binary file के लिए base64 `blob` के साथ `BlobResourceContents` इस्तेमाल करें। + +render होने पर पहले message का `content` एक `resource` block है: + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### image जोड़ना {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image` **[Images, audio और icons](media.md)** वाला helper है। prompt render होते समय `UserMessage` इसे `ImageContent` block में बदल देता है (file base64-encoded, MIME type `.png` से अंदाज़ा लगाया गया); `Audio` इसी तरह `AudioContent` बन जाता है। +* `server.py` के बगल में `architecture.png` नाम की कोई भी PNG रख दें। prompt arguments strings होते हैं, इसलिए तस्वीर हमेशा server से आती है; `component` सिर्फ़ शब्द देता है। + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## runtime पर list बदलना {#changing-the-list-at-runtime} + +clients जुड़े रहते हुए भी prompts जोड़े जा सकते हैं, उदाहरण के लिए ताकि user किसी निर्देश को अपनी खुद की menu entry के रूप में save कर सके। prompt register करें, फिर notify करें: + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` किसी function को ठीक वैसे ही register करता है जैसे `@mcp.prompt()` करता, और `mcp.remove_prompt(name)` इसका उल्टा है। `add_prompt` उसी नाम की मौजूदा entry को overwrite करने के बजाय बनाए रखता है, इसलिए tool पहले कोई भी पुरानी entry हटा देता है ताकि save करना replace बन जाए। `prompts/list` बदलाव तुरंत दिखाती है। +* `await ctx.notify_prompts_changed()` हर उस `2026-07-28` client को `notifications/prompts/list_changed` भेजता है जो `subscriptions/listen` stream पर सुन रहा हो (**[Subscriptions](../handlers/subscriptions.md)**)। `await ctx.session.send_prompt_list_changed()` इसे call करने वाले client को भेजता है, जब वह client 2026 से पहले का हो (**[legacy clients को serve करना](../run/legacy-clients.md)**)। दोनों call करें; जब बताने के लिए कोई न हो तो दोनों में से कोई कुछ नहीं करता। +* जिस client को notification मिलता है, वह `prompts/list` फिर से call करता है। Python `Client` में यह `async with client.listen(prompts_list_changed=True) as sub:` है, जो `PromptsListChanged` event देता है। + ## सारांश {#recap} -* Function पर `@mcp.prompt()` लगाने से वह prompt बन जाता है। नाम function से, description docstring से। -* Prompts **user-controlled** हैं: client इन्हें list करता है, user कोई एक चुनता है और arguments भरता है। -* Arguments named strings की flat list हैं (कोई schema नहीं)। Default वाला parameter optional है। -* `str` लौटाएँ और वह एक user message बन जाता है। Multi-turn बातचीत की शुरुआत करने के लिए `UserMessage` / `AssistantMessage` की list लौटाएँ। +* function पर `@mcp.prompt()` लगाने से वह prompt बन जाता है। नाम function से, description docstring से। +* prompts **user-controlled** हैं: client इन्हें list करता है, user कोई एक चुनता है और arguments भरता है। +* arguments named strings की flat list हैं (कोई schema नहीं)। default वाला parameter optional है। +* `str` लौटाएँ और वह एक user message बन जाता है। multi-turn बातचीत की शुरुआत करने के लिए `UserMessage` / `AssistantMessage` की list लौटाएँ। * `title=` और `Field(description=...)` वही हैं जो client अपने UI में दिखाता है। * कोई required argument छूट जाए तो पूरी request fail होती है। हर prompt का अलग error result नहीं होता। +* document या तस्वीर जोड़ने के लिए `EmbeddedResource` या `Image` को `UserMessage` में wrap करें। +* runtime पर `mcp.add_prompt(...)` / `mcp.remove_prompt(...)` से prompts जोड़ें या हटाएँ, फिर `await ctx.notify_prompts_changed()` और `await ctx.session.send_prompt_list_changed()` call करें। -Prompt के (या resource template के) arguments के लिए server-side autocomplete **[Completions](completions.md)** में है। +prompt के (या resource template के) arguments के लिए server-side autocomplete **[Completions](completions.md)** में है। diff --git a/i18n/hi/pages/servers/structured-output.md b/i18n/hi/pages/servers/structured-output.md index d760a9ce30..ec29b68bc0 100644 --- a/i18n/hi/pages/servers/structured-output.md +++ b/i18n/hi/pages/servers/structured-output.md @@ -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 --- # Structured output {#structured-output} @@ -105,7 +105,7 @@ result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions --8<-- "docs_src/structured_output/tutorial003.py" ``` -runtime पर `TypedDict` सादा `dict` होता है, इसलिए आप वही बनाते और लौटाते हैं। schema, validation और `structured_content` ठीक `BaseModel` वाले version जैसे हैं (descriptions को छोड़कर, जिनके लिए `TypedDict` में कोई जगह नहीं)। +runtime पर `TypedDict` सादा `dict` होता है, इसलिए आप वही बनाते और लौटाते हैं। schema, validation और `structured_content` उन्हीं नियमों पर चलते हैं जिन पर `BaseModel` वाला version चलता है: class docstring या `Annotated[..., Field(description=...)]` जोड़ें और वही descriptions बन जाते हैं, और जो `NotRequired` key आप dict में नहीं डालते, वह `structured_content` से भी बाहर रहती है। ## Dataclass {#a-dataclass} @@ -187,18 +187,19 @@ keys का `str` होना ज़रूरी है। `dict[int, float]` J annotation `WeatherData` का वादा करता है। upstream response ने `humidity` भेजना बंद कर दिया। !!! check - `get_weather` को call करें और यह चुपचाप client को आधा-खाली object नहीं थमाता। call fail होता है, - और error की पहली lines field का नाम बताती हैं: + `get_weather` को call करें और यह चुपचाप client को आधा-खाली object नहीं थमाता। call fail होता है: + client को `Error executing tool get_weather` के साथ `is_error=True` मिलता है, ताकि model को पता रहे कि + call fail हुआ है, बजाय इसके कि वह पूरे भरोसे से ऐसा मौसम पढ़े जो है ही नहीं। field का नाम आपके लिए है, + server log में `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] ``` - यह text `is_error=True` के साथ tool result बनकर लौटता है, ताकि model को पता रहे कि call fail हुआ है, - बजाय इसके कि वह पूरे भरोसे से ऐसा मौसम पढ़े जो है ही नहीं। - वैसे, `-> WeatherData` वाले tool से सादा `dict` लौटाना ठीक है। `json.loads` ने ठीक वही तो बनाया था। validation value पर होता है, Python type पर नहीं। ## इससे बाहर रहना {#opting-out} @@ -213,6 +214,10 @@ annotation `WeatherData` का वादा करता है। upstream res इसका उल्टा, `structured_output=True`, automatic detection को शर्त बना देता है: जिस tool का return type schema नहीं बना सकता, वह text पर वापस आने के बजाय import के समय ही raise करता है। +## Content blocks और media {#content-blocks-and-media} + +content blocks और media (`TextContent`, `EmbeddedResource`, `Image`, `Audio` वगैरह, चाहे अकेले हों, किसी `list`, `tuple` या `Sequence` के items हों, या किसी union के हिस्से हों) आपके लिए अपने आप इससे बाहर रखे जाते हैं: ये model के पढ़ने के लिए हैं, इसलिए auto-detection इनसे कोई schema नहीं बनाता (`Image` और `Audio` की जानकारी **[Images, audio और icons](media.md)** में है)। content-block classes के लिए `structured_output=True` फिर भी schema बनवा देता है। + ## बिना type hints वाली class {#a-class-without-type-hints} बिना माँगे unstructured रह जाने का एक तरीका है: ऐसी class लौटाना जिसकी **body पर कोई annotations न हों**। @@ -245,6 +250,6 @@ annotation `WeatherData` का वादा करता है। upstream res * scalars, lists, tuples और unions `{"result": ...}` में wrap होते हैं। models, `TypedDict`, dataclasses, annotated classes और `dict[str, ...]` पहले से object हैं और जैसे हैं वैसे ही रहते हैं। * हर result में `content` (text, model के लिए) **और** `structured_content` (data, application के लिए) होता है। * आप जो लौटाते हैं वह schema के मुक़ाबले validate होता है। मेल न खाना tool error है, ख़राब result नहीं। -* `structured_output=False` tool को इससे बाहर रखता है। बिना type hints वाली class चुपचाप बाहर हो जाती है; इस पर नज़र रखें। +* `structured_output=False` tool को इससे बाहर रखता है। content blocks, `Image` और `Audio` default रूप से बाहर रहते हैं; बिना type hints वाली class चुपचाप बाहर हो जाती है, इसलिए इस पर नज़र रखें। अब tool जो कुछ भी जवाब में कह सकता है, वह सब आपके हाथ में है। आगे, दूसरा primitive: **[Resources](resources.md)**। diff --git a/i18n/hi/pages/servers/tools.md b/i18n/hi/pages/servers/tools.md index 3abe183a96..2b6405e13e 100644 --- a/i18n/hi/pages/servers/tools.md +++ b/i18n/hi/pages/servers/tools.md @@ -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 @@ translation: दोनों arguments `required` में हैं क्योंकि किसी का भी default नहीं है। इसे आप थोड़ी ही देर में ठीक करेंगे। (`title` keys Pydantic की देन हैं; properties, उनके types और `required` ही असली contract हैं।) +`$schema` key भी नहीं है: जिस schema में यह न हो उसे MCP **JSON Schema 2020-12** मानता है, और Pydantic यही बनाता है, इसलिए चुनने को कुछ नहीं है जब तक आप **[low-level Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)** पर हाथ से schemas न लिखें। + !!! tip यहाँ type hints documentation नहीं हैं। वे ही **contract** हैं। अगर कोई client `"limit": "ten"` भेजता है, तो SDK उसे आपके function के चलने से पहले ही reject कर देता है। diff --git a/i18n/hi/pages/servers/uri-templates.md b/i18n/hi/pages/servers/uri-templates.md index 2de67a4834..c562f261ef 100644 --- a/i18n/hi/pages/servers/uri-templates.md +++ b/i18n/hi/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # URI templates और path safety {#uri-templates-and-path-safety} @@ -115,7 +115,7 @@ template parameters client से आते हैं। अगर वे बि built-in जाँचें आम मामलों को रोकती हैं लेकिन आपकी sandbox सीमा नहीं जान सकतीं। filesystem access के लिए, path resolve करने और यह पक्का करने के लिए कि वह आपकी base directory के अंदर ही रहे, `safe_join` इस्तेमाल करें: -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` @@ -146,7 +146,7 @@ configure की जा सकने वाली जाँचें: `safe_join` ही containment boundary बना रहता है। !!! tip - अगर आपका handler request पूरी नहीं कर सकता (file मौजूद नहीं है, id अनजान है), तो exception raise करें। SDK उसे error response में बदल देता है। protocol error और tool error के बीच के फ़र्क़ के लिए **[errors संभालना](handling-errors.md)** देखें। + अगर आपका handler request पूरी नहीं कर सकता (file मौजूद नहीं है, id अनजान है), तो `ResourceNotFoundError` raise करें, जैसा ऊपर `read_manual` करता है। client को आपके message और URI के साथ `-32602` मिलता है। कोई अनपेक्षित exception इसकी जगह generic `-32603` बन जाता है। **[errors संभालना](handling-errors.md#a-resource-that-doesnt-exist)** देखें। ## Low-level Server पर resources {#resources-on-the-low-level-server} diff --git a/i18n/hi/pages/troubleshooting.md b/i18n/hi/pages/troubleshooting.md index f979f3459f..bb0638ede1 100644 --- a/i18n/hi/pages/troubleshooting.md +++ b/i18n/hi/pages/troubleshooting.md @@ -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 --- # समस्याएँ सुलझाना {#troubleshooting} @@ -81,11 +81,11 @@ async def main() -> None: `__aexit__` ही disconnection है, इसीलिए भूल जाने लायक कोई `client.close()` है ही नहीं। **[Testing](get-started/testing.md)** ठीक इसी pattern पर बना है। -## `Error executing tool : ` और `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `, `Error executing tool `, और `Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} आप एक **result** पढ़ रहे हैं, exception नहीं। `call_tool` ने raise नहीं किया, और fail होने वाले tool के लिए वह कभी करेगा भी नहीं। -`forecast` को ऐसे city के लिए call करें जिसे server नहीं जानता, तो उसका raise किया हुआ exception वापस आता है और request **सफल** mark होती है: +`forecast` को ऐसे city के लिए call करें जिसे server नहीं जानता, तो उसका raise किया हुआ `ToolError` वापस आता है और request **सफल** mark होती है: ```python result.is_error # True @@ -97,6 +97,8 @@ result.structured_content # None सुधार आपके client में है: **`result.is_error` जाँचें**। `call_tool` के चारों ओर लगा `try/except` इनमें से कुछ नहीं पकड़ता, क्योंकि पकड़ने को कुछ है ही नहीं। यह जान-बूझकर है, और इस page की सबसे काम की बात यही है जिसे मन में बिठा लें: call **model** ने चुना था, इसलिए message भी model को मिलता है और दोबारा कोशिश करने का मौका भी। पूरी जानकारी **[errors संभालना](servers/handling-errors.md)** में है, उस `MCPError` वाले रास्ते समेत जो सच में raise **करता** है। +बिना message वाला सादा रूप, `Error executing tool `, बताता है कि tool **crash हुआ**: कोई exception जिसकी उसने उम्मीद नहीं की थी उससे बाहर निकल गया (या उसकी return value output schema पर खरी नहीं उतरी), और उस exception का text wire से दूर रखा जाता है। traceback **server के log** में `ERROR` पर है, `Tool '' 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} आपने `@mcp.tool()` की जगह `@mcp.tool` लिख दिया। `tool()` एक decorator **factory** है: parentheses के बिना Python आपका function उसके `name=` parameter को थमा देता है। @@ -409,7 +411,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## सारांश {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup` कभी असली error नहीं है। **आखिरी line** पढ़ें; `async with Client(...)` block के **अंदर** `MCPError` catch करने से wrapping पूरी तरह टल जाती है। -* `call_tool` fail होने वाले tool के लिए raise नहीं करता। `Error executing tool ...` और `Unknown tool: ...` results हैं: `result.is_error` जाँचें। +* `call_tool` fail होने वाले tool के लिए raise नहीं करता। `Error executing tool ...` और `Unknown tool: ...` results हैं: `result.is_error` जाँचें। tool के नाम के बाद कोई message न हो तो मतलब वह crash हुआ, और traceback server log में है। * `Client must be used within an async context manager` -> `async with` इस्तेमाल करें। `Use @tool() instead of @tool` -> parentheses जोड़ें। * server log में `Tool already exists:` ही इकलौता संकेत है कि एक ही नाम के दो tools सिमटकर एक रह गए। * एक 421, तीन रूप: `Server returned an error response` (python `Client`), `421 Misdirected Request` / `Invalid Host header` (बाकी सब), `Invalid Host header: ` (server log)। सुधार: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`। diff --git a/i18n/hi/pages/whats-new.md b/i18n/hi/pages/whats-new.md index 220fa68ef4..3550445df7 100644 --- a/i18n/hi/pages/whats-new.md +++ b/i18n/hi/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # v2 में नया क्या है {#whats-new-in-v2} @@ -18,7 +18,7 @@ v2 में दो चीज़ें एक साथ हुईं। **SDK ### `FastMCP` अब `MCPServer` है {#fastmcp-is-now-mcpserver} -High-level server class का नाम बदला, और उसके module का भी। हर v1 server सबसे पहले इसी से टकराता है, क्योंकि पुराना import path deprecated नहीं, बल्कि हटा दिया गया है: +high-level server class का नाम बदला, और साथ में उसके module का भी। हर v1 server सबसे पहले इसी से टकराता है, क्योंकि पुराना import path deprecated नहीं, बल्कि हटा दिया गया है: ```python from mcp.server import MCPServer # v1: from mcp.server.fastmcp import FastMCP @@ -26,11 +26,11 @@ from mcp.server import MCPServer # v1: from mcp.server.fastmcp import FastMCP mcp = MCPServer("Demo") # v1: FastMCP("Demo") ``` -Decorator से बने server के लिए port का ज़्यादातर हिस्सा भी बस यही है। `@mcp.tool()`, `@mcp.resource()` और `@mcp.prompt()` वही स्वीकार करते हैं जो v1 में करते थे (`@mcp.resource()` में एक optional `security=` keyword जुड़ा है), और input schema अब भी आपके type hints से आता है। किनारों पर: `mcp.server.fastmcp.*` के नीचे की हर चीज़ अब `mcp.server.mcpserver.*` के नीचे रहती है, `ctx.fastmcp` अब `ctx.mcp_server` है, `get_context()` हटा दिया गया है (उसकी जगह `ctx: Context` parameter declare करें), और exception base `FastMCPError` अब `MCPServerError` है। Import table **[Migration Guide](migration.md#fastmcp-renamed-to-mcpserver)** में है। +decorator से बने server के लिए port का ज़्यादातर हिस्सा भी बस यही है। `@mcp.tool()`, `@mcp.resource()` और `@mcp.prompt()` वही स्वीकार करते हैं जो v1 में करते थे (`@mcp.resource()` में एक optional `security=` keyword जुड़ा है), और input schema अब भी आपके type hints से आता है। किनारों पर: `mcp.server.fastmcp.*` के नीचे की हर चीज़ अब `mcp.server.mcpserver.*` के नीचे रहती है, `ctx.fastmcp` अब `ctx.mcp_server` है, `get_context()` हटा दिया गया है (उसकी जगह `ctx: Context` parameter declare करें), और exception base `FastMCPError` अब `MCPServerError` है। import table **[Migration Guide](migration.md#fastmcp-renamed-to-mcpserver)** में है। ### `Resolve`: user से input माँगने का नया तरीका {#resolve-the-new-way-to-ask-the-user-for-input} -Tool को जो कुछ चाहिए, वह सब model से नहीं आना चाहिए। v2 में नया: `Resolve(fn)` से annotate किया गया tool parameter model के बजाय आपके लिखे function से भरा जाता है, model को इसकी भनक तक नहीं लगती, और वह function user के सामने सवाल रखने के लिए `Elicit(...)` लौटा सकता है। Call के बीच client से कुछ भी पाने का यही पसंदीदा तरीका है: SDK सवाल को उसी mechanism पर ले जाता है जिसे connection support करता है (legacy client के लिए live elicitation request, 2026-07-28 पर multi-round-trip), इसलिए एक ही tool body दोनों पीढ़ियों को serve करती है। इसका page **[Dependencies](handlers/dependencies.md)** है। +tool को जो कुछ चाहिए, वह सब model से नहीं आना चाहिए। v2 में नया: `Resolve(fn)` से annotate किया गया tool parameter model के बजाय आपके लिखे function से भरा जाता है, model को इसकी भनक तक नहीं लगती, और वह function user के सामने सवाल रखने के लिए `Elicit(...)` लौटा सकता है। call के बीच client से कुछ भी पाने का यही पसंदीदा तरीका है: SDK सवाल को उसी mechanism पर ले जाता है जिसे connection support करता है (legacy client के लिए live elicitation request, 2026-07-28 पर multi-round-trip), इसलिए एक ही tool body दोनों पीढ़ियों को serve करती है। इसका page **[Dependencies](handlers/dependencies.md)** है। !!! note बाकी दो रूप ज़रूरत पड़ने पर अब भी मौजूद हैं: legacy connections पर clients के लिए `ctx.elicit()` अब भी काम करता है @@ -38,7 +38,7 @@ Tool को जो कुछ चाहिए, वह सब model से नह rounds को हाथ से चला सकता है, और 2026-07-28 पर sampling और roots requests भी इसी रास्ते से जाती हैं (**[Multi-round-trip requests](handlers/multi-round-trip.md)**)। -### एक first-class `Client` {#a-first-class-client} +### first-class `Client` {#a-first-class-client} v1 आपको तीन nested परतें थमाता था: raw streams देने वाला transport context manager, उनके चारों ओर लिपटा `ClientSession`, और हाथ से call किया जाने वाला `await session.initialize()`। v2 में एक ही object है: @@ -46,11 +46,11 @@ v1 आपको तीन nested परतें थमाता था: raw str --8<-- "docs_src/client/tutorial001.py" ``` -`Client` एक server object लेता है (in memory, कोई transport नहीं: testing की कहानी), एक URL (Streamable HTTP), या कोई भी transport context manager जैसे `stdio_client(...)`। `async with` में प्रवेश करते ही connect होता है और protocol version negotiate होता है, server चाहे जिस पीढ़ी का हो; उसके बाद `client.server_capabilities` और `client.protocol_version` बस उपलब्ध रहते हैं, और जब server अपनी पहचान बताता है तो `client.server_info` भी (यह अब `Implementation | None` है, क्योंकि 2026 पीढ़ी में identity optional है)। v1 में register किए गए sampling और elicitation callbacks अब भी काम करते हैं (उनकी bodies में वही snake_case attribute rename दिखता है जो इस page की हर चीज़ में), वे अब 2026-style requests-inside-results (नीचे) का जवाब भी देते हैं, और वे एक-एक करके नहीं, बल्कि concurrently चलते हैं। जिसे low-level surface चाहिए, उसके लिए `ClientSession` अब भी नीचे मौजूद है, और `client.session` उसे आपको देता है; वह भी बदला है (वह नए dispatcher engine पर चलता है, और उसके कुछ अपने signatures बदले हैं), इसलिए नीचे उतरने से पहले **[Migration Guide](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** पढ़ें। +`Client` server object लेता है (in memory, कोई transport नहीं: testing वाली कहानी), URL (Streamable HTTP), `StdioServerParameters` (stdio subprocess), या `sse_client(...)` जैसा कोई भी दूसरा transport context manager। `async with` में प्रवेश करते ही connect होता है और protocol version negotiate होता है, server चाहे जिस पीढ़ी का हो; उसके बाद `client.server_capabilities` और `client.protocol_version` बस उपलब्ध रहते हैं, और जब server अपनी पहचान बताता है तो `client.server_info` भी (यह अब `Implementation | None` है, क्योंकि 2026 पीढ़ी में identity optional है)। v1 में register किए गए sampling और elicitation callbacks अब भी काम करते हैं (उनकी bodies में वही snake_case attribute rename दिखता है जो इस page की हर चीज़ में), वे अब 2026-style requests-inside-results (नीचे) का जवाब भी देते हैं, और वे एक-एक करके नहीं, बल्कि concurrently चलते हैं। जिसे low-level surface चाहिए, उसके लिए `ClientSession` अब भी नीचे मौजूद है, और `client.session` उसे आपको देता है; वह भी बदला है (वह नए dispatcher engine पर चलता है, और उसके कुछ अपने signatures बदले हैं), इसलिए नीचे उतरने से पहले **[Migration Guide](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** पढ़ें। -**[The Client](client/index.md)** इसका परिचय देता है, **[Client transports](client/transports.md)** connection के तीनों रूप समझाता है, **[Client callbacks](client/callbacks.md)** खुद callbacks को, और **[Testing](get-started/testing.md)** वह in-memory pattern दिखाता है जो v1 के `create_connected_server_and_client_session()` helper की जगह लेता है। +**[The Client](client/index.md)** इसका परिचय देता है, **[Client transports](client/transports.md)** connection के चारों रूप समझाता है, **[Client callbacks](client/callbacks.md)** खुद callbacks को, और **[Testing](get-started/testing.md)** वह in-memory pattern दिखाता है जो v1 के `create_connected_server_and_client_session()` helper की जगह लेता है। -### Low-level `Server` का नाम नहीं बदला, उसे दोबारा बनाया गया {#the-low-level-server-was-rebuilt-not-renamed} +### low-level `Server` का नाम नहीं बदला, उसे दोबारा बनाया गया {#the-low-level-server-was-rebuilt-not-renamed} अगर आप JSON-RPC layer पर काम करते हैं, तो v2 का "सब कुछ अलग है" वाला हिस्सा यही है। यहाँ वही one-tool server दोनों तरह से है; क्या बदला, यह देखने के लिए markers पर click करें। @@ -90,66 +90,66 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB return [types.TextContent(type="text", text=f"Found 3 books matching {arguments['query']!r}.")] # (7)! ``` -1. Handlers decorators से register होते हैं (call किए गए, parentheses के साथ), server बनने के बाद कभी भी। +1. handlers decorators से register होते हैं (call किए गए, parentheses के साथ), server बनने के बाद कभी भी। 2. आप bare `list[Tool]` लौटाते हैं और SDK उसे `ListToolsResult` में लपेट देता है। 3. Python में fields camelCase हैं, और schema **enforce होता है**: SDK आपके function के चलने से पहले `call_tool` arguments को इसके सामने jsonschema-validate करता है, इसीलिए नीचे `arguments["query"]` सुरक्षित है। 4. एक ही `call_tool` handler हर tool को serve करता है, और उसे tool का नाम और पहले से validate किए हुए arguments मिलते हैं, unpack किए हुए और कभी `None` नहीं। 5. v1 tool failure का संकेत raise करके देता है: कोई भी exception पकड़ा जाता है और `CallToolResult(isError=True)` के रूप में लौटाया जाता है, text में `str(e)` के साथ, इसलिए call करने वाला model यह message पढ़ता है और retry कर सकता है। -6. Context एक ambient ContextVar से आता है, जिस तक request के बीच server object के ज़रिए पहुँचा जाता है। -7. Bare content blocks आपके लिए `CallToolResult` में लपेट दिए जाते हैं। +6. context एक ambient ContextVar से आता है, जिस तक request के बीच server object के ज़रिए पहुँचा जाता है। +7. bare content blocks आपके लिए `CallToolResult` में लपेट दिए जाते हैं। ```python title="v2" --8<-- "docs_src/whats_new/tutorial001.py" ``` -1. Fields अब snake_case हैं, और schema **advertise होता है, पर कभी apply नहीं होता**: आपके handler के चलने से पहले arguments को कोई नहीं जाँचता। -2. हर handler का आकार एक जैसा है: `async (ctx, params) -> result`। Context पहला argument है (`ctx.session`, `ctx.request_id`, `ctx.protocol_version` इसी पर रहते हैं); `server.request_context` यहीं गया। -3. पूरा `ListToolsResult` आप खुद बनाते हैं। Bare list लौटाना अब server-side `TypeError` है, SDK उसे लपेटता नहीं। -4. Typed params अंदर (`params.name`, `params.arguments`), पूरा result बाहर। आपके लिए कुछ भी unpack, wrap या convert नहीं किया जाता। +1. fields अब snake_case हैं, और schema **advertise होता है, पर कभी apply नहीं होता**: आपके handler के चलने से पहले arguments को कोई नहीं जाँचता। +2. हर handler का आकार एक जैसा है: `async (ctx, params) -> result`। context पहला argument है (`ctx.session`, `ctx.request_id`, `ctx.protocol_version` इसी पर रहते हैं); `server.request_context` यहीं गया। +3. पूरा `ListToolsResult` आप खुद बनाते हैं। bare list लौटाना अब server-side `TypeError` है, SDK उसे लपेटता नहीं। +4. typed params अंदर (`params.name`, `params.arguments`), पूरा result बाहर। आपके लिए कुछ भी unpack, wrap या convert नहीं किया जाता। 5. वही जाँच, अलग verb। यहाँ `ValueError` model तक एक opaque `-32603` बनकर पहुँचता (नीचे देखें), इसलिए जानबूझकर भेजा जाने वाला wire error `MCPError` के रूप में raise किया जाता है: वह अपने code और message के साथ जस का तस निकल जाता है, और unknown tool के लिए इस text के साथ `-32602` spec का अपना जवाब है। -6. `params.arguments` `None` हो सकता है; v1 इसे आपके code तक पहुँचने से पहले ही `{}` कर देता था। Handler के सामने कोई validation न होने से यह line ज़रूरी है। +6. `params.arguments` `None` हो सकता है; v1 इसे आपके code तक पहुँचने से पहले ही `{}` कर देता था। handler के सामने कोई validation न होने से इस line पर बहुत कुछ टिका है। 7. यहाँ raise हुआ कोई अनपेक्षित exception एक **sanitized** protocol error बनता है, `-32603` `"Internal server error"`: model को message कभी नहीं दिखता। ऐसे failure के लिए जिसे model पढ़े और उस पर प्रतिक्रिया दे, `CallToolResult(is_error=True, ...)` लौटाएँ। -8. Handlers constructor arguments हैं, इसलिए server बनते ही उसकी surface पूरी हो जाती है; `add_request_handler()` construction के बाद का escape hatch है, और custom methods का दरवाज़ा भी। +8. handlers constructor arguments हैं, इसलिए server बनते ही उसकी surface पूरी हो जाती है; `add_request_handler()` construction के बाद का escape hatch है, और custom methods का दरवाज़ा भी। -यह उदाहरण ही pattern है। और आम तौर पर: हर handler का आकार एक जैसा है, typed params अंदर और पूरा result type बाहर; tool arguments की पुरानी jsonschema जाँच हट गई है; exception एक protocol error है, कभी `is_error=True` tool result नहीं; और ambient `server.request_context` ContextVar हट गया है। Custom, vendor-namespaced methods `add_request_handler(method, params_type, handler)` के ज़रिए first class हैं, जो आपके handler के चलने से पहले inbound params को आपके model के सामने validate करता है। और एक `middleware` list (जानबूझकर provisional चिह्नित) हर inbound message को लपेटती है, उन private `_handle_*` methods की जगह जिन्हें लोग override किया करते थे। +यह उदाहरण ही pattern है। और आम तौर पर: हर handler का आकार एक जैसा है, typed params अंदर और पूरा result type बाहर; tool arguments की पुरानी jsonschema जाँच हट गई है; exception एक protocol error है, कभी `is_error=True` tool result नहीं; और ambient `server.request_context` ContextVar हट गया है। custom, vendor-namespaced methods `add_request_handler(method, params_type, handler)` के ज़रिए first class हैं, जो आपके handler के चलने से पहले inbound params को आपके model के सामने validate करता है। और एक `middleware` list (जानबूझकर provisional चिह्नित) हर inbound message को लपेटती है, उन private `_handle_*` methods की जगह जिन्हें लोग override किया करते थे। अंदर ही अंदर, v1 के `BaseSession` receive loop की जगह एक dispatcher engine ने ली है जिसे अब client और server दोनों साझा करते हैं, और इसी की वजह से इस page की कई बातें एक साथ सच हैं: एक ही `Server` object दोनों protocol पीढ़ियों को serve करता है, `Client(server)` बिना JSON-RPC framing के in process dispatch करता है, और timed-out client request अब वाकई server-side handler को cancel करती है। इसका page **[The low-level Server](advanced/low-level-server.md)** है; **[Migration Guide](migration.md#lowlevel-server-decorator-based-handlers-replaced-with-constructor-on_-params)** हर हटाए गए hook से गुज़रता है। अगर आप कभी `MCPServer` से नीचे नहीं उतरे, तो इनमें से कुछ भी आपको नहीं छूता। -### Wire types `mcp-types` में चले गए, और हर field snake_case है {#the-wire-types-moved-to-mcp-types-and-every-field-is-snake_case} +### wire types `mcp-types` में चले गए, और हर field snake_case है {#the-wire-types-moved-to-mcp-types-and-every-field-is-snake_case} -Protocol types अब अपने अलग distribution, `mcp-types`, में रहते हैं। यह pydantic और typing-extensions के सिवा किसी पर निर्भर नहीं है, इसलिए कोई gateway, proxy या code generator बिना HTTP stack install किए MCP के wire shapes इस्तेमाल कर सकता है: ऐसा project `mcp-types` install करता है और `mcp_types` import करता है। खुद `mcp` उस package पर exact version के साथ निर्भर है और उसे दोबारा expose करता है, इसलिए SDK पर निर्भर code पहले की तरह `import mcp.types as types` और `from mcp.types import Tool` लिखता रहता है (एक स्थायी alias, हर नाम वही object) और सिर्फ़ अपनी एक असली dependency, `mcp`, declare करता है। मोटा नियम: जिस package पर आप वाकई निर्भर हैं, उसी से import करें। +protocol types अब अपने अलग distribution, `mcp-types`, में रहते हैं। यह pydantic और typing-extensions के सिवा किसी पर निर्भर नहीं है, इसलिए कोई gateway, proxy या code generator बिना HTTP stack install किए MCP के wire shapes इस्तेमाल कर सकता है: ऐसा project `mcp-types` install करता है और `mcp_types` import करता है। खुद `mcp` उस package पर exact version के साथ निर्भर है और उसे दोबारा expose करता है, इसलिए SDK पर निर्भर code पहले की तरह `import mcp.types as types` और `from mcp.types import Tool` लिखता रहता है (एक स्थायी alias, हर नाम वही object) और सिर्फ़ अपनी एक असली dependency, `mcp`, declare करता है। मोटा नियम: जिस package पर आप वाकई निर्भर हैं, उसी से import करें। -उन types पर हर Python attribute अब snake_case है: `result.is_error`, `tool.input_schema`, `listing.next_cursor`। Wire पर जाने वाला JSON camelCase है, बिल्कुल पहले जैसा; सिर्फ़ attribute की spelling बदली है। दो और सख्त defaults साथ आते हैं: unknown fields round-trip होने के बजाय ignore किए जाते हैं (extras `_meta` में रखें), और दोनों पक्ष traffic को उस protocol version के सामने validate करते हैं जो उन्होंने negotiate किया। Rename table के लिए **[Migration Guide](migration.md#field-names-changed-from-camelcase-to-snake_case)** देखें। +उन types पर हर Python attribute अब snake_case है: `result.is_error`, `tool.input_schema`, `listing.next_cursor`। wire पर जाने वाला JSON camelCase है, बिल्कुल पहले जैसा; सिर्फ़ attribute की spelling बदली है। दो और सख्त defaults साथ आते हैं: unknown fields round-trip होने के बजाय ignore किए जाते हैं (extras `_meta` में रखें), और दोनों पक्ष traffic को उस protocol version के सामने validate करते हैं जो उन्होंने negotiate किया। rename table के लिए **[Migration Guide](migration.md#field-names-changed-from-camelcase-to-snake_case)** देखें। -### Transport configuration `run()` में चली गई {#transport-configuration-moved-to-run} +### transport configuration `run()` में चली गई {#transport-configuration-moved-to-run} -`MCPServer(...)` इस बारे में है कि आपका server **क्या है**: उसका नाम, उसके instructions, उसका lifespan, उसका auth। उसे **serve कैसे** किया जाता है, यह अब `run()` और app builders का काम है, और `host`, `port`, `stateless_http`, `json_response`, endpoint paths और `transport_security` वहीं गए (`MCPServer("x", port=9000)` अब `TypeError` है)। Overloads हर transport के लिए typed हैं, इसलिए आपका editor बताता है कि `stdio` कौन से options लेता है और `streamable-http` कौन से। एक हटाव जानने लायक है: `mount_path` हट गया है; prefix के नीचे serve करने का supported तरीका ASGI app को mount करना है। +`MCPServer(...)` इस बारे में है कि आपका server **क्या है**: उसका नाम, उसके instructions, उसका lifespan, उसका auth। उसे **serve कैसे** किया जाता है, यह अब `run()` और app builders का काम है, और `host`, `port`, `stateless_http`, `json_response`, endpoint paths और `transport_security` वहीं गए (`MCPServer("x", port=9000)` अब `TypeError` है)। overloads हर transport के लिए typed हैं, इसलिए आपका editor बताता है कि `stdio` कौन से options लेता है और `streamable-http` कौन से। एक हटाव जानने लायक है: `mount_path` हट गया है; prefix के नीचे serve करने का supported तरीका ASGI app को mount करना है। -Options के लिए **[अपना server चलाना](run/index.md)** देखें; mounting के लिए **[मौजूदा app में जोड़ना](run/asgi.md)**। +options के लिए **[अपना server चलाना](run/index.md)** देखें; mounting के लिए **[मौजूदा app में जोड़ना](run/asgi.md)**। ### बिना import error के बदलने वाला व्यवहार {#behavior-that-changes-without-an-import-error} -Renames खुद अपनी घोषणा करते हैं। ये नहीं करते: +renames खुद अपनी घोषणा करते हैं। ये नहीं करते: -* **Sync functions worker thread पर चलते हैं।** `def` tool (या resource, prompt, या resolver) अब event loop को block नहीं करता; बदले में उसकी body अब event-loop thread **पर** नहीं चलती, जो thread-affine code के लिए मायने रखता है। `async def` handlers अछूते हैं। **[Migration Guide](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**। -* **Tool के अंदर raise हुआ `MCPError` (v1 का `McpError`) अब protocol error है।** Model उसे कभी नहीं देखता। बाकी हर exception अब भी `is_error=True` result बनता है जिसे model पढ़ सकता है और उस पर प्रतिक्रिया दे सकता है। यह विभाजन **[Errors संभालना](servers/handling-errors.md)** में है। -* **Results निकलने से पहले validate होते हैं।** हाथ से बना `Tool` जिसका `input_schema` `{}` है, अब `tools/list` में fail होता है (spec को `"type": "object"` चाहिए)। `@mcp.tool()` पर बने servers को यह कभी नहीं दिखता; उनके schemas SDK लिखता है। +* **sync functions worker thread पर चलते हैं।** `def` tool (या resource, prompt, या resolver) अब event loop को block नहीं करता; बदले में उसकी body अब event-loop thread **पर** नहीं चलती, जो thread-affine code के लिए मायने रखता है। `async def` handlers अछूते हैं। **[Migration Guide](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**। +* **tool के अंदर raise हुआ `MCPError` (v1 का `McpError`) अब protocol error है।** model उसे कभी नहीं देखता। बाकी हर exception अब भी `is_error=True` result बनता है, लेकिन model तक सिर्फ़ `ToolError` का message पहुँचता है: कोई भी दूसरा exception अब `Error executing tool ` पढ़ा जाता है, और traceback आपके server log में जाता है। यह विभाजन **[errors संभालना](servers/handling-errors.md)** में है। +* **results निकलने से पहले validate होते हैं।** हाथ से बना `Tool` जिसका `input_schema` `{}` है, अब `tools/list` में fail होता है (spec को `"type": "object"` चाहिए)। `@mcp.tool()` पर बने servers को यह कभी नहीं दिखता; उनके schemas SDK लिखता है। * **आपका client जो पाता है उसे validate करता है।** `list_tools()` और `call_tool()` server के जवाब को negotiated protocol version के सामने जाँचते हैं, इसलिए पूरी तरह valid न रहने वाला server, जिसे v1 का ढीला parse सह लेता था, अब `pydantic.ValidationError` raise करता है। अगर आप ऐसे servers से connect करते हैं जो आपके नियंत्रण में नहीं हैं, तो मानकर चलें कि उन्हें खोजने वाले आप ही होंगे; ब्योरा **[Migration Guide](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)** में है। * **URI templates अब असली RFC 6570 हैं।** `{+path}`, `{?query}` वगैरह काम करते हैं, matching regex-loose के बजाय exact है, और निकाली गई values में path traversal default रूप से reject होता है। ज़्यादा सख्त templates decoration के समय fail होते हैं, पहली request पर नहीं। **[URI templates](servers/uri-templates.md)**। -* **Streamable HTTP lifespan एक बार चलता है**, startup पर, और उसका state हर session और request के बीच साझा होता है। v1 में यह हर session पर एक बार चलता था, और `stateless_http=True` के तहत हर request पर एक बार। Lifespan में बने pools और caches बहुत सस्ते हो जाते हैं; जो कुछ वहाँ per-connection resource लेता था, वह अब handler body में होना चाहिए। **[Lifespan](handlers/lifespan.md)**। +* **Streamable HTTP lifespan एक बार चलता है**, startup पर, और उसका state हर session और request के बीच साझा होता है। v1 में यह हर session पर एक बार चलता था, और `stateless_http=True` के तहत हर request पर एक बार। lifespan में बने pools और caches बहुत सस्ते हो जाते हैं; जो कुछ वहाँ per-connection resource लेता था, वह अब handler body में होना चाहिए। **[Lifespan](handlers/lifespan.md)**। * **`mcp dev` और `mcp install` जो environment spawn करते हैं उसे** आपके installed SDK version पर pin करते हैं। दोनों commands आपके server को नए `uv run --with ...` environment में चलाते हैं, जो पहले `mcp` को उस version के बजाय newest stable release पर resolve करता था जिसके सामने आप develop कर रहे हैं। **[Migration Guide](migration.md#mcp-dev-and-mcp-install-pin-the-spawned-environment-to-your-sdk-version)**। -* **HTTP client अब `httpx` नहीं, `httpx2` है।** Dependency बदलने से यह बदलता है कि आपका code क्या catch करता और pass करता है (`httpx2.AsyncClient`, `httpx2.ConnectError`), और यह भी कि TLS certificates कैसे verify होते हैं: `httpx2` certifi की bundled CA list के बजाय `truststore` के ज़रिए operating system trust store के सामने validate करता है। ज़्यादातर environments को पता भी नहीं चलता; बिना system CA store वाला minimal container, या ऐसा private CA जिसे सिर्फ़ certifi का bundle जानता था, TLS handshake fail करने लगता है। `SSL_CERT_FILE`/`SSL_CERT_DIR` set करें या अपने client को `verify=ssl_context` pass करें। **[Migration Guide](migration.md#httpx-and-httpx-sse-replaced-by-httpx2)**। +* **HTTP client अब `httpx` नहीं, `httpx2` है।** dependency बदलने से यह बदलता है कि आपका code क्या catch करता और pass करता है (`httpx2.AsyncClient`, `httpx2.ConnectError`), और यह भी कि TLS certificates कैसे verify होते हैं: `httpx2` certifi की bundled CA list के बजाय `truststore` के ज़रिए operating system trust store के सामने validate करता है। ज़्यादातर environments को पता भी नहीं चलता; बिना system CA store वाला minimal container, या ऐसा private CA जिसे सिर्फ़ certifi का bundle जानता था, TLS handshake fail करने लगता है। `SSL_CERT_FILE`/`SSL_CERT_DIR` set करें या अपने client को `verify=ssl_context` pass करें। **[Migration Guide](migration.md#httpx-and-httpx-sse-replaced-by-httpx2)**। ### पूरी तरह हटाए गए {#removed-outright} इनमें से हर एक **[Migration Guide](migration.md)** में एक section है: * **WebSocket transport**, दोनों तरफ़, और `mcp[ws]` extra। यह कभी MCP specification का हिस्सा नहीं था। -* **Experimental Tasks** API (`mcp.*.experimental`)। 2026-07-28 tasks को core protocol से निकालकर एक official extension में ले जाता है ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)), जिसे यह SDK अभी implement नहीं करता। -* Import paths के रूप में `mcp.shared.version`, `mcp.shared.progress` और `mcp.shared.session` (उस `RequestResponder` stub के साथ जिसे v1 के `message_handler` annotations import करते थे)। (`mcp.types` हटाया **नहीं** गया है: यह standalone `mcp_types` package के स्थायी alias के रूप में बना रहता है।) -* Deprecated `streamablehttp_client` spelling, और `streamable_http_client` से `get_session_id` callback (जो अब ठीक दो streams देता है)। +* **experimental Tasks** API (`mcp.*.experimental`)। 2026-07-28 tasks को core protocol से निकालकर एक official extension में ले जाता है ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)), जिसे यह SDK अभी implement नहीं करता। +* import paths के रूप में `mcp.shared.version`, `mcp.shared.progress` और `mcp.shared.session` (उस `RequestResponder` stub के साथ जिसे v1 के `message_handler` annotations import करते थे)। (`mcp.types` हटाया **नहीं** गया है: यह standalone `mcp_types` package के स्थायी alias के रूप में बना रहता है।) +* deprecated `streamablehttp_client` spelling, और `streamable_http_client` से `get_session_id` callback (जो अब ठीक दो streams देता है)। * `McpError`, जिसका नाम बदलकर **`MCPError`** हुआ, सीधे `(code, message, data)` constructor के साथ। * `MCPServer.get_context()`, `mount_path=`, और lowlevel `Server` के decorator methods, ContextVar और handler dicts। diff --git a/i18n/ja/pages/advanced/low-level-server.md b/i18n/ja/pages/advanced/low-level-server.md index 1b6be6fcbd..c940ae829f 100644 --- a/i18n/ja/pages/advanced/low-level-server.md +++ b/i18n/ja/pages/advanced/low-level-server.md @@ -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 --- # 低レベルの Server {#the-low-level-server} @@ -116,6 +116,17 @@ asyncio.run(main()) サーバーは 2 つのフィールドを比較しません。この SDK の `Client` は比較します。宣言した `output_schema` を満たさない `structured_content` を返すと、`call_tool` は `Invalid structured content returned by tool search_books` で始まり、続けて `jsonschema` の失敗内容を引用する `RuntimeError` を送出します。スキーマを約束するのは簡単ですが、守るのは自分の仕事です。戻り値の型とスキーマの全段階については **[構造化出力](../servers/structured-output.md)** を参照してください。 +## ダイアレクトは JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12} + +`input_schema` と `output_schema` は JSON Schema であり、[MCP 仕様](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) がそのダイアレクトを定めています。`$schema` キーのないスキーマは **JSON Schema 2020-12** です。`MCPServer` が生成するスキーマはこのデフォルトに依存しています(Pydantic は 2020-12 を書き出し、キーを省略します)。手書きの dict にも同じ基準が適用されるので、2020-12 の語彙がすべて使えます。 + +```python title="server.py" hl_lines="8 14-15" +--8<-- "docs_src/lowlevel/tutorial007.py" +``` + +* `input_schema` のルートは `"type": "object"` でなければなりません。その横に置く `oneOf`、`additionalProperties`、`anyOf`、`if`/`then`/`else`、`prefixItems`、ローカルな `$ref` を伴う `$defs`、そのほかの 2020-12 キーワードは、書いたとおりにクライアントに届きます。 +* `$schema` キーは不要です。古いドラフトにオプトインする場合にだけ追加してください。この SDK の `Client` はツールの `output_schema` に照らして `structured_content` を検証しますが、バリデーターは `$schema` から選び、キーがなければ 2020-12 を使います。 + ## `_meta`:モデルではなくアプリケーションのために {#\_meta-for-the-application-not-the-model} `content` は答えのうちモデルが読む部分です。`structured_content` は同じ答えを型付きデータにしたものです。`_meta` は 3 つ目のチャネルで、答えの一部ではまったくなく、**クライアントアプリケーション**のために結果に同乗するデータです。 @@ -166,7 +177,7 @@ asyncio.run(main()) --8<-- "docs_src/lowlevel/tutorial006.py" ``` -* 最初の引数はメソッド文字列です。通知には対になる `add_notification_handler` があります。 +* 最初の引数はメソッド文字列です。通知には対になる `add_notification_handler` があります。そのハンドラーは stdio と、ハンドシェイク世代の HTTP 接続で発火します。`2026-07-28` の Streamable HTTP 経路では、クライアントの通知 POST は `202` で受理されるだけでディスパッチされません。このリビジョンは HTTP 上でのクライアントからサーバーへの通知を定義していないからです。 * `params_type` は、受信した `params` をハンドラーの実行**前**に検証するためのモデルです。つまり、カスタムメソッドはツールが受けられない検証を受けられます。`_meta` フィールドがほかのメソッドと同じようにパースされるよう、`RequestParams` をサブクラス化してください。 * ハンドラーは `BaseModel`、`dict`、または `None` を返します。SDK がそれを JSON-RPC の結果にシリアライズします。 diff --git a/i18n/ja/pages/advanced/middleware.md b/i18n/ja/pages/advanced/middleware.md index 40af1070b8..c7a8c6226b 100644 --- a/i18n/ja/pages/advanced/middleware.md +++ b/i18n/ja/pages/advanced/middleware.md @@ -1,6 +1,6 @@ --- translation: - sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c] + sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43] tool: 1 --- # ミドルウェア {#middleware} @@ -42,7 +42,7 @@ tools/call took 0.1 ms ここがポイントです。ミドルウェアは受信する**すべての**メッセージを包みます。 * 接続のセットアップ。`server/discover`、あるいはレガシーセッションでは `initialize` と `notifications/initialized` です。 -* すべてのリクエストとすべての通知。通知の場合は `ctx.request_id is None` であり、`call_next(ctx)` は `None` を返し、何を返しても破棄されます。 +* サーバーに届くすべてのリクエストとすべての通知。通知の場合は `ctx.request_id is None` であり、`call_next(ctx)` は `None` を返し、何を返しても破棄されます。(`2026-07-28` の Streamable HTTP 経路では、クライアントの通知 POST はトランスポートで `202` として受領されるだけでディスパッチされないため、ミドルウェアにも届きません。このリビジョンは HTTP 上でのクライアントからサーバーへの通知を定義していません。) * サーバーにハンドラーがないメソッドでさえ対象です。`call_next` は `MCPError(-32601, "Method not found")` を送出し、それがミドルウェアを「通り抜けて」クライアントへ向かいます。 ## ミドルウェアの中でできること {#what-you-can-do-inside-one} @@ -75,7 +75,7 @@ SDK が同梱するミドルウェアはちょうど 1 つで、すでにサー ## まとめ {#recap} * ミドルウェアは `async (ctx, call_next) -> result` です。`MCPServer(middleware=[...])` として渡すか(または `mcp.middleware` に追加し)、低レベルの `Server` では `server.middleware` に追加します。 -* 受信する**すべての**メッセージ(`server/discover`、`initialize`、リクエスト、通知、未知のメソッド)を包み、外側から順に実行されます。 +* サーバーに届く**すべての**受信メッセージ(`server/discover`、`initialize`、リクエスト、通知、未知のメソッド)を包み、外側から順に実行されます。 * `ctx.request_id is None` で、通知とリクエストを見分けます。 * `call_next` を呼ぶ代わりに例外を送出すると、メッセージを 1 つ拒否できます。接続は維持されます。 * SDK 自身の OpenTelemetry トレースもミドルウェアであり、すでにリストに載っています。**[OpenTelemetry](../run/opentelemetry.md)** を参照してください。 diff --git a/i18n/ja/pages/client/index.md b/i18n/ja/pages/client/index.md index e9a9f266b1..7ca4241626 100644 --- a/i18n/ja/pages/client/index.md +++ b/i18n/ja/pages/client/index.md @@ -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 --- # Client {#the-client} @@ -27,9 +27,10 @@ translation: * `MCPServer`(または低レベルの `Server`)のインスタンス:**プロセス内**で接続します。 * URL 文字列(`Client("http://localhost:8000/mcp")`):Streamable HTTP。本番向けの経路です。 -* **トランスポート**:`async with ... as (read, write)` できるものなら何でも。たとえばサブプロセスをラップする `stdio_client(...)` です。 +* `StdioServerParameters`:**サブプロセス**として起動するコマンドで、その stdin と stdout を通じて対話します。 +* **トランスポート**:`async with ... as (read, write)` できるものなら何でも。たとえば、自分の HTTP クライアントをラップする `streamable_http_client(url, http_client=...)` です。 -このページの残りの内容は、3 つのどれでも同じです。ヘッダー、サブプロセス、タイムアウト、そして `Transport` プロトコルについては、専用のページ **[クライアントのトランスポート](transports.md)** があります。 +このページの残りの内容は、4 つのどれでも同じです。ヘッダー、サブプロセス、タイムアウト、そして `Transport` プロトコルについては、専用のページ **[クライアントのトランスポート](transports.md)** があります。 ### 接続済みクライアントが持つもの {#whats-on-a-connected-client} @@ -82,7 +83,7 @@ tool.description # 'Search the catalog by title or author.' `call_tool(name, arguments)` はツールを実行し、`CallToolResult` を返します。 -```python title="client.py" hl_lines="26-33" +```python title="client.py" hl_lines="27-34" --8<-- "docs_src/client/tutorial003.py" ``` @@ -113,7 +114,7 @@ result.is_error # False 例外を送出するツールが、クライアント側で例外を送出することは**ありません**。`is_error=True` の付いた通常の結果として返ってきます。 !!! check - `lookup_book` に `"Solaris"`(カタログにない書名)を問い合わせると、関数は `ValueError` を送出します。それでも呼び出しは正常に返ります。 + `lookup_book` に `"Solaris"`(カタログにない書名)を問い合わせると、関数は `ToolError` を送出します。それでも呼び出しは正常に返ります。 ```python result.is_error # True @@ -121,7 +122,7 @@ result.is_error # False result.structured_content # None ``` - 例外のメッセージは `content` に入りました。そこなら**モデル**が読んで、やり直せます。これは意図的なものです。ツールのエラーはクラッシュではなく、会話の一部です。`structured_content` を信用する前に、必ず `is_error` を確認してください。 + `ToolError` のメッセージは `content` に入りました。そこなら**モデル**が読んで、やり直せます。これは意図的なものです。ツールのエラーはクラッシュではなく、会話の一部です。(仮にツールが別の例外でクラッシュしていたら、`content` には `Error executing tool lookup_book` とだけ入ります。)`structured_content` を信用する前に、必ず `is_error` を確認してください。 !!! warning `is_error=True` がカバーするのは、自分で書いた `raise` だけではありません。サーバーに存在すらしないツールを要求しても(`call_tool("does_not_exist", {})`)、何も送出されません。同じ形の結果が返り、`is_error=True` で `content` には `Unknown tool: does_not_exist` が入ります。`Client` のメソッドが `MCPError` を送出するのは、サーバーが結果ではなく JSON-RPC の**エラー**で応答したときだけです。サーバーがどんなときにどちらを返すかは **[エラーの処理](../servers/handling-errors.md)** で扱っています。 diff --git a/i18n/ja/pages/client/transports.md b/i18n/ja/pages/client/transports.md index 7ac7c611ea..4c61766a1c 100644 --- a/i18n/ja/pages/client/transports.md +++ b/i18n/ja/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # クライアントのトランスポート {#client-transports} @@ -74,17 +74,17 @@ TLS について 1 点。`httpx2` は、同梱の CA リストではなく、オ ## stdio {#stdio} -**stdio** サーバーはサブプロセスです。クライアントがそれを起動し、stdin に JSON-RPC を書き込み、stdout から JSON-RPC を読み取ります。デスクトップのホストが手元のマシンでサーバーを動かす方法がこれです。ホストとは、このコードに UI を加えたもの「そのもの」です。**[本物のホストに接続する](../get-started/real-host.md)** は、同じ関係をホストの側から設定ファイルとして見たものです。 +**stdio** サーバーはサブプロセスです。クライアントがそれを起動し、stdin に JSON-RPC を書き込み、stdout から JSON-RPC を読み取ります。デスクトップのホストが手元のマシンでサーバーを動かす方法がこれです。ホストとは、まさにこのコードに UI を加えたものです。**[本物のホストに接続する](../get-started/real-host.md)** は、同じ関係をホストの側から設定ファイルとして見たものです。 -`StdioServerParameters` でプロセスを記述し、`stdio_client` でトランスポートに変換して、「それ」を `Client` に渡します。 +`StdioServerParameters` でプロセスを記述し、それを `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` はパラメーターオブジェクトをそのままでは受け取りません。`StdioServerParameters` は設定であり、`stdio_client(server)` はそこからプロセスを起動する方法を知っているトランスポートです。必ず包んでください。 +ブロックに入るとプロセスが起動します。抜けるとサブプロセスは終了されます。stdin を閉じ、待機し、居残っていれば強制終了します。自分で後始末をすることはありません。 -`async with` ブロックを抜けると、サブプロセスも終了されます。stdin を閉じ、待機し、居残っていれば強制終了します。自分で後始末をすることはありません。 +子プロセスの stderr は自分の stderr に流れます。別の場所に送るには、`stdio_client`(`mcp` にあります)でトランスポートを自分で組み立て、代わりにそれを渡してください。`Client(stdio_client(server, errlog=log_file))` のように書きます。 !!! warning 子プロセスは環境変数を継承**しません**。最小限の許可リスト(POSIX では `HOME`、`LOGNAME`、`PATH`、`SHELL`、`TERM`、`USER`)だけを受け取るので、自分が書いたとは限らないプロセスに機密情報が漏れることはありません。 @@ -99,16 +99,16 @@ TLS について 1 点。`httpx2` は、同梱の CA リストではなく、オ `Client` から見れば、上記はすべて同じものです。 -**トランスポート**とは、`(read, write)` というメッセージストリームのペアを yield する非同期コンテキストマネージャーのことです。正式には `mcp.client` の `Transport` プロトコルです。`Client` は引数を型で解決します。サーバーオブジェクトならインプロセスで接続し、`str` なら `streamable_http_client(url)` になり、それ以外は直接トランスポートとして入ります。この最後の規則があるからこそ、`stdio_client(...)`、`streamable_http_client(...)`、`sse_client(...)` はすべて同じ場所に収まり、自分で独自のものを書くこともできます。 +**トランスポート**とは、`(read, write)` というメッセージストリームのペアを yield する非同期コンテキストマネージャーのことです。正式には `mcp.client` の `Transport` プロトコルです。`Client` は引数を型で解決します。サーバーオブジェクトならインプロセスで接続し、`str` なら `streamable_http_client(url)` になり、`StdioServerParameters` なら `stdio_client(params)` になり、それ以外は直接トランスポートとして入ります。この最後の規則があるからこそ、`stdio_client(...)`、`streamable_http_client(...)`、`sse_client(...)` はすべて同じ場所に収まり、自分で独自のものを書くこともできます。 ## まとめ {#recap} * `Client(mcp)`(サーバーオブジェクト)はインメモリで接続します。テストと組み込みに使ってください。 * `Client("http://.../mcp")`(URL)は、本番用のトランスポートである Streamable HTTP で接続します。 * ヘッダー、認証、プロキシ、タイムアウトは、`streamable_http_client(url, http_client=...)` に渡す `httpx2.AsyncClient` に設定します。`headers=` キーワードはありません。 -* stdio は `Client(stdio_client(StdioServerParameters(...)))` であり、パラメーターオブジェクト単体では決してありません。 +* stdio は `Client(StdioServerParameters(...))` です。自分で `stdio_client(...)` に包むのは、子プロセスの stderr をリダイレクトしたいときだけです。 * サブプロセスが受け取るのは自分の環境ではなく、許可リストに基づく環境です。`env=` でそこに追加します。 -* トランスポートとは、`async with x as (read, write)` と書けるものすべてです。`Client` は、サーバーオブジェクトでも URL でもないものをそのままこのプロトコルに渡します。 +* トランスポートとは、`async with x as (read, write)` と書けるものすべてです。`Client` は、サーバーオブジェクトでも URL でも `StdioServerParameters` でもないものを、そのままこのプロトコルに渡します。 * `Client` の構築でトランスポートが選ばれ、`async with` でそれが開かれます。 トランスポートが開いたら、両者はプロトコルバージョンについて合意する必要があります。普段は意識することはありません。意識することになったら、**[プロトコルバージョン](../protocol-versions.md)** のページを参照してください。 diff --git a/i18n/ja/pages/deprecated.md b/i18n/ja/pages/deprecated.md index 9b251f69f0..8754ecc85d 100644 --- a/i18n/ja/pages/deprecated.md +++ b/i18n/ja/pages/deprecated.md @@ -1,11 +1,11 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # 非推奨の機能 {#deprecated-features} -2026-07-28 の仕様では、5 つのものが役目を終えます。SDK は今もその 5 つすべてを実装しており、そのすべてに**非推奨の警告**が付くようになりました。 +2026-07-28 の仕様では、5 つのものが役目を終えます。SDK は今もその 5 つすべてを実装しており、そのすべてに**非推奨の警告**が付くようになりました。SDK のヘルパーが 1 つ、仕様とは別の理由で非推奨になっており、[ページの最後](#deprecated-sdk-helpers)に挙げています。 下の表は、非推奨になった機能それぞれについて、なくなる理由と、代わりに土台にすべきものを挙げています。 @@ -49,6 +49,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE シグナルは 2 つ、この順番です。`MCPDeprecationWarning` は、どの接続でもメソッドを呼び出した瞬間に発生します。エラーは、そのあと SDK が送信を試みたときに返ってくるものです。この 2 つの機能がエンドツーエンドで動作するのは、対応するコールバックをクライアントが登録した `mode="legacy"` の接続だけです。 +## レガシーセッションでの `ping` {#ping-on-a-legacy-session} + +**ping** は、相手がまだ応答しているかを確かめるために、どちらの側からでも送れる空のリクエストです。2026-07-28 の仕様はこれを削除します([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575))。現行仕様のクライアントが送るリクエストはどれも、それ自体がサーバーの存在を証明していますし、現行仕様のサーバーには ping を送るチャネルがありません。SDK の 2 つのメソッドはどちらも、ハンドシェイク世代のセッションでは引き続き動作します。クライアント側からは次のように書きます。 + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +サーバー側からは、任意のハンドラーの中で次のように書きます。 + +```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()` は呼び出しのたびに `MCPDeprecationWarning` で警告します。デフォルト(`2026-07-28`)の接続では、サーバーは代わりに `MCPError: Method not found` と応答します。 +* `ctx.session.send_ping()` には警告がありません。現行仕様の接続では、ほかのサーバー起点のリクエストと同じく、バックチャネル(back-channel)がないというエラーを送出します。 +* どちらの側も、ping に応答するために何かを登録する必要はありません。 + +## ルートの変更通知 {#roots-change-notifications} + +ルートのケイパビリティを宣言した 2025 年世代のクライアントは、`notifications/roots/list_changed` を送ることで、ワークスペースのフォルダーが変わったことをサーバーに伝えられます。サーバーはそれを受けて `roots/list` をもう一度リクエストします。2026-07-28 の仕様は、プッシュ型のルートのフローの残りとともに、この通知を削除します。クライアント側では、`list_roots_callback=` を渡すこと(**[クライアントのコールバック](client/callbacks.md)**)が `"roots": {"listChanged": true}` の宣言にあたり、1 回の呼び出しでその約束を果たします。 + +```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() +``` + +サーバー側では、低レベルの `Server` が受信側のハンドラーを受け取ります。 + +```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` は `list_roots_callback` が返すリストです。`client.send_roots_list_changed()` は警告を出し、`mode="legacy"` のクライアントが必要です。現行仕様の接続では、通知は黙って捨てられます。サーバーからの後続の `roots/list` は同じセッションに届くので、呼び出したあともセッションは開いたままにしてください。 +* `MCPServer` にはこの通知のフックがありません。低レベルの `Server` では `on_roots_list_changed=` がハンドラーを登録します(これも非推奨で、構築時に警告を出します)。通知はペイロードを運ばないので、ハンドラーは `ctx.session.list_roots()` を呼んで新しいリストを取得します。 + ## 警告を抑止する {#silencing-the-warning} 新しいコードでは、しないでください。 @@ -66,21 +115,30 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning) API はこれだけです。メソッドごとのスイッチはありませんし、必要もありません。カテゴリが 1 つである利点は、1 行で黙らせ、1 行で元に戻せることです。 !!! check - フィルターを逆向きにかければ、無料で回帰テストが手に入ります。pytest の設定の `filterwarnings` に `"error::mcp.MCPDeprecationWarning"` を追加すると、非推奨の呼び出しは警告ではなく**例外を送出**します。まだ `ctx.info()` を呼んでいる `old_log` という名前のツールは通らなくなり、次のように報告し始めます。 + フィルターを逆向きにかければ、無料で回帰テストが手に入ります。pytest の設定の `filterwarnings` に `"error::mcp.MCPDeprecationWarning"` を追加すると、非推奨の呼び出しは警告ではなく**例外を送出**します。まだ `ctx.info()` を呼んでいる `old_log` という名前のツールは通らなくなります。呼び出しは `is_error=True` と `Error executing tool old_log` を伴って返り、キャプチャされたサーバーのログが原因を名指しします。 ```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). ``` pytest の設定を 1 行足すだけで、非推奨の呼び出しがテストを失敗させずにコードベースへ紛れ込むことは二度とありません。 +## 非推奨の SDK ヘルパー {#deprecated-sdk-helpers} + +これらは仕様の変更ではなく、よりよい代替がある SDK の内部実装にすぎません。同じ `MCPDeprecationWarning` で警告し、3.0 で削除されます。 + +| 非推奨 | 代わりにすること | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()` を呼んでから `FuncMetadata.call_fn()` を呼びます。これを呼んでいたのは、`FuncMetadata` を直接扱うコード(たとえば独自の `Tool` サブクラス)だけです。 | + ## まとめ {#recap} * 2026-07-28 の仕様は、**ルート**、サーバー起点の**サンプリング**、プロトコルの**ロギング**を非推奨にし(いずれも [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577))、**進捗**をサーバーからクライアントへの方向に限定し、**`ping`** を削除します。 * 「代わりにすること」の列が次の行き先を示しています。サンプリングとルートには **[マルチラウンドトリップリクエスト](handlers/multi-round-trip.md)**、ロギングには **[ロギング](handlers/logging.md)**、進捗には **[進捗](handlers/progress.md)** です。`ping` には何も必要ありません。 * 非推奨は勧告にすぎません。通信上の変更はなく、2026 年より前のセッションに対してはすべてが引き続き動作します。そして目に見える `MCPDeprecationWarning` が出ます(`UserWarning` なので、デフォルトで有効です)。 -* サンプリングとルートにはさらに、2026-07-28 のセッションにはないバックチャネル(back-channel)が必要です。現行仕様の接続では警告を出し、そのあと例外を送出します。 +* サンプリングとルートにはさらに、2026-07-28 のセッションにはないバックチャネルが必要です。現行仕様の接続では警告を出し、そのあと例外を送出します。 * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` でカテゴリ全体を黙らせます。pytest で `"error::mcp.MCPDeprecationWarning"` を指定すれば、テストの失敗に変わります。 +* SDK のヘルパー `FuncMetadata.call_fn_with_arg_validation()` は、これとは別に非推奨になっており、3.0 で削除されます。 * 新しいコードは、これらのどれの上にも築くべきではありません。 このドキュメントのほかのページはすべて、現行の API を扱っています。 diff --git a/i18n/ja/pages/get-started/real-host.md b/i18n/ja/pages/get-started/real-host.md index e594ee6cb0..c04476db5f 100644 --- a/i18n/ja/pages/get-started/real-host.md +++ b/i18n/ja/pages/get-started/real-host.md @@ -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 --- # 実際のホストに接続する {#connect-to-a-real-host} @@ -11,7 +11,7 @@ translation: ## 1 つのサーバー、すべてのホスト {#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" ``` @@ -39,9 +39,9 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py ホストは最小限の `PATH` でサーバーを起動するため、そこに `uv` が入っていないことがあります。`uv` とだけ書いた部分を、`which uv`(macOS/Linux)または `where uv`(Windows)で得られる絶対パスに置き換えてください。`mcp install` が書き込むのもまさにこの形です。 !!! note "このページはローカルの話" - ここで扱うものはすべて、ホストと同じマシン上でサーバーを動かします。ホストがファイルを stdio 経由で起動する形です。個人用のツールや 1 台のマシンで使うツールなら、まさにこれが正解です。ファイルを持って**いない**人たちにサーバーを渡すには、コマンドではなく **URL** を配ります。つまり、同じ `mcp` オブジェクトを Streamable HTTP で提供します。**[サーバーの実行](../run/index.md)** はその判断を 1 つの表にまとめており、**[デプロイとスケール](../run/deploy.md)** はそこから実際のホスト名に至るまでの道のりです。 + ここで扱うものはすべて、ホストと同じマシン上でサーバーを動かします。ホストがファイルを stdio 経由で起動する形です。個人用のツールや 1 台のマシンで使うツールなら、まさにこれが正解です。ファイルを持っていない人たちにサーバーを渡すには、コマンドではなく **URL** を配ります。つまり、同じ `mcp` オブジェクトを Streamable HTTP で提供します。**[サーバーの実行](../run/index.md)** はその判断を 1 つの表にまとめており、**[デプロイとスケール](../run/deploy.md)** はそこから実際のホスト名に至るまでの道のりです。 - また、ホストとは内部に MCP クライアントを持つアプリケーションにすぎないので、自分の Python コードがホストの役を演じることもできます。**[クライアントのトランスポート](../client/transports.md)** ではこの同じファイルを `stdio_client(...)` でサブプロセスとして起動し、**[テスト](testing.md)** ではプロセスを一切使わずにメモリ内で接続します。 + また、ホストとは内部に MCP クライアントを持つアプリケーションにすぎないので、自分の Python コードがホストの役を演じることもできます。**[クライアントのトランスポート](../client/transports.md)** ではこの同じファイルを `Client(StdioServerParameters(...))` でサブプロセスとして起動し、**[テスト](testing.md)** ではプロセスを一切使わずにメモリ内で接続します。 ## Claude Desktop {#claude-desktop} diff --git a/i18n/ja/pages/get-started/testing.md b/i18n/ja/pages/get-started/testing.md index f510f983eb..2636eec939 100644 --- a/i18n/ja/pages/get-started/testing.md +++ b/i18n/ja/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # テスト {#testing} @@ -80,7 +80,7 @@ async def test_call_add_tool(client: Client): 問題が起こりうる場所は 2 種類あり、このフラグが関わるのはそのうちの一方だけです。 -**ツール**の内部で発生した例外は、プロトコル上の失敗ではありません。`is_error=True` の付いた通常の結果になり、モデルがそのメッセージを読みます。`raise_exceptions` はこの挙動を変えません。指定してもしなくても、`call_tool` は同じ `is_error=True` の結果を返します。これについては専用のページがあります。**[エラーの処理](../servers/handling-errors.md)** を参照してください。 +**ツール**の内部で発生した例外は、プロトコル上の失敗ではありません。`is_error=True` の付いた通常の結果になります(`ToolError` だった場合は、モデルがそのメッセージを読みます)。`raise_exceptions` はこの挙動を変えません。指定してもしなくても、`call_tool` は同じ `is_error=True` の結果を返します。これについては専用のページがあります。**[エラーの処理](../servers/handling-errors.md)** を参照してください。 ツール本体の**外側**で起きた失敗は事情が異なります。`Client(mcp)` で得られる接続では、クライアントの目に触れる前に、サーバーがその失敗を汎用の `"Internal server error"` にサニタイズします。予期しないクラッシュの詳細は、リモートの呼び出し側に決して漏らしてはいけないからです。しかしテストでは、これはまさに望まない挙動です。そして `raise_exceptions=True` が変えるのはまさにこの点で、テストからはサニタイズ後のメッセージではなく本来のメッセージが見えるようになります。 diff --git a/i18n/ja/pages/handlers/elicitation.md b/i18n/ja/pages/handlers/elicitation.md index 367225d3a5..aee358b0fc 100644 --- a/i18n/ja/pages/handlers/elicitation.md +++ b/i18n/ja/pages/handlers/elicitation.md @@ -1,6 +1,6 @@ --- translation: - sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] + sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] tool: 1 --- # エリシテーション {#elicitation} @@ -83,7 +83,7 @@ translation: このスキーマがフォームです。`Field(description=...)` がラベルになり、デフォルト値は入力欄にあらかじめ入って、そのフィールドを省略可能にします。これは、**[ツール](../servers/tools.md)** のページがツールの引数について説明しているのと同じ、Pydantic から JSON Schema への変換の仕組みです。 !!! warning - エリシテーションのスキーマは、ツールの入力スキーマほど表現力がありません。フラットなプリミティブ型のフィールドだけです。`str`、`int`、`float`、`bool`、または文字列の `Literal`(`enum` になります)。モデルの中にモデルを入れると、クライアントに何かを送る前に `ctx.elicit` が例外を送出します。 + エリシテーションのスキーマは、ツールの入力スキーマほど表現力がありません。フラットなプリミティブ型のフィールドだけです。`str`、`int`、`float`、`bool`、または文字列の `Literal`(`enum` になります)。モデルの中にモデルを入れると、クライアントに何かを送る前に `ctx.elicit` が例外を送出します。ツール呼び出しは `Error executing tool ` で失敗し、サーバーログにはその理由が残ります。 ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -104,7 +104,7 @@ translation: 断られてもエラーではありません。断られたことが何を意味するか(ここでは、予約しないこと)はツールが決め、モデルには普通に答えます。 !!! tip - 答えは、コードに届く前にモデルに照らして検証されます。`bool` に `"maybe"` を送ってくるクライアントがいても、予約が壊れることはありません。呼び出しはスキーマ不一致のエラーで失敗し、`if` は実行されません。 + 答えは、コードに届く前にモデルに照らして検証されます。`bool` に `"maybe"` を送ってくるクライアントがいても、予約が壊れることはありません。`ctx.elicit` が `ValueError` を送出し、呼び出しは失敗し、`if` は実行されません。 ## ユーザーを URL へ誘導する {#send-the-user-to-a-url} diff --git a/i18n/ja/pages/handlers/logging.md b/i18n/ja/pages/handlers/logging.md index 825d3ebc0c..d7dd71c0fe 100644 --- a/i18n/ja/pages/handlers/logging.md +++ b/i18n/ja/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # ロギング {#logging} @@ -49,6 +49,8 @@ MCP にはプロトコルレベルの**ロギングのケイパビリティ**が `logging.basicConfig()` は、すでに存在するハンドラーを置き換えることはありません。サーバーを作成する前に自分でロギングを設定していれば、その設定が優先されます。 +失敗を記録するためだけに、すべてのハンドラーに `try`/`except` を書く必要もありません。ツールやリソースの関数が例外を送出すると、SDK が代わりにログを出力します。何がどのレベルで記録されるかは、**[エラーの処理](../servers/handling-errors.md#any-other-exception)** で説明しています。 + ## 試してみる {#try-it} MCP Inspector でサーバーを実行してください。 diff --git a/i18n/ja/pages/run/index.md b/i18n/ja/pages/run/index.md index 5a407e1121..feaac858bc 100644 --- a/i18n/ja/pages/run/index.md +++ b/i18n/ja/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # サーバーの実行 {#running-your-server} @@ -71,7 +71,7 @@ Inspector は本物のホストとまったく同じことをします。`server * `streamable_http_path`:MCP エンドポイントの場所です。デフォルトは `/mcp` です。 * `json_response=True`:各 POST に SSE ストリームではなく単一の JSON ボディで応答します。このボディにはレスポンスしか入る余地がありません。そのため、リクエストの途中でクライアントを呼び返すツール(`ctx.elicit()` やサンプリング)は、この区間で `NoBackChannelError` を送出します。進行中の呼び出しに紐づく通知(`ctx.report_progress()` による進捗や呼び出しごとのログメッセージ)は破棄されますが、独立した `GET` ストリームは無関係な通知を引き続き運びます。 * `stateless_http=True`:リクエストごとに新しいトランスポートを作り、セッションを追跡しません。 -* `max_request_body_size`:受け付ける POST ボディの最大サイズ(バイト単位)です。デフォルトは 4 MiB で、これより大きいリクエストはパースやセッション作成の前に HTTP 413 を受け取ります。正当な MCP メッセージがこのサイズを超える場合にだけ引き上げてください。 +* `max_request_body_size`:受け付けるリクエストボディの最大サイズ(バイト単位)です。デフォルトは 4 MiB で、これより大きいリクエストはパースやセッション作成の前に HTTP 413 を受け取ります。正当な MCP メッセージがこのサイズを超える場合にだけ引き上げてください。 * `event_store`、`retry_interval`、`transport_security`:再開可能性と DNS リバインディング保護です。localhost 以外の場所にデプロイするまでは後回しでかまいません。`transport_security` については **[デプロイとスケール](deploy.md)** で扱います。 !!! warning diff --git a/i18n/ja/pages/servers/handling-errors.md b/i18n/ja/pages/servers/handling-errors.md index 257d6a5b90..e667d1c8a9 100644 --- a/i18n/ja/pages/servers/handling-errors.md +++ b/i18n/ja/pages/servers/handling-errors.md @@ -1,25 +1,25 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # エラーの処理 {#handling-errors} -ツールの失敗には 2 通りあり、SDK はそれぞれをまったく違う形で扱います。 +ツールの失敗には 3 通りあり、SDK はそれぞれを違う形で扱います。 -通常の例外を送出すると、**モデル**がそれを目にします。`MCPError` を送出すると、**プロトコル**がそれを目にします。 +`ToolError` を送出すると、**モデル**がメッセージを目にします。`MCPError` を送出すると、**プロトコル**がそれを目にします。それ以外を送出するとクラッシュです。モデルには呼び出しが失敗したことだけが伝わり、トレースバックはログに記録されます。 -このページは、そのどちらを選ぶかについてです。 +このページは、そのどれを選ぶかについてです。 ## モデルが直せるエラー {#an-error-the-model-can-fix} 何かを検索するツールを用意し、その検索を空振りさせてみます。 -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -この 2 行に MCP らしいところは何もありません。`get_author` は、どんな Python 関数でもそうするように、ただの `ValueError` を送出しているだけです。 +`mcp.server.mcpserver.exceptions` にある `ToolError` は、何かがうまくいかなかったことをツールがモデルに伝える手段です。 カタログにないタイトルで呼び出して、結果を見てみましょう。 @@ -30,19 +30,21 @@ result.structured_content # None ``` * リクエストは**成功**しています。結果が返っており、呼び出し側では何も送出されていません。 -* `is_error` は `True` で、例外のメッセージ(ツール名が前に付きます)が `content` に入っています。まさにモデルが読む場所です。 +* `is_error` は `True` で、メッセージ(ツール名が前に付きます)が `content` に入っています。まさにモデルが読む場所です。 * `structured_content` は `None` です。失敗した呼び出しには、構造化すべき戻り値がありません。 -これが**ツールエラー**で、ツールが送出する「あらゆる」例外のデフォルトの扱いです。そして、ほとんどの場合これこそが望む挙動です。 +これが**ツールエラー**で、ほとんどの場合これこそが望む挙動です。 ツールを呼び出しているのはモデルです。引数を選んだのもモデルです。つまりツールエラーは会話の 1 ターンになります。モデルは「No book titled 'Nothing' in the catalog.」を読み、タイトルを推測し損ねたことに気づき、もっと良いタイトルで呼び直します。`raise` を 1 つ書いただけで、自己修正するエージェントが手に入りました。 +サーバー側では、`ToolError` はログに `INFO` が 1 行出るだけで、トレースバックはありません。想定していた失敗なので、調べることは何もありません。 + !!! tip ツールからエラーメッセージを `return` しないでください。返された文字列は `is_error=False` なので、モデルにとっても(そしてあらゆるクライアント UI にとっても)ツールは正常に動作し、その文字列が答えだったように見えます。`raise` してください。シグナルはこのフラグです。 ## モデルが直せないエラー {#an-error-the-model-cannot-fix} -今度は `ValueError` を `MCPError` に置き換えます。 +今度は `ToolError` を `MCPError` に置き換えます。 ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -74,16 +76,35 @@ result.structured_content # None 2 つの経路は、2 つの異なる問いに答えるものです。 -* 「実行」の失敗、つまりツールがやろうとしたことがうまくいかなかった場合は、**任意の例外を送出**します。呼び出しを選んだのはモデルなので、モデルがその結果を目にし、立て直す機会を得るべきです。綴りの間違ったタイトル、タイムアウトした上流の API、存在しない行。どれもツールエラーです。 +* 「実行」の失敗、つまりツールがやろうとしたことがうまくいかなかった場合は、**`ToolError` を送出**します。呼び出しを選んだのはモデルなので、モデルがその結果を目にし、立て直す機会を得るべきです。綴りの間違ったタイトル、タイムアウトした上流の API、存在しない行。どれもツールエラーです。 * 「リクエストそのもの」を拒否すべきときは **`MCPError` を送出**します。ツールが依存するケイパビリティをクライアントが持っていない、サーバーが誰にも応答できる状態にない、呼び出し側が必要な手順を飛ばした。どれもモデルが再試行しても直らないので、メッセージを渡しても得るものはありません。 -決め手になる問いは 1 つです。**もっと賢いモデルならこれを避けられたか**。はい → 通常の例外。いいえ → `MCPError`。 +決め手になる問いは 1 つです。**もっと賢いモデルならこれを避けられたか**。はい → `ToolError`。いいえ → `MCPError`。 この基準で見ると、`get_author` の 2 番目のバージョンは選択を誤っています。より良いタイトルで直るのですから、モデルはメッセージを見るべきでした。あれは仕組みを見せるためのもので、推奨するためのものではありません。 !!! info `MCPError` は `from mcp import MCPError` でインポートでき、`code`、`message`、省略可能な `data` ペイロードを受け取ります。そこに入れた内容がそのままクライアントに届きます。SDK は送出された `MCPError` をサニタイズせず、そのまま転送します。 +## その他の例外 {#any-other-exception} + +今度はチェックを外し、辞書の検索がそれ自体で失敗するのに任せます。 + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]` は `KeyError` を送出します。想定していなかった例外なので、SDK はクラッシュとして扱います。 + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +呼び出しは依然として `is_error=True` を返すので、モデルは失敗したことを知り、先へ進めます。受け取らないのは例外のテキストです。コードから出た `KeyError` や、3 つ下の層のライブラリのドライバーが吐いた SQL の山は、サーバーの内部を説明してしまうかもしれません。そのため、サーバーの外には決して出ません。 + +それを受け取るのはサーバー側です。サーバーはクラッシュを `ERROR` レベルで完全なトレースバック付きで記録し、`Tool 'get_author' raised an unexpected exception` と出力します。したがって、`WARNING` レベルの本番ログはどの `ToolError` でも静かなままで、本当に何かが壊れた瞬間に声を上げます。 + ## 存在しないリソース {#a-resource-that-doesnt-exist} リソースも同じ線引きをします。そして、よくあるケースのために名前付きの例外を 1 つ用意しています。 @@ -104,7 +125,7 @@ result.structured_content # None } ``` -ここには `is_error=True` のような中間的な結果がないことに注目してください。リソースの読み取りは、内容を返すか失敗するかのどちらかです。リソースにはプロトコルの経路しかありません。テンプレートをはじめ、リソースに関するその他すべては **[リソース](resources.md)** にあります。 +ここには `is_error=True` のような中間的な結果がないことに注目してください。リソースの読み取りは、内容を返すか失敗するかのどちらかです。リソースにはプロトコルの経路しかありません。`ResourceError` は「見つからない」以外の失敗のための同じ仕組みで(`-32603` とメッセージ)、どちらもログには `INFO` が 1 行出るだけです。`MCPError` を除くその他の例外はクラッシュです。クライアントには URI だけを示す `-32603` が届き、トレースバックは `ERROR` レベルでログに記録されます。テンプレートをはじめ、リソースに関するその他すべては **[リソース](resources.md)** にあります。 ## 送出する必要のないエラー {#errors-you-never-raise} @@ -115,16 +136,17 @@ result.structured_content # None つまり、書かなくてよい `raise` 文がまるごと一群あるということです。自分の型ヒントを改めて検証しないでください。 !!! info - このページの内容はすべて**クライアント**から見えるものです。テストを書くときに使うインメモリの `Client` にも、まったく同じものが見えます。`raise_exceptions=True` でもツールエラーがトレースバックに戻ることはありません。このフラグが作用できる時点では、例外はすでに `is_error=True` の結果になっています。結果に対してアサートしてください。このパターンは **[テスト](../get-started/testing.md)** で扱っています。 + このページで**クライアント**から見えるものはすべて、テストを書くときに使うインメモリの `Client` からも見えます。`raise_exceptions=True` でも、失敗したツールの例外が呼び出し側に返されることはありません。このフラグが作用できる時点では、例外はすでに `is_error=True` の結果になっています。結果に対してアサートしてください。クラッシュのトレースバックが必要なら、それはサーバーのログにあり、pytest の `caplog` で捕捉できます。このパターンは **[テスト](../get-started/testing.md)** で扱っています。 ## まとめ {#recap} -* ツールの中で**任意の例外**を送出する → 呼び出しは `is_error=True` を返し、メッセージが `content` に入ります。モデルはそれを読み、再試行できます。これがデフォルトです。 +* ツールの中で **`ToolError`** を送出する → 呼び出しは `is_error=True` を返し、メッセージが `content` に入ります。モデルはそれを読み、再試行できます。 * **`MCPError`** を送出する → 呼び出しそのものが JSON-RPC エラーで失敗します。モデルには何も見えず、ホストが対処します。`code`、`message`、`data` はそのまま残ります。 -* 決め手の問い:「もっと賢いモデルならこれを避けられたか」。はい → 例外。いいえ → `MCPError`。 +* 決め手の問い:「もっと賢いモデルならこれを避けられたか」。はい → `ToolError`。いいえ → `MCPError`。 +* **その他の例外**はクラッシュ → モデルには `Error executing tool ` とだけ書かれた `is_error=True`、サーバー側にはトレースバック付きの `ERROR` レコードが残ります。 * リソースのハンドラーから `ResourceNotFoundError` を送出する → プロトコルの `-32602` になり、URI が `data` に入ります。 * 不正な引数は関数が実行される前にスキーマと照合して拒否されます。そのために `raise` する必要はありません。 -* `from mcp import MCPError` でインポートします。エラーコードの定数は `mcp.types` から取得します。 +* インポート:`from mcp import MCPError`、`from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`、そしてエラーコードの定数は `mcp.types` から取得します。 エラーの処理はここまでです。サーバーが「公開する」ものはこれですべてです。すべてのハンドラーが実行中に読み取れるもの、そして実行中にクライアントに対して行えることは、次のセクション **[ハンドラーの中で](../handlers/index.md)** で扱います。 diff --git a/i18n/ja/pages/servers/media.md b/i18n/ja/pages/servers/media.md index 74d7ac21e0..29654bcb4b 100644 --- a/i18n/ja/pages/servers/media.md +++ b/i18n/ja/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # メディア {#media} @@ -81,6 +81,24 @@ result.structured_content # None !!! check `data=` の場合はファイル名がないので、推測する材料がありません。`format=` を忘れると、SDK はデフォルトにフォールバックします。画像なら `image/png`、音声なら `audio/wav` です。この方法で MP3 のバイト列から `Audio` を作ると、クライアントには `mime_type="audio/wav"` と伝えられ、それを忠実に信じてデコードに失敗します。`data=` を渡すときは `format=` も渡してください。 +## リソースを埋め込む {#embedding-a-resource} + +ツールはドキュメントを返すこともできます。テキストまたはバイト列に、それが置かれている URI と MIME タイプを添えたものです。これが **`EmbeddedResource`** で、コンテンツブロックのもう 1 つの種類です。単純な `str` と違い、コンテンツが何であるかをクライアントに伝えるので、クライアントはそれを添付ファイルとして表示したり、すでに知っているリソースだと認識したりできます。 + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines` は普通のリソースです(リソースについては **[リソース](resources.md)** で扱います)。このツールはリクエストに応じて同じドキュメントをモデルに渡します。`guidelines()` を直接呼び出すことで、情報源を 1 つに保っています。 +* `EmbeddedResource` と `TextResourceContents` は `mcp.types` にあります。画像のようなヘルパーはありません。組み立てたブロックはそのまま結果に入り、`structured_content` はありません。 +* リソースを登録したときの URI を使ってください。そうすれば、添付ファイルと `brand://guidelines` が同じドキュメントだとクライアントが判断できます。登録されているかどうかにかかわらず、どんな URI でも有効です。 + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +バイナリのコンテンツには、`TextResourceContents` の代わりに `BlobResourceContents(uri=..., mime_type=..., blob=...)` を使い、バイト列を base64 エンコードして `blob` に入れます。クライアントが後で `resources/read` できるポインターだけを送りたい場合は、代わりに `ResourceLink(name=..., uri=...)` を返してください。これもコンテンツブロックです。 + ## アイコン {#icons} `Icon` はメタデータであって、コンテンツではありません。画像そのものは運ばず、URI で画像を指し示します。クライアントはそれを取得して、サーバーの名前やツール、リソース、プロンプトの横に表示することがあります。 @@ -110,6 +128,7 @@ client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_ * ツールから `Image` または `Audio` を返すと、クライアントは `ImageContent` / `AudioContent` ブロックを受け取ります。バイト列が base64 エンコードされ、MIME タイプが付きます。 * `path=` から作って拡張子に MIME タイプを決めさせるか、メモリ上の `data=` に明示的な `format=` を添えて作ります。 +* `EmbeddedResource` を返すとドキュメント(テキストまたは base64 の blob に、その URI と MIME タイプを添えたもの)を結果に入れられ、`ResourceLink` を返すとポインターだけを送れます。 * メディアの結果には `structured_content` も出力スキーマもありません。 * `Icon` はポインターです。`src` URI に、省略可能な `mime_type`、`sizes`、`theme` を加えたものです。 * `icons=[...]` はサーバー、ツール、リソース、プロンプトのどれにも使え、クライアントは対応するオブジェクト上でそれらを見つけます。 diff --git a/i18n/ja/pages/servers/prompts.md b/i18n/ja/pages/servers/prompts.md index a367f7cde3..055c7b275b 100644 --- a/i18n/ja/pages/servers/prompts.md +++ b/i18n/ja/pages/servers/prompts.md @@ -1,6 +1,6 @@ --- translation: - sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094] + sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd] tool: 1 --- # プロンプト {#prompts} @@ -137,7 +137,52 @@ uv run mcp dev server.py ``` !!! info - **[ツール](tools.md)** を読んでいれば、このページの内容はもうすべて知っています。同じデコレーター、同じく docstring が説明になる仕組み、同じ `Annotated`/`Field` です。変わるのは、誰が起動するか(ユーザー)と、結果がどこへ行くか(会話の中)だけです。 + **[ツール](tools.md)** を読んでいれば、ここまでの内容はもうすべて知っています。同じデコレーター、同じく docstring が説明になる仕組み、同じ `Annotated`/`Field` です。変わるのは、誰が起動するか(ユーザー)と、結果がどこへ行くか(会話の中)だけです。 + +## テキスト以外のコンテンツ {#more-than-text} + +`UserMessage` と `AssistantMessage` は、`str` を受け取れる場所ならどこでも、コンテンツブロックや `Image` / `Audio` ヘルパーも受け取れます。プロンプトでよく出てくるケースは 2 つ、ドキュメントの添付と画像の添付です。 + +### ファイルを埋め込む {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* スタイルガイドは `style://python` にあるリソースで(リソースについては **[リソース](resources.md)** で扱います)、`server.py` の隣にある `style-guide.md` から読み込まれます。そこに任意の Markdown ファイルを置いてください。 +* `EmbeddedResource(resource=TextResourceContents(...))`(どちらも `mcp.types` にあります)は、URI と MIME タイプ付きのファイルを最初のメッセージとして運びます。そのファイルに言及するリクエストは、プレーンテキストとして後に続きます。 +* ガイドを f-string に貼り付けるのではなく埋め込むことで、クライアントはそれを添付ファイルとして表示でき、後から `style://python` を開き直せます。モデルはファイルをそのままの形で受け取ります。バイナリファイルの場合は、base64 の `blob` を持つ `BlobResourceContents` を使ってください。 + +レンダリングすると、最初のメッセージの `content` は `resource` ブロックです。 + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### 画像を添付する {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image` は **[画像、音声、アイコン](media.md)** で紹介するヘルパーです。プロンプトがレンダリングされるとき、`UserMessage` はこれを `ImageContent` ブロック(ファイルは base64 エンコードされ、MIME タイプは `.png` から推測されます)に変換します。`Audio` も同じように `AudioContent` になります。 +* `server.py` の隣に `architecture.png` という名前の PNG を何か置いてください。プロンプトの引数は文字列なので、画像は常にサーバー側から来ます。`component` が与えるのは言葉だけです。 + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## 実行時にリストを変更する {#changing-the-list-at-runtime} + +プロンプトは、クライアントが接続している間にも追加できます。たとえば、ユーザーが指示を自分専用のメニュー項目として保存できるようにする場合です。プロンプトを登録してから、通知します。 + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` は `@mcp.prompt()` とまったく同じように関数を登録し、`mcp.remove_prompt(name)` はその逆です。`add_prompt` は同名の既存エントリを上書きせずそのまま残すので、このツールは保存が置き換えになるよう、先に古いエントリを削除しています。`prompts/list` には変更がすぐに反映されます。 +* `await ctx.notify_prompts_changed()` は、`subscriptions/listen` ストリームで待ち受けているすべての `2026-07-28` クライアントに `notifications/prompts/list_changed` を送ります(**[サブスクリプション](../handlers/subscriptions.md)**)。`await ctx.session.send_prompt_list_changed()` は、呼び出し元のクライアントが 2026 年より前の世代のときに、そのクライアントへ送ります(**[レガシークライアントへの対応](../run/legacy-clients.md)**)。両方を呼んでください。どちらも、伝える相手がいなければ何もしません。 +* 通知を受け取ったクライアントは、もう一度 `prompts/list` を呼びます。Python の `Client` では `async with client.listen(prompts_list_changed=True) as sub:` がそれにあたり、`PromptsListChanged` イベントが届きます。 ## まとめ {#recap} @@ -147,5 +192,7 @@ uv run mcp dev server.py * `str` を返すと 1 つのユーザーメッセージになります。`UserMessage` / `AssistantMessage` のリストを返すと、複数ターンの会話の出発点を用意できます。 * `title=` と `Field(description=...)` は、クライアントが UI に表示するものです。 * 必須の引数が欠けていると、リクエスト全体が失敗します。プロンプト単位のエラー結果はありません。 +* `EmbeddedResource` や `Image` を `UserMessage` でラップすると、ドキュメントや画像を添付できます。 +* 実行時にプロンプトを追加・削除するには `mcp.add_prompt(...)` / `mcp.remove_prompt(...)` を使い、その後 `await ctx.notify_prompts_changed()` と `await ctx.session.send_prompt_list_changed()` を呼びます。 プロンプト(やリソーステンプレート)の引数をサーバー側でオートコンプリートする機能については、**[補完](completions.md)** を参照してください。 diff --git a/i18n/ja/pages/servers/structured-output.md b/i18n/ja/pages/servers/structured-output.md index ad278aa435..73fa0bb949 100644 --- a/i18n/ja/pages/servers/structured-output.md +++ b/i18n/ja/pages/servers/structured-output.md @@ -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 --- # 構造化出力 {#structured-output} @@ -103,7 +103,7 @@ result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions --8<-- "docs_src/structured_output/tutorial003.py" ``` -`TypedDict` は実行時にはただの `dict` なので、組み立てて返すのもそれです。スキーマもバリデーションも `structured_content` も、`BaseModel` 版と同一です(説明だけは付きません。`TypedDict` には説明を書く場所がないからです)。 +`TypedDict` は実行時にはただの `dict` なので、組み立てて返すのもそれです。スキーマもバリデーションも `structured_content` も、`BaseModel` 版と同じルールに従います。クラスの docstring や `Annotated[..., Field(description=...)]` を足せばそれが説明になり、dict に入れなかった `NotRequired` のキーは `structured_content` にも入りません。 ## データクラス {#a-dataclass} @@ -185,16 +185,16 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} アノテーションは `WeatherData` を約束しています。ところが、上流のレスポンスが `humidity` を送ってこなくなりました。 !!! check - `get_weather` を呼び出しても、中身が半分欠けたオブジェクトがこっそりクライアントに渡ることはありません。呼び出しは失敗し、エラーの冒頭の数行にそのフィールド名が示されます。 + `get_weather` を呼び出しても、中身が半分欠けたオブジェクトがこっそりクライアントに渡ることはありません。呼び出しは失敗します。クライアントは `Error executing tool get_weather` とともに `is_error=True` を受け取るので、モデルは、ありもしない天気を自信満々に読み上げる代わりに、呼び出しが失敗したと分かります。フィールド名は開発者向けに、サーバーログの `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] ``` - このテキストは `is_error=True` の付いたツール結果として返ってくるので、モデルは、ありもしない天気を自信満々に読み上げる代わりに、呼び出しが失敗したと分かります。 - ちなみに、`-> WeatherData` のツールから単なる `dict` を返してもかまいません。`json.loads` が返したのはまさにそれです。バリデーションの対象は Python の型ではなく、値です。 ## オプトアウト {#opting-out} @@ -209,6 +209,10 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} その逆の `structured_output=True` は、自動検出を必須要件に変えます。戻り値の型からスキーマを作れないツールは、テキストにフォールバックするのではなく、インポート時に例外を送出します。 +## コンテンツブロックとメディア {#content-blocks-and-media} + +コンテンツブロックとメディア(`TextContent`、`EmbeddedResource`、`Image`、`Audio` など)は、何もしなくてもオプトアウトされます。単体で返しても、`list`、`tuple`、`Sequence` の要素にしても、ユニオンの一方にしても同じです。これらはモデルが読むためのものなので、自動検出はそこからスキーマを導き出しません(`Image` と `Audio` については **[画像、音声、アイコン](media.md)** で扱っています)。それでも `structured_output=True` を渡せば、コンテンツブロックのクラスにはスキーマが強制されます。 + ## 型ヒントのないクラス {#a-class-without-type-hints} 頼んでもいないのに非構造化になってしまう道が 1 つだけあります。**本体にアノテーションが 1 つもない**クラスを返すことです。 @@ -237,6 +241,6 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} * スカラー、リスト、タプル、ユニオンは `{"result": ...}` でラップされます。モデル、`TypedDict`、データクラス、アノテーション付きクラス、`dict[str, ...]` はもともとオブジェクトなので、そのままです。 * どの結果も `content`(モデル向けのテキスト)**と** `structured_content`(アプリケーション向けのデータ)の両方を持ちます。 * 返したものはスキーマに照らして検証されます。食い違いは壊れた結果ではなく、ツールエラーになります。 -* `structured_output=False` を渡すと、そのツールはオプトアウトします。型ヒントのないクラスは黙ってオプトアウトするので、気をつけてください。 +* `structured_output=False` を渡すと、そのツールはオプトアウトします。コンテンツブロック、`Image`、`Audio` はデフォルトでオプトアウトします。型ヒントのないクラスは黙ってオプトアウトするので、気をつけてください。 これで、ツールが返せるものはすべて押さえました。次は 2 つ目のプリミティブ、**[リソース](resources.md)** です。 diff --git a/i18n/ja/pages/servers/tools.md b/i18n/ja/pages/servers/tools.md index 78c3243092..74c1a58c6d 100644 --- a/i18n/ja/pages/servers/tools.md +++ b/i18n/ja/pages/servers/tools.md @@ -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} @@ -39,6 +39,8 @@ SDK はこれらの型ヒントから JSON Schema を生成し、`tools/list` どちらの引数にもデフォルト値がないため、両方とも `required` に入っています。これはすぐ後で直します。(`title` キーは Pydantic が生成した付随物です。契約にあたるのは、プロパティとその型、そして `required` です。) +`$schema` キーもありません。MCP はこのキーのないスキーマを **JSON Schema 2020-12** として扱い、Pydantic が生成するのもまさにこの形式です。そのため、**[低レベル Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)** でスキーマを手書きするようになるまで、選ぶべきものは何もありません。 + !!! tip ここでの型ヒントはドキュメントではありません。**契約そのもの**です。クライアントが `"limit": "ten"` を送ってきても、関数が実行される前に SDK が拒否します。 diff --git a/i18n/ja/pages/servers/uri-templates.md b/i18n/ja/pages/servers/uri-templates.md index c4f6a7eaa0..8ba223eddf 100644 --- a/i18n/ja/pages/servers/uri-templates.md +++ b/i18n/ja/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # URI テンプレートとパスの安全性 {#uri-templates-and-path-safety} @@ -98,7 +98,7 @@ SDK はハンドラーの実行前に、次のいずれかに当てはまるパ 組み込みのチェックはよくあるケースを止めますが、サンドボックスの境界までは知りようがありません。ファイルシステムにアクセスする場合は、`safe_join` を使ってパスを解決し、ベースディレクトリの内側に収まっていることを検証してください。 -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` @@ -127,7 +127,7 @@ SDK はハンドラーの実行前に、次のいずれかに当てはまるパ これらのチェックはヒューリスティックな事前フィルターです。ファイルシステムへのアクセスでは、依然として `safe_join` が封じ込めの境界です。 !!! tip - ハンドラーがリクエストに応えられない場合(ファイルが存在しない、ID が不明など)は、例外を送出してください。SDK がそれをエラーレスポンスに変換します。プロトコルエラーとツールエラーの違いについては、**[エラーの処理](handling-errors.md)** を参照してください。 + ハンドラーがリクエストに応えられない場合(ファイルが存在しない、ID が不明など)は、上の `read_manual` と同じように `ResourceNotFoundError` を送出してください。クライアントには、指定したメッセージと URI を含む `-32602` が返ります。想定外の例外は、代わりに汎用の `-32603` になります。**[エラーの処理](handling-errors.md#a-resource-that-doesnt-exist)** を参照してください。 ## 低レベル Server でのリソース {#resources-on-the-low-level-server} diff --git a/i18n/ja/pages/troubleshooting.md b/i18n/ja/pages/troubleshooting.md index caffc9d146..77f5d874e1 100644 --- a/i18n/ja/pages/troubleshooting.md +++ b/i18n/ja/pages/troubleshooting.md @@ -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 --- # トラブルシューティング {#troubleshooting} @@ -79,11 +79,11 @@ async def main() -> None: `__aexit__` が切断です。だからこそ、呼び忘れる `client.close()` というものが存在しません。**[テスト](get-started/testing.md)** は、まさにこのパターンの上に組み立てられています。 -## `Error executing tool : ` と `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `、`Error executing tool `、`Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} 読んでいるのは**結果**であって、例外ではありません。`call_tool` は例外を送出しておらず、ツールが失敗しても送出することは決してありません。 -サーバーが知らない都市で `forecast` を呼ぶと、ツールが送出した例外は、リクエストが「成功」と記された形で返ってきます。 +サーバーが知らない都市で `forecast` を呼ぶと、ツールが送出した `ToolError` は、リクエストが「成功」と記された形で返ってきます。 ```python result.is_error # True @@ -95,6 +95,8 @@ result.structured_content # None 直すのはクライアント側です。**`result.is_error` を確認してください**。`call_tool` を `try/except` で囲んでも、これらはどれも捕まりません。捕まえるものがないからです。これは意図した設計であり、このページで身につけておくと一番役に立つ点です。呼び出しを選んだのは「モデル」なので、メッセージを受け取ってやり直す機会を得るのもモデルです。詳しくは **[エラーの処理](servers/handling-errors.md)** を参照してください。例外を「送出する」側の `MCPError` の経路も含めて説明しています。 +メッセージのない素の形、`Error executing tool ` は、ツールが**クラッシュした**ことを意味します。ツールが想定していなかった例外が抜け出した(または戻り値が出力スキーマに通らなかった)場合で、その例外の文字列は通信路には載せません。トレースバックは**サーバーのログ**に `ERROR` で、`Tool '' 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} `@mcp.tool()` ではなく `@mcp.tool` と書いています。`tool()` はデコレーターの「ファクトリー」です。括弧がないと、Python は関数をその `name=` パラメーターに渡してしまいます。 @@ -393,7 +395,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## まとめ {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup` がエラーであることは決してありません。**最後の行**を読んでください。`async with Client(...)` ブロックの「内側」で `MCPError` を捕まえれば、包まれること自体を完全に避けられます。 -* `call_tool` は、ツールが失敗しても例外を送出しません。`Error executing tool ...` と `Unknown tool: ...` は結果です。`result.is_error` を確認してください。 +* `call_tool` は、ツールが失敗しても例外を送出しません。`Error executing tool ...` と `Unknown tool: ...` は結果です。`result.is_error` を確認してください。ツール名の後にメッセージがなければクラッシュしたという意味で、トレースバックはサーバーログにあります。 * `Client must be used within an async context manager` -> `async with` を使ってください。`Use @tool() instead of @tool` -> 括弧を付けてください。 * サーバーログの `Tool already exists:` は、同名の 2 つのツールが 1 つに潰れた唯一の合図です。 * 1 つの 421、3 つの綴り:`Server returned an error response`(python の `Client`)、`421 Misdirected Request` / `Invalid Host header`(それ以外すべて)、`Invalid Host header: `(サーバーログ)。直し方:`transport_security=TransportSecuritySettings(allowed_hosts=[...])`。 diff --git a/i18n/ja/pages/whats-new.md b/i18n/ja/pages/whats-new.md index c145a4bf0e..eb226e2692 100644 --- a/i18n/ja/pages/whats-new.md +++ b/i18n/ja/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # v2 の新機能 {#whats-new-in-v2} @@ -41,9 +41,9 @@ v1 では 3 つの層が入れ子になっていました。生のストリー --8<-- "docs_src/client/tutorial001.py" ``` -`Client` が受け取るのは、サーバーオブジェクト(インメモリでトランスポートなし。テストで使う形です)、URL(Streamable HTTP)、または `stdio_client(...)` のような任意のトランスポートのコンテキストマネージャーです。`async with` に入ると接続し、サーバーがどの世代を話すかにかかわらずプロトコルバージョンをネゴシエートします。その後は `client.server_capabilities` と `client.protocol_version` がそのまま使え、サーバーが自身を名乗る場合は `client.server_info` も使えます(2026 年世代では識別情報が省略可能なので、`Implementation | None` になりました)。v1 で登録したサンプリングとエリシテーションのコールバックは引き続き動作します(コールバックの本体には、このページのほかの項目と同じ snake_case への属性名の変更が及びます)。加えて 2026 形式の「結果に埋め込まれたリクエスト」(後述)にも応答するようになり、1 つずつではなく並行して実行されます。低レベルのインターフェースが必要な人のために `ClientSession` は今も下にあり、`client.session` で取り出せます。ただしこちらも変わっています(新しいディスパッチャーエンジンの上で動き、自身のシグネチャも一部変わりました)。下りていく前に**[移行ガイド](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**を読んでください。 +`Client` が受け取るのは、サーバーオブジェクト(インメモリでトランスポートなし。テストで使う形です)、URL(Streamable HTTP)、`StdioServerParameters`(stdio のサブプロセス)、または `sse_client(...)` のようなそれ以外の任意のトランスポートのコンテキストマネージャーです。`async with` に入ると接続し、サーバーがどの世代を話すかにかかわらずプロトコルバージョンをネゴシエートします。その後は `client.server_capabilities` と `client.protocol_version` がそのまま使え、サーバーが自身を名乗る場合は `client.server_info` も使えます(2026 年世代では識別情報が省略可能なので、`Implementation | None` になりました)。v1 で登録したサンプリングとエリシテーションのコールバックは引き続き動作します(コールバックの本体には、このページのほかの項目と同じ snake_case への属性名の変更が及びます)。加えて 2026 形式の「結果に埋め込まれたリクエスト」(後述)にも応答するようになり、1 つずつではなく並行して実行されます。低レベルのインターフェースが必要な人のために `ClientSession` は今も下にあり、`client.session` で取り出せます。ただしこちらも変わっています(新しいディスパッチャーエンジンの上で動き、自身のシグネチャも一部変わりました)。下りていく前に**[移行ガイド](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**を読んでください。 -**[Client](client/index.md)** で紹介し、**[クライアントのトランスポート](client/transports.md)**で 3 つの接続形態を、**[クライアントのコールバック](client/callbacks.md)**でコールバックそのものを扱います。**[テスト](get-started/testing.md)**では、v1 の `create_connected_server_and_client_session()` ヘルパーに代わるインメモリのパターンを示します。 +**[Client](client/index.md)** で紹介し、**[クライアントのトランスポート](client/transports.md)**で 4 つの接続形態を、**[クライアントのコールバック](client/callbacks.md)**でコールバックそのものを扱います。**[テスト](get-started/testing.md)**では、v1 の `create_connected_server_and_client_session()` ヘルパーに代わるインメモリのパターンを示します。 ### 低レベルの `Server` は改名ではなく再構築 {#the-low-level-server-was-rebuilt-not-renamed} @@ -129,7 +129,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB 名前の変更は自分から存在を知らせてくれます。次のものは知らせてくれません。 * **同期関数はワーカースレッドで動きます。** `def` のツール(リソース、プロンプト、リゾルバーも同様)はイベントループをブロックしなくなりました。その代わり、本体はイベントループのスレッド上では動かなくなったので、特定のスレッドに縛られたコードには影響します。`async def` のハンドラーはそのままです。詳しくは**[移行ガイド](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**を参照してください。 -* **ツールの中で送出した `MCPError`(v1 の `McpError`)はプロトコルエラーになりました。** モデルがそれを見ることはありません。ほかの例外はすべて、これまでどおりモデルが読んで対応できる `is_error=True` の結果になります。この切り分けは**[エラーの処理](servers/handling-errors.md)**で説明しています。 +* **ツールの中で送出した `MCPError`(v1 の `McpError`)はプロトコルエラーになりました。** モデルがそれを見ることはありません。ほかの例外はすべて、これまでどおり `is_error=True` の結果になりますが、モデルに届くメッセージは `ToolError` のものだけです。それ以外の例外は `Error executing tool ` と表示されるようになり、トレースバックはサーバーのログに残ります。この切り分けは**[エラーの処理](servers/handling-errors.md)**で説明しています。 * **結果は送り出す前に検証されます。** `input_schema` が `{}` の手組みの `Tool` は、`tools/list` で失敗するようになりました(仕様は `"type": "object"` を要求します)。`@mcp.tool()` で作ったサーバーがこれに出会うことはありません。スキーマは SDK が書くからです。 * **クライアントは受け取ったものを検証します。** `list_tools()` と `call_tool()` は、ネゴシエートしたプロトコルバージョンに照らしてサーバーの応答を検査します。そのため、v1 の寛容なパースが見逃していた「少しだけ不正な」サーバーは `pydantic.ValidationError` を送出するようになりました。自分で管理していないサーバーに接続するなら、そうしたサーバーを見つけるのは自分だと思っておいてください。詳しくは**[移行ガイド](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)**を参照してください。 * **URI テンプレートは本物の RFC 6570 になりました。** `{+path}`、`{?query}` などが使え、マッチングは正規表現的な緩さではなく厳密になり、取り出した値に含まれるパストラバーサルはデフォルトで拒否されます。厳格になったテンプレートは、最初のリクエストではなくデコレーターの適用時に失敗します。詳しくは **[URI テンプレート](servers/uri-templates.md)**を参照してください。 diff --git a/i18n/ko/pages/advanced/low-level-server.md b/i18n/ko/pages/advanced/low-level-server.md index 543acf708d..811989ad21 100644 --- a/i18n/ko/pages/advanced/low-level-server.md +++ b/i18n/ko/pages/advanced/low-level-server.md @@ -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 --- # 저수준 Server {#the-low-level-server} @@ -116,6 +116,17 @@ asyncio.run(main()) 서버는 두 필드를 비교하지 않습니다. 이 SDK의 `Client`는 비교합니다. 선언한 `output_schema`를 만족하지 않는 `structured_content`를 반환하면 `call_tool`이 `RuntimeError`를 일으키는데, 메시지는 `Invalid structured content returned by tool search_books`로 시작해 `jsonschema` 실패 내용을 인용합니다. 스키마를 약속하기는 쉽지만, 지키는 것은 작성자의 몫입니다. 반환 타입과 스키마의 전체 단계는 **[구조화된 출력](../servers/structured-output.md)**에서 확인하세요. +## JSON Schema 2020-12 다이얼렉트 {#the-dialect-is-json-schema-2020-12} + +`input_schema`와 `output_schema`는 JSON Schema이며, 다이얼렉트는 [MCP 사양](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage)이 정해 둡니다. `$schema` 키가 없는 스키마는 **JSON Schema 2020-12**입니다. `MCPServer`가 생성하는 스키마는 이 기본값에 기대고(Pydantic은 2020-12를 쓰고 키를 생략합니다), 손으로 작성한 dict도 같은 기준을 따르므로 2020-12 어휘 전체를 사용할 수 있습니다. + +```python title="server.py" hl_lines="8 14-15" +--8<-- "docs_src/lowlevel/tutorial007.py" +``` + +* `input_schema`의 루트는 `"type": "object"`여야 합니다. 그 옆의 `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, 로컬 `$ref`를 쓰는 `$defs`, 그리고 나머지 2020-12 키워드는 쓴 그대로 클라이언트에 도달합니다. +* `$schema` 키는 필요 없습니다. 더 오래된 드래프트를 선택할 때만 추가하세요. 도구의 `output_schema`에 `structured_content`를 대조해 검증하는 이 SDK의 `Client`는 `$schema`를 보고 검증기를 고르며, 키가 없으면 2020-12를 사용합니다. + ## `_meta`: 모델이 아닌 애플리케이션을 위한 데이터 {#\_meta-for-the-application-not-the-model} `content`는 답변 중 모델이 읽는 부분입니다. `structured_content`는 같은 답변을 타입이 있는 데이터로 나타낸 것입니다. `_meta`는 세 번째 채널입니다. 답변의 일부가 전혀 아니면서 결과에 함께 실려 **클라이언트 애플리케이션**으로 가는 데이터입니다. @@ -167,7 +178,7 @@ asyncio.run(main()) --8<-- "docs_src/lowlevel/tutorial006.py" ``` -* 첫 번째 인수는 메서드 문자열입니다. 알림에는 짝이 되는 `add_notification_handler`가 있습니다. +* 첫 번째 인수는 메서드 문자열입니다. 알림에는 짝이 되는 `add_notification_handler`가 있습니다. 이 핸들러는 stdio와 핸드셰이크 세대의 HTTP 연결에서 실행됩니다. `2026-07-28` Streamable HTTP 경로에서는 클라이언트가 보낸 알림 POST가 `202`로 수신 확인만 되고 디스패치되지 않는데, 해당 리비전이 HTTP를 통한 클라이언트에서 서버로의 알림을 정의하지 않기 때문입니다. * `params_type`은 핸들러가 실행되기 **전에** 들어오는 `params`를 검증하는 기준 모델입니다. 따라서 커스텀 메서드는 도구가 받지 못하는 검증을 **받습니다**. `_meta` 필드가 다른 모든 메서드처럼 파싱되도록 `RequestParams`를 상속하세요. * 핸들러는 `BaseModel`, `dict`, `None` 중 하나를 반환합니다. SDK가 이를 JSON-RPC 결과로 직렬화합니다. diff --git a/i18n/ko/pages/advanced/middleware.md b/i18n/ko/pages/advanced/middleware.md index 0099e795cb..3045521526 100644 --- a/i18n/ko/pages/advanced/middleware.md +++ b/i18n/ko/pages/advanced/middleware.md @@ -1,6 +1,6 @@ --- translation: - sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c] + sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43] tool: 1 --- # 미들웨어 {#middleware} @@ -53,8 +53,11 @@ tools/call took 0.1 ms * 연결 설정. `server/discover`이거나, 레거시 세션에서는 `initialize`와 `notifications/initialized`입니다. -* 모든 요청과 모든 알림. 알림의 경우 `ctx.request_id is None`이고, +* 서버에 도달하는 모든 요청과 모든 알림. 알림의 경우 `ctx.request_id is None`이고, `call_next(ctx)`는 `None`을 반환하며, 무엇을 반환하든 버려집니다. + (`2026-07-28` Streamable HTTP 경로에서는 클라이언트의 알림 POST가 트랜스포트에서 `202`로 + 확인 응답되고 디스패치되지 않으므로 미들웨어에도 도달하지 않습니다. 해당 리비전은 HTTP를 + 통한 클라이언트에서 서버로의 알림을 정의하지 않습니다.) * 서버에 핸들러가 없는 메서드까지도 포함됩니다. `call_next`가 `MCPError(-32601, "Method not found")`를 일으키고, 이 예외는 클라이언트로 가는 길에 미들웨어를 **통과합니다**. @@ -113,8 +116,8 @@ OpenTelemetry 스팬을 내보내는 미들웨어입니다. 직접 추가할 필 * 미들웨어는 `async (ctx, call_next) -> result` 형태이며, `MCPServer(middleware=[...])`로 전달하거나(또는 `mcp.middleware`에 추가하거나) 저수준 `Server`에서는 `server.middleware`에 추가합니다. -* 들어오는 **모든** 메시지(`server/discover`, `initialize`, 요청, 알림, 알 수 없는 메서드)를 - 감싸며 바깥쪽부터 실행됩니다. +* 서버에 도달하는 들어오는 **모든** 메시지(`server/discover`, `initialize`, 요청, 알림, + 알 수 없는 메서드)를 감싸며 바깥쪽부터 실행됩니다. * `ctx.request_id is None`으로 알림과 요청을 구분합니다. * `call_next`를 호출하는 대신 예외를 일으키면 메시지 하나를 거부합니다. 연결은 유지됩니다. * SDK 자체의 OpenTelemetry 트레이싱도 미들웨어이며, 이미 목록에 있습니다. diff --git a/i18n/ko/pages/client/index.md b/i18n/ko/pages/client/index.md index 00312b731e..636b1609a2 100644 --- a/i18n/ko/pages/client/index.md +++ b/i18n/ko/pages/client/index.md @@ -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 --- # 클라이언트 {#the-client} @@ -27,9 +27,10 @@ translation: * `MCPServer`(또는 저수준 `Server`) 인스턴스: **프로세스 내부**에서 연결합니다. * URL 문자열(`Client("http://localhost:8000/mcp")`): 프로덕션 경로인 Streamable HTTP입니다. -* **트랜스포트**: `async with ... as (read, write)`로 사용할 수 있는 모든 것, 예를 들어 서브프로세스를 감싸는 `stdio_client(...)`입니다. +* `StdioServerParameters`: **서브프로세스**로 실행할 명령이며, 그 stdin과 stdout을 통해 통신합니다. +* **트랜스포트**: `async with ... as (read, write)`로 사용할 수 있는 모든 것, 예를 들어 직접 만든 HTTP 클라이언트를 감싸는 `streamable_http_client(url, http_client=...)`입니다. -이 페이지의 나머지 내용은 세 가지 모두에서 동일합니다. 헤더, 서브프로세스, 타임아웃, `Transport` 프로토콜은 별도의 페이지인 **[클라이언트 트랜스포트](transports.md)**에서 다룹니다. +이 페이지의 나머지 내용은 네 가지 모두에서 동일합니다. 헤더, 서브프로세스, 타임아웃, `Transport` 프로토콜은 별도의 페이지인 **[클라이언트 트랜스포트](transports.md)**에서 다룹니다. ### 연결된 클라이언트에 있는 것 {#whats-on-a-connected-client} @@ -85,7 +86,7 @@ tool.description # 'Search the catalog by title or author.' `call_tool(name, arguments)`는 도구를 실행하고 `CallToolResult`를 돌려줍니다. -```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 @@ result.is_error # False !!! check `lookup_book`에 `"Solaris"`(카탈로그에 없는 제목)를 요청하면 함수가 - `ValueError`를 발생시킵니다. 그래도 호출은 정상적으로 반환됩니다. + `ToolError`를 발생시킵니다. 그래도 호출은 정상적으로 반환됩니다. ```python result.is_error # True @@ -125,8 +126,9 @@ result.is_error # False result.structured_content # None ``` - 예외 메시지는 **모델**이 읽고 다시 시도할 수 있는 `content`에 담겼습니다. 이는 - 의도된 것입니다. 도구 오류는 충돌이 아니라 대화의 일부입니다. `structured_content`를 + `ToolError`의 메시지는 **모델**이 읽고 다시 시도할 수 있는 `content`에 담겼습니다. 이는 + 의도된 것입니다. 도구 오류는 충돌이 아니라 대화의 일부입니다. (도구가 다른 예외로 + 충돌했다면 `content`에는 `Error executing tool lookup_book`만 담겼을 것입니다.) `structured_content`를 믿기 전에 항상 `is_error`를 확인하세요. !!! warning diff --git a/i18n/ko/pages/client/transports.md b/i18n/ko/pages/client/transports.md index 3a8dbca682..801f67be94 100644 --- a/i18n/ko/pages/client/transports.md +++ b/i18n/ko/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # 클라이언트 트랜스포트 {#client-transports} @@ -87,15 +87,15 @@ TLS 관련 참고 사항이 하나 있습니다. `httpx2`는 번들된 CA 목록 **stdio** 서버는 서브프로세스입니다. 클라이언트가 이를 실행하고, stdin에 JSON-RPC를 쓰고, stdout에서 JSON-RPC를 읽습니다. 데스크톱 호스트가 사용자 컴퓨터에서 서버를 실행하는 방식이 바로 이것입니다. 호스트는 **곧** 이 코드에 UI를 더한 것이며, **[실제 호스트에 연결하기](../get-started/real-host.md)**는 같은 관계를 호스트 쪽에서 설정 파일로 바라본 것입니다. -`StdioServerParameters`로 프로세스를 기술하고, `stdio_client`로 트랜스포트로 바꾼 다음, **그것**을 `Client`에 넘기세요. +`StdioServerParameters`로 프로세스를 기술하고 `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`는 매개변수 객체를 단독으로 받지 않습니다. `StdioServerParameters`는 설정이고, `stdio_client(server)`는 그 설정으로 프로세스를 띄우는 방법을 아는 트랜스포트입니다. 항상 감싸서 전달하세요. +블록에 진입하면 프로세스가 생성됩니다. 블록을 벗어나면 서브프로세스도 종료됩니다. stdin을 닫고, 기다리고, 남아 있으면 강제 종료합니다. 직접 정리할 일은 없습니다. -`async with` 블록을 벗어나면 서브프로세스도 함께 종료됩니다. stdin을 닫고, 기다리고, 남아 있으면 강제 종료합니다. 직접 정리할 일은 없습니다. +자식 프로세스의 stderr는 부모 프로세스의 stderr로 나갑니다. 다른 곳으로 보내려면 `mcp`의 `stdio_client`로 트랜스포트를 직접 만들어 `Client(stdio_client(server, errlog=log_file))`처럼 대신 전달하세요. !!! warning 자식 프로세스는 환경 변수를 상속하지 **않습니다**. 직접 작성하지 않았을 수도 있는 프로세스로 @@ -113,16 +113,16 @@ TLS 관련 참고 사항이 하나 있습니다. `httpx2`는 번들된 CA 목록 `Client`에게 위의 모든 것은 같은 것입니다. -**트랜스포트**란 `(read, write)` 메시지 스트림 쌍을 내어주는 비동기 컨텍스트 매니저라면 무엇이든 해당합니다. 정식으로는 `mcp.client`의 `Transport` 프로토콜입니다. `Client`는 인자를 타입으로 구분합니다. 서버 객체는 프로세스 내에서 연결하고, `str`은 `streamable_http_client(url)`이 되며, 그 밖의 것은 트랜스포트로 직접 진입합니다. 마지막 규칙 덕분에 `stdio_client(...)`, `streamable_http_client(...)`, `sse_client(...)`가 모두 같은 자리에 들어가고, 직접 만든 트랜스포트도 쓸 수 있습니다. +**트랜스포트**란 `(read, write)` 메시지 스트림 쌍을 내어주는 비동기 컨텍스트 매니저라면 무엇이든 해당합니다. 정식으로는 `mcp.client`의 `Transport` 프로토콜입니다. `Client`는 인자를 타입으로 구분합니다. 서버 객체는 프로세스 내에서 연결하고, `str`은 `streamable_http_client(url)`이 되며, `StdioServerParameters`는 `stdio_client(params)`가 되고, 그 밖의 것은 트랜스포트로 직접 진입합니다. 마지막 규칙 덕분에 `stdio_client(...)`, `streamable_http_client(...)`, `sse_client(...)`가 모두 같은 자리에 들어가고, 직접 만든 트랜스포트도 쓸 수 있습니다. ## 요약 {#recap} * `Client(mcp)`(서버 객체)는 인메모리로 연결합니다. 테스트와 임베딩에 사용하세요. * `Client("http://.../mcp")`(URL)는 프로덕션 트랜스포트인 Streamable HTTP로 연결합니다. * 헤더, 인증, 프록시, 타임아웃은 `streamable_http_client(url, http_client=...)`에 전달하는 `httpx2.AsyncClient`에 설정합니다. `headers=` 키워드는 없습니다. -* stdio는 `Client(stdio_client(StdioServerParameters(...)))`이며, 매개변수 객체만 단독으로 쓰는 일은 절대 없습니다. +* stdio는 `Client(StdioServerParameters(...))`입니다. 자식 프로세스의 stderr를 다른 곳으로 돌릴 때만 직접 `stdio_client(...)`로 감싸세요. * 서브프로세스는 현재 환경이 아니라 허용 목록에 있는 환경 변수만 받습니다. `env=`로 여기에 추가합니다. -* 트랜스포트는 `async with x as (read, write)`로 쓸 수 있는 것이면 무엇이든 됩니다. `Client`는 서버 객체나 URL이 아닌 것은 모두 그 프로토콜에 그대로 넘깁니다. +* 트랜스포트는 `async with x as (read, write)`로 쓸 수 있는 것이면 무엇이든 됩니다. `Client`는 서버 객체, URL, `StdioServerParameters`가 아닌 것은 모두 그 프로토콜에 그대로 넘깁니다. * `Client`를 생성하면 트랜스포트가 정해집니다. `async with`가 이를 엽니다. 트랜스포트가 열리면 양쪽은 프로토콜 버전에 합의해야 합니다. 보통은 신경 쓸 일이 없지만, 필요할 때는 **[프로토콜 버전](../protocol-versions.md)** 페이지를 확인하세요. diff --git a/i18n/ko/pages/deprecated.md b/i18n/ko/pages/deprecated.md index ba4dc0e836..63d1d27a41 100644 --- a/i18n/ko/pages/deprecated.md +++ b/i18n/ko/pages/deprecated.md @@ -1,11 +1,11 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # 지원 중단 예정 기능 {#deprecated-features} -2026-07-28 사양은 다섯 가지를 퇴역시킵니다. SDK는 여전히 이 다섯 가지를 모두 구현하며, 이제 모두에 **지원 중단 예정(deprecated) 경고**가 붙습니다. +2026-07-28 사양은 다섯 가지를 퇴역시킵니다. SDK는 여전히 이 다섯 가지를 모두 구현하며, 이제 모두에 **지원 중단 예정(deprecated) 경고**가 붙습니다. SDK 헬퍼 하나는 별도의 이유로 지원 중단 예정이며 [페이지 끝](#deprecated-sdk-helpers)에 정리되어 있습니다. 아래 표는 지원 중단 예정인 각 기능의 이름, 사라지는 이유, 그리고 대신 사용할 대체 수단을 정리한 것입니다. @@ -55,6 +55,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE 이 두 기능은 클라이언트가 해당 콜백을 등록한 `mode="legacy"` 연결에서만 처음부터 끝까지 동작합니다. +## 레거시 세션에서의 `ping` {#ping-on-a-legacy-session} + +**ping**은 상대가 여전히 응답하는지 확인하려고 어느 쪽이든 보낼 수 있는 빈 요청입니다. 2026-07-28 사양은 이를 제거합니다([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)). 최신 클라이언트가 보내는 모든 요청이 이미 서버가 살아 있음을 증명하고, 최신 서버에는 ping을 보낼 채널이 없기 때문입니다. 두 SDK 메서드 모두 핸드셰이크 시절의 세션에서는 여전히 동작합니다. 클라이언트에서는 다음과 같습니다. + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +서버에서는 어느 핸들러 안에서든 다음과 같습니다. + +```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()`은 호출할 때마다 `MCPDeprecationWarning` 경고를 냅니다. 기본(`2026-07-28`) 연결에서는 서버가 대신 `MCPError: Method not found`로 응답합니다. +* `ctx.session.send_ping()`에는 경고가 없습니다. 최신 연결에서는 다른 모든 서버 주도 요청과 마찬가지로 백채널 없음 오류를 발생시킵니다. +* 양쪽 모두 ping에 응답하려고 따로 등록하는 것은 없습니다. + +## 루트 변경 알림 {#roots-change-notifications} + +루트 기능을 선언한 2025년 시절 클라이언트는 `notifications/roots/list_changed`를 보내 작업 공간 폴더가 바뀌었음을 서버에 알릴 수 있고, 서버는 `roots/list`를 다시 요청하는 것으로 응답합니다. 2026-07-28 사양은 푸시 방식 루트 흐름의 나머지와 함께 이 알림을 제거합니다. 클라이언트에서는 `list_roots_callback=`을 전달하는 것(**[클라이언트 콜백](client/callbacks.md)**)이 곧 `"roots": {"listChanged": true}`를 선언하는 일이며, 호출 하나가 그 약속을 지킵니다. + +```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() +``` + +서버에서는 저수준 `Server`가 수신 핸들러를 받습니다. + +```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`는 `list_roots_callback`이 반환하는 목록입니다. `client.send_roots_list_changed()`는 경고를 내며, `mode="legacy"` 클라이언트가 필요합니다. 최신 연결에서는 알림이 조용히 버려집니다. 이후에도 세션을 열어 두세요. 서버의 후속 `roots/list` 요청이 그 세션으로 도착하기 때문입니다. +* `MCPServer`에는 이 알림을 위한 훅이 없습니다. 저수준 `Server`에서는 `on_roots_list_changed=`가 핸들러를 등록합니다(이것도 지원 중단 예정이며 생성 시점에 경고를 냅니다). 알림에는 페이로드가 없으므로 핸들러가 `ctx.session.list_roots()`를 호출해 새 목록을 가져옵니다. + ## 경고 끄기 {#silencing-the-warning} 새 코드에서는 끄지 마세요. @@ -75,15 +124,24 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning) 필터를 반대 방향으로 적용하면 회귀 테스트를 거저 얻습니다. pytest 설정의 `filterwarnings` 항목에 `"error::mcp.MCPDeprecationWarning"`을 추가하면 지원 중단 예정 호출이 경고 대신 예외를 **발생시킵니다**. 여전히 `ctx.info()`를 호출하는 `old_log`라는 - 도구는 더 이상 통과하지 못하고 다음과 같이 보고하기 시작합니다. + 도구는 더 이상 통과하지 못합니다. 호출은 `Error executing tool old_log`와 함께 + `is_error=True`로 돌아오고, 캡처된 서버 로그가 원인을 지목합니다. ```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). ``` pytest 설정 한 줄이면, 지원 중단 예정 호출이 테스트를 실패시키지 않고 코드베이스에 몰래 다시 들어오는 일은 결코 없습니다. +## 지원 중단 예정 SDK 헬퍼 {#deprecated-sdk-helpers} + +이것은 사양 변경이 아니라 더 나은 대체 수단이 있는 SDK 내부 구현일 뿐입니다. 같은 `MCPDeprecationWarning`으로 경고하며 3.0에서 제거됩니다. + +| 지원 중단 예정 | 대신 할 일 | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()`를 호출한 뒤 `FuncMetadata.call_fn()`을 호출하세요. `FuncMetadata`를 직접 다루는 코드(예를 들어 사용자 정의 `Tool` 하위 클래스)만 이 메서드를 호출했습니다. | + ## 요약 {#recap} * 2026-07-28 사양은 **루트**, 서버 주도 **샘플링**, 프로토콜 **로깅**을 지원 중단 예정으로 지정하고(모두 [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), **진행 상황**을 서버에서 클라이언트 방향으로 제한하며, **`ping`**을 제거합니다. @@ -91,6 +149,7 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning) * 지원 중단 예정은 권고 사항입니다. 와이어 변경은 없고, 2026년 이전 세션에서는 모든 것이 계속 동작하며, 눈에 띄는 `MCPDeprecationWarning`이 나옵니다(`UserWarning`이므로 기본적으로 켜져 있습니다). * 샘플링과 루트는 추가로 2026-07-28 세션에는 없는 백채널이 필요합니다. 최신 연결에서는 경고를 낸 뒤 예외를 발생시킵니다. * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)`은 카테고리 전체를 끄고, pytest의 `"error::mcp.MCPDeprecationWarning"`은 이를 테스트 실패로 바꿉니다. +* SDK 헬퍼 하나인 `FuncMetadata.call_fn_with_arg_validation()`은 별도로 지원 중단 예정이며 3.0에서 제거됩니다. * 새 코드는 이 기능 중 어느 것에도 기반해서는 안 됩니다. 이 문서의 다른 모든 페이지는 현재 API를 설명합니다. diff --git a/i18n/ko/pages/get-started/real-host.md b/i18n/ko/pages/get-started/real-host.md index 7eca25370c..ceec2b1645 100644 --- a/i18n/ko/pages/get-started/real-host.md +++ b/i18n/ko/pages/get-started/real-host.md @@ -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 --- # 실제 호스트에 연결하기 {#connect-to-a-real-host} @@ -11,7 +11,7 @@ translation: ## 서버 하나, 모든 호스트 {#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" ``` @@ -50,8 +50,8 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py 그리고 호스트란 MCP 클라이언트를 품은 애플리케이션에 지나지 않으므로, 직접 작성한 Python 코드도 호스트 역할을 할 수 있습니다. **[클라이언트 트랜스포트](../client/transports.md)**에서는 - `stdio_client(...)`로 같은 파일을 서브프로세스로 실행하고, **[테스트](testing.md)**에서는 - 프로세스 없이 인메모리로 연결합니다. + `Client(StdioServerParameters(...))` 호출로 같은 파일을 서브프로세스로 실행하고, + **[테스트](testing.md)**에서는 프로세스 없이 인메모리로 연결합니다. ## Claude Desktop {#claude-desktop} diff --git a/i18n/ko/pages/get-started/testing.md b/i18n/ko/pages/get-started/testing.md index 859ce61e7f..2537f1b054 100644 --- a/i18n/ko/pages/get-started/testing.md +++ b/i18n/ko/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # 테스트 {#testing} @@ -84,9 +84,9 @@ async def test_call_add_tool(client: Client): 잘못될 수 있는 일은 서로 다른 두 가지이고, 이 플래그는 그중 하나에만 관여합니다. **작성한 도구** 안에서 발생한 예외는 프로토콜 실패가 아닙니다. `is_error=True`인 정상적인 결과가 -되고, 모델이 그 메시지를 읽습니다. `raise_exceptions`는 이 점을 바꾸지 않습니다. 플래그가 있든 -없든 `call_tool`은 똑같은 `is_error=True` 결과를 반환합니다. 이 주제를 통째로 다루는 페이지가 -따로 있습니다. **[오류 처리](../servers/handling-errors.md)**를 참고하세요. +됩니다(`ToolError`였다면 모델이 작성한 메시지를 읽습니다). `raise_exceptions`는 이 점을 바꾸지 +않습니다. 플래그가 있든 없든 `call_tool`은 똑같은 `is_error=True` 결과를 반환합니다. 이 주제를 +통째로 다루는 페이지가 따로 있습니다. **[오류 처리](../servers/handling-errors.md)**를 참고하세요. 도구 본문 **바깥**에서 일어난 실패는 다릅니다. `Client(mcp)`가 제공하는 연결에서는 클라이언트가 보기 전에 서버가 이 실패를 일반적인 `"Internal server error"`로 정제합니다. 예상치 못한 크래시의 diff --git a/i18n/ko/pages/handlers/elicitation.md b/i18n/ko/pages/handlers/elicitation.md index 8fa7053142..d8c7e897b3 100644 --- a/i18n/ko/pages/handlers/elicitation.md +++ b/i18n/ko/pages/handlers/elicitation.md @@ -1,6 +1,6 @@ --- translation: - sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] + sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] tool: 1 --- # 엘리시테이션 {#elicitation} @@ -90,6 +90,7 @@ translation: 엘리시테이션 스키마는 도구의 입력 스키마만큼 표현력이 높지 않습니다. 평평한 원시 타입 필드만 가능합니다. `str`, `int`, `float`, `bool`, 또는 문자열 `Literal`(`enum`이 됩니다)입니다. 모델 안에 모델을 넣으면 클라이언트에 아무것도 보내기 전에 `ctx.elicit`이 예외를 일으킵니다. + 도구 호출은 `Error executing tool `으로 실패하고, 그 이유는 서버 로그에 남습니다. ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -112,8 +113,8 @@ translation: !!! tip 답은 코드가 보기 전에 모델을 기준으로 검증됩니다. `bool` 자리에 `"maybe"`를 보내는 클라이언트가 - 예약을 망가뜨리지는 않습니다. 호출은 스키마 불일치 오류로 실패하고, `if` 문은 실행되지 - 않습니다. + 예약을 망가뜨리지는 않습니다. `ctx.elicit`이 `ValueError`를 일으키고, 호출은 실패하며, `if` 문은 + 실행되지 않습니다. ## 사용자를 URL로 보내기 {#send-the-user-to-a-url} diff --git a/i18n/ko/pages/handlers/logging.md b/i18n/ko/pages/handlers/logging.md index 758cabf560..7c6ee08947 100644 --- a/i18n/ko/pages/handlers/logging.md +++ b/i18n/ko/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # 로깅 {#logging} @@ -55,6 +55,8 @@ MCP에는 프로토콜 수준의 **로깅 기능**이 있습니다. 서버가 `C `logging.basicConfig()`는 이미 존재하는 핸들러를 절대 교체하지 않습니다. 서버를 만들기 전에 로깅을 직접 설정했다면 그 설정이 우선합니다. +실패를 기록하려는 목적만으로 모든 핸들러에 `try`/`except`를 넣을 필요도 없습니다. 도구나 리소스 함수가 예외를 일으키면 SDK가 대신 로그로 남깁니다. 무엇이 어떤 레벨로 기록되는지는 **[오류 처리](../servers/handling-errors.md#any-other-exception)**에서 설명합니다. + ## 직접 해 보기 {#try-it} MCP Inspector로 서버를 실행하세요. diff --git a/i18n/ko/pages/run/index.md b/i18n/ko/pages/run/index.md index 5ed10af203..a2a69a98c3 100644 --- a/i18n/ko/pages/run/index.md +++ b/i18n/ko/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # 서버 실행하기 {#running-your-server} @@ -72,7 +72,7 @@ Inspector는 실제 호스트가 하는 일을 그대로 합니다. `server.py` * `streamable_http_path`: MCP 엔드포인트가 위치하는 경로. 기본값은 `/mcp`입니다. * `json_response=True`: 각 POST에 SSE 스트림 대신 단일 JSON 본문으로 응답합니다. 이 본문에는 응답 외에 다른 것을 담을 자리가 없으므로, 요청 도중 클라이언트를 다시 호출하는 도구(`ctx.elicit()`, 샘플링)는 이 구간에서 `NoBackChannelError`를 발생시키고, 진행 중인 호출에 묶인 알림(`ctx.report_progress()`의 진행 상황, 호출별 로그 메시지)은 버려집니다. 독립된 `GET` 스트림은 관련 없는 알림을 여전히 전달합니다. * `stateless_http=True`: 요청마다 새 트랜스포트를 만들고 세션을 추적하지 않습니다. -* `max_request_body_size`: 허용되는 POST 본문의 최대 크기(바이트). 기본값은 4MiB이며, 더 큰 요청은 +* `max_request_body_size`: 허용되는 요청 본문의 최대 크기(바이트). 기본값은 4MiB이며, 더 큰 요청은 파싱이나 세션 생성 전에 HTTP 413을 받습니다. 정상적인 MCP 메시지가 이 크기를 넘을 때만 올리세요. * `event_store`, `retry_interval`, `transport_security`: 재개 가능성과 DNS 리바인딩 보호. localhost가 아닌 곳에 배포하기 전까지는 미뤄도 됩니다. `transport_security`는 **[배포와 확장](deploy.md)**에서 다룹니다. diff --git a/i18n/ko/pages/servers/handling-errors.md b/i18n/ko/pages/servers/handling-errors.md index 00bd4296aa..f773acc99d 100644 --- a/i18n/ko/pages/servers/handling-errors.md +++ b/i18n/ko/pages/servers/handling-errors.md @@ -1,25 +1,25 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # 오류 처리 {#handling-errors} -도구가 실패하는 방식은 두 가지이며, SDK는 이 둘을 매우 다르게 다룹니다. +도구가 실패하는 방식은 세 가지이며, SDK는 각각을 다르게 다룹니다. -일반적인 예외를 발생시키면 **모델**이 보게 됩니다. `MCPError`를 발생시키면 **프로토콜**이 보게 됩니다. +`ToolError`를 발생시키면 **모델**이 메시지를 보게 됩니다. `MCPError`를 발생시키면 **프로토콜**이 보게 됩니다. 그 밖의 것을 발생시키면 크래시로 취급됩니다. 모델은 호출이 실패했다는 사실만 알게 되고, 트레이스백은 로그에 남습니다. -이 페이지는 둘 중 무엇을 선택할지에 관한 내용입니다. +이 페이지는 무엇을 선택할지에 관한 내용입니다. ## 모델이 고칠 수 있는 오류 {#an-error-the-model-can-fix} 무언가를 조회하는 도구를 하나 두고, 조회가 실패하게 해 봅시다. -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -이 두 줄에는 MCP와 관련된 것이 전혀 없습니다. `get_author`는 여느 Python 함수가 그러듯 평범한 `ValueError`를 발생시킵니다. +`mcp.server.mcpserver.exceptions`에 있는 `ToolError`는 도구가 무언가 잘못되었다고 모델에게 알리는 수단입니다. 카탈로그에 없는 제목으로 호출하고 결과를 살펴보세요. @@ -30,13 +30,15 @@ result.structured_content # None ``` * 요청은 **성공했습니다**. 결과가 있고, 호출한 쪽에서는 아무 예외도 발생하지 않았습니다. -* `is_error`가 `True`이고, 예외 메시지(앞에 도구 이름이 붙음)가 `content`에, 즉 모델이 읽는 바로 그 자리에 들어 있습니다. +* `is_error`가 `True`이고, 메시지(앞에 도구 이름이 붙음)가 `content`에, 즉 모델이 읽는 바로 그 자리에 들어 있습니다. * `structured_content`는 `None`입니다. 실패한 호출에는 구조화할 반환 값이 없습니다. -이것이 **도구 오류**이며, 도구가 발생시키는 **모든** 예외의 기본 동작입니다. 그리고 거의 언제나 원하는 동작이기도 합니다. +이것이 **도구 오류**이며, 거의 언제나 원하는 동작입니다. 도구를 호출하는 쪽은 모델입니다. 인자를 고른 것도 모델입니다. 그래서 도구 오류는 대화의 한 차례가 됩니다. 모델은 *"No book titled 'Nothing' in the catalog."*를 읽고, 제목을 잘못 추측했다는 것을 깨닫고, 더 나은 제목으로 다시 호출합니다. `raise` 한 줄을 썼을 뿐인데 스스로 교정하는 에이전트를 얻은 셈입니다. +서버에서 `ToolError`는 로그의 `INFO` 한 줄이며, 트레이스백은 없습니다. 예상한 실패이므로 조사할 것이 없습니다. + !!! tip 도구에서 오류 메시지를 `return`으로 돌려주지 마세요. 반환된 문자열은 `is_error=False`이므로, 모델에게(그리고 모든 클라이언트 UI에게) 도구가 제대로 작동했고 그 문자열이 답인 것처럼 보입니다. @@ -44,7 +46,7 @@ result.structured_content # None ## 모델이 고칠 수 없는 오류 {#an-error-the-model-cannot-fix} -이제 `ValueError`를 `MCPError`로 바꿔 봅시다. +이제 `ToolError`를 `MCPError`로 바꿔 봅시다. ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -77,10 +79,10 @@ result.structured_content # None 두 경로는 서로 다른 두 질문에 답합니다. -* **실행**이 실패했을 때는 **아무 예외나 발생시키세요**. 도구가 하려던 일이 되지 않은 경우입니다. 호출을 선택한 것은 모델이므로, 모델이 그 결과를 보고 회복할 기회를 얻어야 합니다. 철자가 틀린 제목, 시간 초과된 상위 API, 존재하지 않는 행은 모두 도구 오류입니다. +* **실행**이 실패했을 때는 **`ToolError`를 발생시키세요**. 도구가 하려던 일이 되지 않은 경우입니다. 호출을 선택한 것은 모델이므로, 모델이 그 결과를 보고 회복할 기회를 얻어야 합니다. 철자가 틀린 제목, 시간 초과된 상위 API, 존재하지 않는 행은 모두 도구 오류입니다. * **요청 자체**를 거부해야 할 때는 **`MCPError`를 발생시키세요**. 도구가 의존하는 기능이 클라이언트에 없거나, 서버가 누구에게도 응답할 수 있는 상태가 아니거나, 호출한 쪽이 필수 단계를 건너뛴 경우입니다. 모델이 재시도해도 이 중 어느 것도 해결되지 않으므로, 메시지를 모델에게 건네서 얻을 것이 없습니다. -판단 기준은 질문 하나입니다. **더 똑똑한 모델이었다면 이 상황을 피할 수 있었을까?** 예 -> 일반 예외. 아니요 -> `MCPError`. +판단 기준은 질문 하나입니다. **더 똑똑한 모델이었다면 이 상황을 피할 수 있었을까?** 예 -> `ToolError`. 아니요 -> `MCPError`. 이 기준으로 보면 `get_author`의 두 번째 버전은 잘못된 선택을 했습니다. 더 나은 제목이면 해결되므로, 모델이 메시지를 볼 자격이 있었습니다. 그 버전은 메커니즘을 보여 주기 위한 것이지, 권장하기 위한 것이 아닙니다. @@ -89,6 +91,25 @@ result.structured_content # None `data` 페이로드를 받습니다. 여기에 넣은 내용이 그대로 클라이언트가 받는 내용입니다. SDK는 발생한 `MCPError`를 정제하지 않고 그대로 전달합니다. +## 그 밖의 모든 예외 {#any-other-exception} + +이제 검사를 빼고 딕셔너리 조회가 스스로 실패하게 둬 봅시다. + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]` 조회는 `KeyError`를 발생시킵니다. 대비하지 않은 예외이므로 SDK는 이를 크래시로 취급합니다. + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +호출은 여전히 `is_error=True`를 반환하므로, 모델은 실패했다는 것을 알고 다음으로 넘어갈 수 있습니다. 받지 못하는 것은 예외의 텍스트입니다. 작성한 코드에서 나온 `KeyError`나, 세 단계 아래 라이브러리의 드라이버가 쏟아낸 SQL 더미는 서버 내부를 드러낼 수 있으므로, 절대 서버 밖으로 나가지 않습니다. + +대신 그 내용은 로그에서 확인할 수 있습니다. 서버는 크래시를 전체 트레이스백과 함께 `ERROR` 수준으로, `Tool 'get_author' raised an unexpected exception`이라는 메시지로 기록합니다. 따라서 `WARNING` 수준으로 설정한 운영 로그는 모든 `ToolError`에는 조용하다가, 실제로 무언가 고장 난 순간에 알려 줍니다. + ## 존재하지 않는 리소스 {#a-resource-that-doesnt-exist} 리소스도 같은 선을 긋고, 흔한 경우를 위해 이름 붙은 예외를 하나 제공합니다. @@ -109,7 +130,7 @@ result.structured_content # None } ``` -여기에는 `is_error=True`인 절반짜리 결과가 없다는 점에 주목하세요. 리소스 읽기는 내용을 반환하거나 실패하거나 둘 중 하나입니다. 리소스에는 프로토콜 경로만 있습니다. 템플릿을 비롯해 리소스에 관한 나머지 모든 내용은 **[리소스](resources.md)**에서 다룹니다. +여기에는 `is_error=True`인 절반짜리 결과가 없다는 점에 주목하세요. 리소스 읽기는 내용을 반환하거나 실패하거나 둘 중 하나입니다. 리소스에는 프로토콜 경로만 있습니다. `ResourceError`는 "찾을 수 없음"이 아닌 실패를 위한 같은 종류의 예외이며(`-32603`, 작성한 메시지), 둘 다 로그에 `INFO` 한 줄로 남습니다. `MCPError`를 제외한 그 밖의 모든 예외는 크래시입니다. 클라이언트는 URI만 밝히는 `-32603`을 받고, 트레이스백은 `ERROR` 수준으로 로그에 남습니다. 템플릿을 비롯해 리소스에 관한 나머지 모든 내용은 **[리소스](resources.md)**에서 다룹니다. ## 직접 발생시킬 일이 없는 오류 {#errors-you-never-raise} @@ -120,19 +141,21 @@ result.structured_content # None 이는 작성하지 않아도 되는 `raise` 문이 한 부류 통째로 있다는 뜻입니다. 타입 힌트를 직접 다시 검증하지 마세요. !!! info - 이 페이지의 모든 내용은 **클라이언트**가 보는 것이며, 테스트를 작성할 때 쓸 인메모리 `Client`도 - 정확히 같은 것을 봅니다. `raise_exceptions=True`조차 도구 오류를 트레이스백으로 되돌리지 않습니다. + 이 페이지에서 **클라이언트**가 보는 모든 것은, 테스트를 작성할 때 쓸 인메모리 `Client`도 + 똑같이 봅니다. `raise_exceptions=True`조차 실패한 도구의 예외를 호출한 쪽으로 되돌려 주지 않습니다. 그 플래그가 동작할 수 있는 시점에는 예외가 이미 `is_error=True` 결과가 되어 있기 때문입니다. - 결과에 대해 단언하세요. 이 패턴은 **[테스트](../get-started/testing.md)**에서 다룹니다. + 검증은 결과를 대상으로 하세요. 크래시의 트레이스백이 필요하다면 서버 로그에 있으며, pytest의 + `caplog`로 캡처할 수 있습니다. 이 패턴은 **[테스트](../get-started/testing.md)**에서 다룹니다. ## 요약 {#recap} -* 도구에서 **아무 예외**나 발생시키면 -> 호출은 `is_error=True`와 함께 메시지를 `content`에 담아 반환합니다. 모델이 읽고 재시도할 수 있습니다. 이것이 기본 동작입니다. +* 도구에서 **`ToolError`**를 발생시키면 -> 호출은 `is_error=True`와 함께 메시지를 `content`에 담아 반환합니다. 모델이 읽고 재시도할 수 있습니다. * **`MCPError`**를 발생시키면 -> 호출 자체가 JSON-RPC 오류로 실패합니다. 모델은 아무것도 보지 못하고, 호스트가 처리합니다. `code`, `message`, `data`는 그대로 유지됩니다. -* 판단 기준이 되는 질문은 **더 똑똑한 모델이었다면 이 상황을 피할 수 있었을까?**입니다. 예 -> 예외. 아니요 -> `MCPError`. +* 판단 기준이 되는 질문은 **더 똑똑한 모델이었다면 이 상황을 피할 수 있었을까?**입니다. 예 -> `ToolError`. 아니요 -> `MCPError`. +* 그 밖의 **모든 예외**는 크래시입니다 -> 모델에게는 `Error executing tool `만 담긴 `is_error=True`가 가고, 로그에는 트레이스백이 담긴 `ERROR` 레코드가 남습니다. * 리소스 핸들러에서 `ResourceNotFoundError`를 발생시키면 -> 프로토콜의 `-32602`가 되며, URI가 `data`에 담깁니다. * 잘못된 인자는 함수가 실행되기 전에 스키마와 대조해 거부되므로, 이를 위해 `raise`를 쓰지 않습니다. -* `from mcp import MCPError`를 쓰고, 오류 코드 상수는 `mcp.types`에서 가져옵니다. +* 임포트: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, 그리고 오류 코드 상수는 `mcp.types`에서 가져옵니다. 오류 처리까지 마쳤습니다. 이것으로 서버가 **노출하는** 모든 것을 다뤘습니다. 모든 핸들러가 실행 중에 무엇을 읽을 수 있고 클라이언트에게 무엇을 되돌려 할 수 있는지는 다음 절인 **[핸들러 내부](../handlers/index.md)**에서 다룹니다. diff --git a/i18n/ko/pages/servers/media.md b/i18n/ko/pages/servers/media.md index 826b5609d3..938880ef82 100644 --- a/i18n/ko/pages/servers/media.md +++ b/i18n/ko/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # 미디어 {#media} @@ -86,6 +86,24 @@ result.structured_content # None `Audio`를 그렇게 만들면 클라이언트는 `mime_type="audio/wav"`라고 전달받고, 그대로 믿고 디코딩에 실패합니다. `data=`를 전달할 때는 `format=`도 전달하세요. +## 리소스 임베드하기 {#embedding-a-resource} + +도구는 문서도 반환할 수 있습니다. 텍스트나 바이트를 그 문서가 위치한 URI, MIME 타입과 함께 묶은 것입니다. 이것이 또 다른 종류의 콘텐츠 블록인 **`EmbeddedResource`**입니다. 평범한 `str`과 달리 콘텐츠가 무엇인지 클라이언트에게 알려 주므로, 클라이언트는 이를 첨부 파일로 보여 주거나 이미 알고 있는 리소스임을 알아볼 수 있습니다. + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines`는 평범한 리소스입니다(**[리소스](resources.md)**에서 다룹니다). 도구는 요청이 있을 때 같은 문서를 모델에게 건네며, `guidelines()`를 직접 호출하므로 단일 정보 출처가 유지됩니다. +* `EmbeddedResource`와 `TextResourceContents`는 `mcp.types`에서 가져옵니다. 이미지처럼 헬퍼가 있는 것은 아닙니다. 만든 블록은 그대로 결과에 들어가고, `structured_content`는 없습니다. +* 리소스가 등록된 URI를 쓰세요. 그래야 클라이언트가 첨부 파일과 `brand://guidelines`가 같은 문서임을 알 수 있습니다. 등록 여부와 상관없이 어떤 URI든 허용됩니다. + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +바이너리 콘텐츠에는 `TextResourceContents` 대신 `BlobResourceContents(uri=..., mime_type=..., blob=...)`를 쓰고, 바이트를 base64로 인코딩해 `blob`에 넣으세요. 클라이언트가 나중에 `resources/read`로 읽을 수 있는 포인터만 보내려면 대신 `ResourceLink(name=..., uri=...)`를 반환하세요. 이것도 콘텐츠 블록입니다. + ## 아이콘 {#icons} `Icon`은 콘텐츠가 아니라 메타데이터입니다. 이미지를 담지 않고 URI로 이미지를 가리키며, 클라이언트는 이를 가져와 서버 이름, 도구, 리소스, 프롬프트 옆에 표시할 수 있습니다. @@ -115,6 +133,7 @@ client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_ * 도구에서 `Image`나 `Audio`를 반환하면 클라이언트는 `ImageContent` / `AudioContent` 블록을 받습니다. 바이트는 base64로 인코딩되고 MIME 타입이 함께 갑니다. * `path=`로 만들어 확장자가 MIME 타입을 정하게 하거나, 메모리의 `data=`와 명시적인 `format=`으로 만드세요. +* `EmbeddedResource`를 반환하면 문서(텍스트 또는 base64 blob, URI와 MIME 타입 포함)를 결과에 넣을 수 있고, `ResourceLink`를 반환하면 포인터만 보냅니다. * 미디어 결과에는 `structured_content`도 출력 스키마도 없습니다. * `Icon`은 포인터입니다. `src` URI에 선택적인 `mime_type`, `sizes`, `theme`이 더해집니다. * `icons=[...]`는 서버, 도구, 리소스, 프롬프트에서 동작하며, 클라이언트는 대응하는 객체에서 아이콘을 찾습니다. diff --git a/i18n/ko/pages/servers/prompts.md b/i18n/ko/pages/servers/prompts.md index fffb218cd7..eeb828cf22 100644 --- a/i18n/ko/pages/servers/prompts.md +++ b/i18n/ko/pages/servers/prompts.md @@ -1,6 +1,6 @@ --- translation: - sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094] + sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd] tool: 1 --- # 프롬프트 {#prompts} @@ -139,10 +139,55 @@ uv run mcp dev server.py ``` !!! info - **[도구](tools.md)**를 읽었다면 이 페이지의 내용은 이미 모두 알고 있는 셈입니다. 같은 데코레이터, + **[도구](tools.md)**를 읽었다면 여기까지의 내용은 이미 모두 알고 있는 셈입니다. 같은 데코레이터, 설명이 되는 같은 docstring, 같은 `Annotated`/`Field`입니다. 달라지는 것은 누가 실행하는지(사용자)와 결과가 어디로 가는지(대화 속으로)뿐입니다. +## 텍스트 그 이상 {#more-than-text} + +`UserMessage`와 `AssistantMessage`는 `str`을 받는 자리라면 어디든 콘텐츠 블록이나 `Image` / `Audio` 헬퍼도 받습니다. 프롬프트에서 자주 나오는 경우는 두 가지입니다. 문서를 첨부하는 경우와 그림을 첨부하는 경우입니다. + +### 파일 임베딩 {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* 스타일 가이드는 `style://python`에 있는 리소스이며(**[리소스](resources.md)**에서 다룹니다), `server.py` 옆의 `style-guide.md`에서 읽어 옵니다. 아무 Markdown 파일이나 그 자리에 두세요. +* `EmbeddedResource(resource=TextResourceContents(...))`(둘 다 `mcp.types`에 있습니다)는 URI와 MIME 타입과 함께 파일을 첫 번째 메시지로 담고, 이 파일을 참조하는 요청이 일반 텍스트로 뒤따릅니다. +* 가이드를 f-string에 붙여 넣는 대신 임베딩하면 클라이언트가 첨부 파일로 보여 주고 나중에 `style://python`을 다시 열 수 있으며, 모델은 파일을 원문 그대로 받습니다. 바이너리 파일에는 base64 `blob`을 담은 `BlobResourceContents`를 사용하세요. + +렌더링하면 첫 번째 메시지의 `content`는 `resource` 블록입니다. + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### 이미지 첨부 {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image`는 **[이미지, 오디오, 아이콘](media.md)**의 헬퍼입니다. 프롬프트가 렌더링될 때 `UserMessage`가 이를 `ImageContent` 블록(파일은 base64로 인코딩되고, MIME 타입은 `.png`에서 추측)으로 변환합니다. `Audio`도 같은 방식으로 `AudioContent`가 됩니다. +* `architecture.png`라는 이름의 PNG를 아무거나 `server.py` 옆에 두세요. 프롬프트 인수는 문자열이므로 그림은 항상 서버에서 나옵니다. `component`는 문구만 제공합니다. + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## 런타임에 목록 바꾸기 {#changing-the-list-at-runtime} + +클라이언트가 연결된 상태에서도 프롬프트를 추가할 수 있습니다. 예를 들어 사용자가 지시 사항을 자신만의 메뉴 항목으로 저장하게 할 수 있습니다. 프롬프트를 등록한 다음 알림을 보내세요. + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))`는 `@mcp.prompt()`와 똑같이 함수를 등록하고, `mcp.remove_prompt(name)`은 그 반대입니다. `add_prompt`는 같은 이름의 기존 항목을 덮어쓰지 않고 유지하므로, 저장이 교체가 되도록 이 도구는 먼저 이전 항목을 제거합니다. `prompts/list`에는 변경 사항이 즉시 반영됩니다. +* `await ctx.notify_prompts_changed()`는 `subscriptions/listen` 스트림을 듣고 있는 모든 `2026-07-28` 클라이언트에게 `notifications/prompts/list_changed`를 보냅니다(**[구독](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()`는 호출한 클라이언트가 2026 이전 버전일 때 그 클라이언트에게 보냅니다(**[레거시 클라이언트 지원](../run/legacy-clients.md)**). 둘 다 호출하세요. 알릴 대상이 없으면 각각 아무 일도 하지 않습니다. +* 알림을 받은 클라이언트는 `prompts/list`를 다시 호출합니다. Python `Client`에서는 `async with client.listen(prompts_list_changed=True) as sub:`이며, `PromptsListChanged` 이벤트를 내놓습니다. + ## 요약 {#recap} * 함수에 `@mcp.prompt()`를 붙이면 프롬프트가 됩니다. 이름은 함수에서, 설명은 docstring에서 옵니다. @@ -151,5 +196,7 @@ uv run mcp dev server.py * `str`을 반환하면 사용자 메시지 하나가 됩니다. `UserMessage` / `AssistantMessage`의 목록을 반환하면 여러 턴의 대화 시작점을 마련할 수 있습니다. * `title=`과 `Field(description=...)`은 클라이언트가 UI에 표시하는 내용입니다. * 필수 인수가 빠지면 요청 전체가 실패합니다. 프롬프트별 오류 결과는 없습니다. +* `EmbeddedResource`나 `Image`를 `UserMessage`로 감싸면 문서나 그림을 첨부할 수 있습니다. +* 런타임에 프롬프트를 추가하거나 제거하려면 `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`를 쓰고, 이어서 `await ctx.notify_prompts_changed()`와 `await ctx.session.send_prompt_list_changed()`를 호출하세요. 프롬프트(또는 리소스 템플릿) 인수의 서버 측 자동 완성은 **[자동 완성](completions.md)**에서 다룹니다. diff --git a/i18n/ko/pages/servers/structured-output.md b/i18n/ko/pages/servers/structured-output.md index f94b0d8870..6884e2e585 100644 --- a/i18n/ko/pages/servers/structured-output.md +++ b/i18n/ko/pages/servers/structured-output.md @@ -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 --- # 구조화된 출력 {#structured-output} @@ -103,7 +103,7 @@ result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions --8<-- "docs_src/structured_output/tutorial003.py" ``` -`TypedDict`는 런타임에 평범한 `dict`이므로, 바로 그 형태로 만들어서 반환하면 됩니다. 스키마와 검증, `structured_content`는 `BaseModel` 버전과 똑같습니다(설명은 빠지는데, `TypedDict`에는 설명을 둘 자리가 없기 때문입니다). +`TypedDict`는 런타임에 평범한 `dict`이므로, 바로 그 형태로 만들어서 반환하면 됩니다. 스키마와 검증, `structured_content`는 `BaseModel` 버전과 같은 규칙을 따릅니다. 클래스 독스트링이나 `Annotated[..., Field(description=...)]` 표기를 추가하면 그 내용이 설명이 되고, 딕셔너리에서 빼 둔 `NotRequired` 키는 `structured_content`에서도 빠집니다. ## 데이터클래스 {#a-dataclass} @@ -185,16 +185,16 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} 어노테이션은 `WeatherData`를 약속합니다. 그런데 업스트림 응답이 더 이상 `humidity`를 보내지 않습니다. !!! check - `get_weather`를 호출해도 반쯤 빈 객체를 클라이언트에 슬그머니 넘기지 않습니다. 호출은 실패하고, 오류의 첫 몇 줄이 문제의 필드를 지목합니다. + `get_weather`를 호출해도 반쯤 빈 객체를 클라이언트에 슬그머니 넘기지 않습니다. 호출은 실패합니다. 클라이언트는 `is_error=True`와 함께 `Error executing tool get_weather`를 받으므로, 모델은 있지도 않은 날씨를 자신 있게 읽어 내는 대신 호출이 실패했다는 사실을 알게 됩니다. 필드 이름은 개발자를 위한 것으로, 서버 로그에 `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] ``` - 이 텍스트는 `is_error=True` 상태의 도구 결과로 돌아오므로, 모델은 있지도 않은 날씨를 자신 있게 읽어 내는 대신 호출이 실패했다는 사실을 알게 됩니다. - 참고로 `-> WeatherData` 도구에서 평범한 `dict`를 반환해도 괜찮습니다. 위 예제에서 `json.loads`가 만들어 낸 결과가 바로 평범한 딕셔너리였습니다. 검증 대상은 Python 타입이 아니라 값입니다. ## 구조화된 출력 끄기 {#opting-out} @@ -209,6 +209,10 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} 반대로 `structured_output=True` 옵션은 자동 감지를 필수 요건으로 바꿉니다. 반환 타입으로 스키마를 만들 수 없는 도구는 텍스트로 물러나는 대신 임포트 시점에 예외를 일으킵니다. +## 콘텐츠 블록과 미디어 {#content-blocks-and-media} + +콘텐츠 블록과 미디어(`TextContent`, `EmbeddedResource`, `Image`, `Audio` 등이 단독으로 쓰이거나, `list`, `tuple`, `Sequence`의 항목으로 쓰이거나, 유니언의 갈래로 쓰이는 경우)는 구조화된 출력이 자동으로 꺼집니다. 모델이 읽기 위한 것이므로 자동 감지가 여기서는 스키마를 만들지 않습니다(`Image`와 `Audio`는 **[이미지, 오디오, 아이콘](media.md)**에서 다룹니다). 다만 `structured_output=True` 옵션을 주면 콘텐츠 블록 클래스에 대해서는 여전히 스키마를 강제로 만듭니다. + ## 타입 힌트가 없는 클래스 {#a-class-without-type-hints} 요청하지 않았는데도 구조화되지 않은 결과로 끝나는 길이 하나 있습니다. **본문에 어노테이션이 전혀 없는** 클래스를 반환하는 경우입니다. @@ -237,6 +241,6 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} * 스칼라, 리스트, 튜플, 유니언은 `{"result": ...}` 형태로 감싸집니다. 모델, `TypedDict`, 데이터클래스, 어노테이션이 달린 클래스, 그리고 `dict[str, ...]` 타입은 이미 객체이므로 그대로 유지됩니다. * 모든 결과에는 `content`(모델을 위한 텍스트)와 `structured_content`(애플리케이션을 위한 데이터)가 **함께** 담깁니다. * 반환한 값은 스키마에 맞춰 검증됩니다. 어긋나면 손상된 결과가 아니라 도구 오류가 됩니다. -* `structured_output=False` 옵션으로 도구의 구조화된 출력을 끌 수 있습니다. 타입 힌트가 없는 클래스는 아무 경고 없이 꺼지므로 주의하세요. +* `structured_output=False` 옵션으로 도구의 구조화된 출력을 끌 수 있습니다. 콘텐츠 블록, `Image`, `Audio`는 기본적으로 꺼집니다. 타입 힌트가 없는 클래스는 아무 경고 없이 꺼지므로 주의하세요. 이제 도구가 돌려줄 수 있는 모든 것을 손에 넣었습니다. 다음은 두 번째 프리미티브인 **[리소스](resources.md)**입니다. diff --git a/i18n/ko/pages/servers/tools.md b/i18n/ko/pages/servers/tools.md index e1453d0304..a9bbe8de0e 100644 --- a/i18n/ko/pages/servers/tools.md +++ b/i18n/ko/pages/servers/tools.md @@ -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} @@ -39,6 +39,8 @@ SDK는 이 타입 힌트로부터 JSON Schema를 생성해 `tools/list` 과정 두 인자 모두 기본값이 없으므로 `required`에 들어 있습니다. 이 부분은 곧 고칩니다. (`title` 키는 Pydantic이 만들어 내는 부산물입니다. 계약에 해당하는 것은 속성과 그 타입, 그리고 `required`입니다.) +`$schema` 키도 없습니다. MCP는 이 키가 없는 스키마를 **JSON Schema 2020-12**로 취급하는데, Pydantic이 생성하는 것이 바로 이 형식이므로 **[저수준 Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**에서 스키마를 직접 손으로 작성하기 전까지는 따로 고를 것이 없습니다. + !!! tip 여기서 타입 힌트는 문서가 아닙니다. 타입 힌트가 바로 **계약**입니다. 클라이언트가 `"limit": "ten"`을 보내면 함수가 실행되기도 전에 SDK가 거부합니다. diff --git a/i18n/ko/pages/servers/uri-templates.md b/i18n/ko/pages/servers/uri-templates.md index 730ee28bc0..e5ee1d9868 100644 --- a/i18n/ko/pages/servers/uri-templates.md +++ b/i18n/ko/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # URI 템플릿과 경로 안전성 {#uri-templates-and-path-safety} @@ -98,7 +98,7 @@ translation: 내장 검사는 흔한 경우를 막아 주지만 샌드박스 경계까지는 알 수 없습니다. 파일시스템에 접근할 때는 `safe_join`으로 경로를 해석하고 기준 디렉터리 안에 머무르는지 확인하세요. -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` @@ -127,7 +127,7 @@ translation: 이 검사는 휴리스틱 사전 필터입니다. 파일시스템 접근에서는 `safe_join`이 여전히 격리 경계입니다. !!! tip - 핸들러가 요청을 처리할 수 없다면(파일이 없거나, id를 알 수 없는 경우) 예외를 발생시키세요. SDK가 이를 오류 응답으로 바꿉니다. 프로토콜 오류와 도구 오류의 차이는 **[오류 처리](handling-errors.md)**에서 확인하세요. + 핸들러가 요청을 처리할 수 없다면(파일이 없거나, id를 알 수 없는 경우) 위의 `read_manual`처럼 `ResourceNotFoundError`를 발생시키세요. 클라이언트는 작성한 메시지와 URI가 담긴 `-32602` 오류를 받습니다. 예상치 못한 예외는 대신 일반적인 `-32603` 오류가 됩니다. **[오류 처리](handling-errors.md#a-resource-that-doesnt-exist)**를 참고하세요. ## 저수준 Server의 리소스 {#resources-on-the-low-level-server} diff --git a/i18n/ko/pages/troubleshooting.md b/i18n/ko/pages/troubleshooting.md index f1f815cd91..caaf97c46d 100644 --- a/i18n/ko/pages/troubleshooting.md +++ b/i18n/ko/pages/troubleshooting.md @@ -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 --- # 문제 해결 {#troubleshooting} @@ -81,11 +81,11 @@ async def main() -> None: 연결을 끊는 일은 `__aexit__`에서 일어나므로, 잊어버릴 `client.close()` 같은 것은 없습니다. **[테스트](get-started/testing.md)**는 바로 이 패턴 위에 만들어져 있습니다. -## `Error executing tool : ` 및 `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `, `Error executing tool `, `Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} 지금 보고 있는 것은 예외가 아니라 **결과**입니다. `call_tool`은 예외를 일으키지 않았고, 실패한 도구에 대해서는 앞으로도 절대 일으키지 않습니다. -서버가 모르는 도시로 `forecast`를 호출하면, 도구가 일으킨 예외는 **성공**으로 표시된 요청에 담겨 돌아옵니다. +서버가 모르는 도시로 `forecast`를 호출하면, 도구가 일으킨 `ToolError`는 **성공**으로 표시된 요청에 담겨 돌아옵니다. ```python result.is_error # True @@ -97,6 +97,8 @@ result.structured_content # None 해결책은 클라이언트 쪽에 있습니다. **`result.is_error`를 확인하세요.** `call_tool`을 `try/except`로 감싸도 잡히는 것은 하나도 없습니다. 잡을 것이 없기 때문입니다. 이것은 의도된 설계이며, 이 페이지에서 가장 체득할 가치가 있는 한 가지입니다. 호출을 선택한 것은 **모델**이므로, 메시지를 받고 다시 시도할 기회를 얻는 것도 모델입니다. 예외를 **실제로** 일으키는 `MCPError` 경로를 포함해 자세한 내용은 **[오류 처리](servers/handling-errors.md)**에서 확인하세요. +메시지 없이 `Error executing tool `만 나오는 형태는 도구가 **죽었다**는 뜻입니다. 도구가 예상하지 못한 예외가 빠져나갔고(또는 반환값이 출력 스키마를 통과하지 못했고), 그 예외의 텍스트는 와이어에 실리지 않습니다. 트레이스백은 **서버 로그**에 `ERROR` 수준으로, `Tool '' 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} `@mcp.tool()` 대신 `@mcp.tool`을 쓴 경우입니다. `tool()`은 데코레이터 **팩토리**이므로, 괄호가 없으면 Python은 함수를 `name=` 매개변수에 넘겨 버립니다. @@ -409,7 +411,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## 요약 {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup`은 절대 진짜 오류가 아닙니다. **마지막 줄**을 읽으세요. `async with Client(...)` 블록 **안에서** `MCPError`를 잡으면 감싸기를 완전히 건너뜁니다. -* `call_tool`은 실패한 도구에 대해 예외를 일으키지 않습니다. `Error executing tool ...` 및 `Unknown tool: ...` 메시지는 결과이므로 `result.is_error`를 확인하세요. +* `call_tool`은 실패한 도구에 대해 예외를 일으키지 않습니다. `Error executing tool ...` 및 `Unknown tool: ...` 메시지는 결과이므로 `result.is_error`를 확인하세요. 도구 이름 뒤에 메시지가 없으면 도구가 죽은 것이며, 트레이스백은 서버 로그에 있습니다. * `Client must be used within an async context manager` -> `async with`를 사용하세요. `Use @tool() instead of @tool` -> 괄호를 추가하세요. * 서버 로그의 `Tool already exists:`는 이름이 같은 두 도구가 하나로 합쳐졌다는 유일한 신호입니다. * 421 하나에 표기는 세 가지입니다. `Server returned an error response`(Python `Client`), `421 Misdirected Request` / `Invalid Host header`(그 밖의 모든 곳), `Invalid Host header: `(서버 로그). 해결책은 `transport_security=TransportSecuritySettings(allowed_hosts=[...])`입니다. diff --git a/i18n/ko/pages/whats-new.md b/i18n/ko/pages/whats-new.md index 616d160dfd..84077c3ff6 100644 --- a/i18n/ko/pages/whats-new.md +++ b/i18n/ko/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # v2에서 달라진 점 {#whats-new-in-v2} @@ -46,9 +46,9 @@ v1은 세 겹으로 중첩된 계층을 건네주었습니다. 원시 스트림 --8<-- "docs_src/client/tutorial001.py" ``` -`Client`는 서버 객체(트랜스포트 없이 인메모리로 동작하며, 테스트에 쓰는 방식입니다), URL(Streamable HTTP), 또는 `stdio_client(...)` 같은 임의의 트랜스포트 컨텍스트 매니저를 받습니다. `async with`에 진입하면 서버가 어느 시대의 프로토콜을 말하든 연결을 맺고 프로토콜 버전을 협상합니다. 그 뒤에는 `client.server_capabilities`와 `client.protocol_version`이 그냥 준비되어 있고, 서버가 자신을 식별하는 경우에는 `client.server_info`도 마찬가지입니다(2026 시대에는 식별 정보가 선택 사항이므로 이제 타입은 `Implementation | None`입니다). v1에서 등록한 샘플링 및 엘리시테이션 콜백은 여전히 동작하며(콜백 본문에는 이 페이지의 다른 모든 것과 마찬가지로 snake_case 속성 이름 변경이 적용됩니다), 이제 2026 방식의 결과 속 요청(아래 참고)에도 응답하고, 한 번에 하나씩이 아니라 동시에 실행됩니다. 저수준 인터페이스를 원하는 경우를 위해 `ClientSession`은 여전히 그 아래에 있으며 `client.session`으로 얻을 수 있습니다. 다만 이 클래스 역시 바뀌었으므로(새 디스패처 엔진 위에서 실행되고, 자체 시그니처 일부도 변경되었습니다) 아래 계층으로 내려가기 전에 **[마이그레이션 가이드](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**를 읽어 보세요. +`Client`는 서버 객체(트랜스포트 없이 인메모리로 동작하며, 테스트에 쓰는 방식입니다), URL(Streamable HTTP), `StdioServerParameters`(stdio 하위 프로세스), 또는 `sse_client(...)` 같은 그 밖의 임의의 트랜스포트 컨텍스트 매니저를 받습니다. `async with`에 진입하면 서버가 어느 시대의 프로토콜을 말하든 연결을 맺고 프로토콜 버전을 협상합니다. 그 뒤에는 `client.server_capabilities`와 `client.protocol_version`이 그냥 준비되어 있고, 서버가 자신을 식별하는 경우에는 `client.server_info`도 마찬가지입니다(2026 시대에는 식별 정보가 선택 사항이므로 이제 타입은 `Implementation | None`입니다). v1에서 등록한 샘플링 및 엘리시테이션 콜백은 여전히 동작하며(콜백 본문에는 이 페이지의 다른 모든 것과 마찬가지로 snake_case 속성 이름 변경이 적용됩니다), 이제 2026 방식의 결과 속 요청(아래 참고)에도 응답하고, 한 번에 하나씩이 아니라 동시에 실행됩니다. 저수준 인터페이스를 원하는 경우를 위해 `ClientSession`은 여전히 그 아래에 있으며 `client.session`으로 얻을 수 있습니다. 다만 이 클래스 역시 바뀌었으므로(새 디스패처 엔진 위에서 실행되고, 자체 시그니처 일부도 변경되었습니다) 아래 계층으로 내려가기 전에 **[마이그레이션 가이드](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**를 읽어 보세요. -**[클라이언트](client/index.md)**에서 소개하고, **[클라이언트 트랜스포트](client/transports.md)**에서 세 가지 연결 형태를, **[클라이언트 콜백](client/callbacks.md)**에서 콜백 자체를 다루며, **[테스트](get-started/testing.md)**에서는 v1의 `create_connected_server_and_client_session()` 헬퍼를 대체하는 인메모리 패턴을 보여 줍니다. +**[클라이언트](client/index.md)**에서 소개하고, **[클라이언트 트랜스포트](client/transports.md)**에서 네 가지 연결 형태를, **[클라이언트 콜백](client/callbacks.md)**에서 콜백 자체를 다루며, **[테스트](get-started/testing.md)**에서는 v1의 `create_connected_server_and_client_session()` 헬퍼를 대체하는 인메모리 패턴을 보여 줍니다. ### 저수준 `Server`: 이름 변경이 아닌 재구축 {#the-low-level-server-was-rebuilt-not-renamed} @@ -134,7 +134,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB 이름 변경은 스스로 존재를 알립니다. 다음 항목은 그렇지 않습니다. * **동기 함수는 워커 스레드에서 실행됩니다.** `def` 도구(또는 리소스, 프롬프트, 리졸버)는 더 이상 이벤트 루프를 막지 않습니다. 그 대가로 본문이 더 이상 이벤트 루프 스레드 **위에서** 실행되지 않으며, 이는 특정 스레드에서 실행되어야 하는 코드에는 중요한 차이입니다. `async def` 핸들러는 영향이 없습니다. **[마이그레이션 가이드](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**를 참고하세요. -* **도구 안에서 발생시킨 `MCPError`(v1의 `McpError`)는 이제 프로토콜 오류입니다.** 모델은 이 오류를 보지 못합니다. 그 밖의 모든 예외는 여전히 모델이 읽고 반응할 수 있는 `is_error=True` 결과가 됩니다. 이 구분은 **[오류 처리](servers/handling-errors.md)**에서 다룹니다. +* **도구 안에서 발생시킨 `MCPError`(v1의 `McpError`)는 이제 프로토콜 오류입니다.** 모델은 이 오류를 보지 못합니다. 그 밖의 모든 예외는 여전히 `is_error=True` 결과가 되지만, 모델에 전달되는 메시지는 `ToolError`의 메시지뿐입니다. 다른 예외는 이제 `Error executing tool `으로 표시되고, 트레이스백은 서버 로그에 남습니다. 이 구분은 **[오류 처리](servers/handling-errors.md)**에서 다룹니다. * **결과는 나가기 전에 검증됩니다.** `input_schema`가 `{}`인 손수 만든 `Tool`은 이제 `tools/list`에서 실패합니다(사양은 `"type": "object"`를 요구합니다). `@mcp.tool()`로 만든 서버는 이 문제를 겪지 않습니다. 스키마를 SDK가 작성하기 때문입니다. * **클라이언트는 받은 것을 검증합니다.** `list_tools()`와 `call_tool()`은 서버의 응답을 협상한 프로토콜 버전에 맞춰 검사하므로, v1의 관대한 파싱이 눈감아 주던 완전히 유효하지는 않은 서버는 이제 `pydantic.ValidationError`를 발생시킵니다. 직접 제어하지 않는 서버에 연결한다면 그런 서버를 가장 먼저 발견하는 쪽이 될 것을 예상하세요. 자세한 내용은 **[마이그레이션 가이드](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)**에 있습니다. * **URI 템플릿은 이제 진짜 RFC 6570입니다.** `{+path}`, `{?query}` 등이 동작하고, 매칭은 정규식처럼 느슨한 것이 아니라 정확하며, 추출된 값의 경로 탐색(path traversal)은 기본적으로 거부됩니다. 더 엄격해진 템플릿은 첫 요청 때가 아니라 데코레이터를 적용하는 시점에 실패합니다. **[URI 템플릿](servers/uri-templates.md)**을 참고하세요. diff --git a/i18n/pt/pages/advanced/low-level-server.md b/i18n/pt/pages/advanced/low-level-server.md index 4121d274b2..9c9cd540ac 100644 --- a/i18n/pt/pages/advanced/low-level-server.md +++ b/i18n/pt/pages/advanced/low-level-server.md @@ -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 --- # O Server de baixo nível {#the-low-level-server} @@ -116,6 +116,17 @@ O bloco `_meta` é o carimbo de identidade do servidor: o SDK o adiciona a todo O servidor nunca compara os dois campos. O `Client` deste SDK compara: retorne um `structured_content` que não satisfaz o `output_schema` que você declarou e `call_tool` levanta um `RuntimeError` que começa com `Invalid structured content returned by tool search_books` e segue citando a falha do `jsonschema`. Prometer um schema é barato; cumprir a promessa é com você. A escada inteira de tipos de retorno e schemas está em **[Saída estruturada](../servers/structured-output.md)**. +## O dialeto é JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12} + +`input_schema` e `output_schema` são JSON Schema, e a [especificação do MCP](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) fixa o dialeto: um schema sem a chave `$schema` é **JSON Schema 2020-12**. Os schemas que o `MCPServer` gera dependem desse padrão (o Pydantic escreve 2020-12 e omite a chave), e um dict escrito à mão também é cobrado por ele, então o vocabulário completo de 2020-12 está disponível: + +```python title="server.py" hl_lines="8 14-15" +--8<-- "docs_src/lowlevel/tutorial007.py" +``` + +* A raiz do `input_schema` precisa ser `"type": "object"`. Ao lado dela, `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, `$defs` com `$ref`s locais e o resto das palavras-chave de 2020-12 chegam ao cliente exatamente como foram escritas. +* Nenhuma chave `$schema` é necessária. Adicione uma só para optar por um draft mais antigo: o `Client` deste SDK, que valida o `structured_content` contra o `output_schema` de uma ferramenta, escolhe o validador a partir de `$schema` e usa 2020-12 quando não há nenhuma. + ## `_meta`: para a aplicação, não para o modelo {#\_meta-for-the-application-not-the-model} `content` é a parte da resposta que o modelo lê. `structured_content` é a mesma resposta como dados tipados. `_meta` é o terceiro canal: dados que viajam junto com o resultado para a **aplicação cliente**, sem fazer parte da resposta de forma alguma. @@ -167,7 +178,7 @@ O construtor cobre os métodos que o MCP define. `add_request_handler` cobre tod --8<-- "docs_src/lowlevel/tutorial006.py" ``` -* O primeiro argumento é a string do método. Notificações têm um irmão gêmeo, `add_notification_handler`. +* O primeiro argumento é a string do método. Notificações têm um irmão gêmeo, `add_notification_handler`. Os handlers dele disparam em stdio e em conexões HTTP da era do handshake; no caminho streamable-HTTP de `2026-07-28`, o POST de notificação de um cliente é confirmado com `202` e não é despachado, porque essa revisão não define notificações de cliente para servidor sobre HTTP. * `params_type` é o modelo contra o qual os `params` recebidos são validados **antes** de o seu handler executar, então métodos personalizados *recebem* a validação que as ferramentas não recebem. Faça subclasse de `RequestParams` para que o campo `_meta` seja parseado como o de qualquer outro método. * O handler retorna um `BaseModel`, um `dict` ou `None`. O SDK serializa isso no resultado JSON-RPC. diff --git a/i18n/pt/pages/advanced/middleware.md b/i18n/pt/pages/advanced/middleware.md index fe7a500d02..e7917869d7 100644 --- a/i18n/pt/pages/advanced/middleware.md +++ b/i18n/pt/pages/advanced/middleware.md @@ -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 @@ cliente enviou para estabelecer a conexão, antes de você pedir qualquer coisa. * O estabelecimento da conexão: `server/discover`, ou `initialize` e `notifications/initialized` em uma sessão legada. -* Toda requisição e toda notificação. Para uma notificação, `ctx.request_id is None`, - `call_next(ctx)` retorna `None` e o que quer que você retorne é descartado. +* Toda requisição e toda notificação que chega ao servidor. Para uma notificação, + `ctx.request_id is None`, `call_next(ctx)` retorna `None` e o que quer que você retorne é + descartado. (No caminho Streamable HTTP de `2026-07-28`, o POST de notificação de um cliente + recebe a confirmação `202` no transporte e nunca é despachado, então também não chega ao + middleware; essa revisão não define nenhuma notificação do cliente para o servidor sobre HTTP.) * Até um método para o qual o servidor não tem handler: `call_next` lança o `MCPError(-32601, "Method not found")` *através* do seu middleware a caminho do cliente. @@ -111,8 +114,8 @@ nele. Ele é um no-op até você instalar um exportador, e tem a própria págin * Um middleware é `async (ctx, call_next) -> result`, passado como `MCPServer(middleware=[...])` (ou adicionado a `mcp.middleware`) e adicionado a `server.middleware` no `Server` de baixo nível. -* Ele envolve **toda** mensagem de entrada (`server/discover`, `initialize`, requisições, - notificações, métodos desconhecidos) e executa do mais externo para o mais interno. +* Ele envolve **toda** mensagem de entrada que chega ao servidor (`server/discover`, `initialize`, + requisições, notificações, métodos desconhecidos) e executa do mais externo para o mais interno. * `ctx.request_id is None` é como você distingue uma notificação de uma requisição. * Lance uma exceção em vez de chamar `call_next` para recusar uma mensagem; a conexão sobrevive. * O tracing do OpenTelemetry do próprio SDK também é um middleware, já na lista. Veja diff --git a/i18n/pt/pages/client/index.md b/i18n/pt/pages/client/index.md index 4089d397fe..abffbbdee7 100644 --- a/i18n/pt/pages/client/index.md +++ b/i18n/pt/pages/client/index.md @@ -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 --- # O cliente {#the-client} @@ -27,9 +27,10 @@ O servidor no topo só está ali para você ter algo a que se conectar. O client * Uma instância de `MCPServer` (ou do `Server` de baixo nível): conectada **no mesmo processo**. * Uma string de URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, o caminho de produção. -* Um **transporte**: qualquer coisa com que você possa fazer `async with ... as (read, write)`, como `stdio_client(...)` encapsulando um subprocesso. +* Um `StdioServerParameters`: o comando a iniciar como **subprocesso**, com o qual se conversa pelo stdin e stdout dele. +* Um **transporte**: qualquer coisa com que você possa fazer `async with ... as (read, write)`, como `streamable_http_client(url, http_client=...)` em volta do seu próprio cliente HTTP. -Todo o resto desta página é idêntico entre os três. Cabeçalhos, subprocessos, timeouts e o protocolo `Transport` têm sua própria página: **[Transportes do cliente](transports.md)**. +Todo o resto desta página é idêntico entre os quatro. Cabeçalhos, subprocessos, timeouts e o protocolo `Transport` têm sua própria página: **[Transportes do cliente](transports.md)**. ### O que há em um cliente conectado {#whats-on-a-connected-client} @@ -85,7 +86,7 @@ Esse schema é tudo o que uma UI precisa para renderizar um formulário de argum `call_tool(name, arguments)` executa a ferramenta e devolve um `CallToolResult`. -```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 @@ Uma ferramenta que lança uma exceção **não** lança no seu cliente. Ela volt !!! check Peça `"Solaris"` ao `lookup_book` (um título que não está no catálogo) e a função lança - `ValueError`. A chamada ainda retorna normalmente: + `ToolError`. A chamada ainda retorna normalmente: ```python result.is_error # True @@ -125,9 +126,10 @@ Uma ferramenta que lança uma exceção **não** lança no seu cliente. Ela volt result.structured_content # None ``` - A mensagem da exceção foi parar em `content`, onde o **modelo** pode lê-la e tentar de novo. Isso - é proposital: um erro de ferramenta faz parte da conversa, não é um crash. Sempre olhe `is_error` - antes de confiar em `structured_content`. + A mensagem do `ToolError` foi parar em `content`, onde o **modelo** pode lê-la e tentar de novo. Isso + é proposital: um erro de ferramenta faz parte da conversa, não é um crash. (Se a ferramenta tivesse + quebrado com alguma outra exceção, `content` diria apenas `Error executing tool lookup_book`.) Sempre + olhe `is_error` antes de confiar em `structured_content`. !!! warning `is_error=True` cobre mais do que o seu próprio `raise`. Peça uma ferramenta que o servidor nem tem diff --git a/i18n/pt/pages/client/transports.md b/i18n/pt/pages/client/transports.md index 877eafd303..31504bac38 100644 --- a/i18n/pt/pages/client/transports.md +++ b/i18n/pt/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # Transportes do cliente {#client-transports} @@ -87,15 +87,15 @@ sem um repositório de CAs do sistema utilizável (alguns contêineres mínimos) Um servidor **stdio** é um subprocesso. O cliente o inicia, escreve JSON-RPC no stdin dele e lê JSON-RPC do stdout dele. É assim que um host de desktop executa um servidor na sua máquina: um host *é* este código mais uma interface, e **[Conecte a um host real](../get-started/real-host.md)** é a mesma relação vista do lado do host, como um arquivo de configuração. -Descreva o processo com `StdioServerParameters`, transforme-o em um transporte com `stdio_client` e entregue *isso* ao `Client`: +Descreva o processo com `StdioServerParameters` e entregue-o ao `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` não aceita o objeto de parâmetros sozinho. `StdioServerParameters` é configuração; `stdio_client(server)` é o transporte que sabe como iniciar um processo a partir dela. Sempre envolva. +Entrar no bloco inicia o processo. Sair dele encerra o subprocesso: fecha o stdin, espera e mata o processo se ele demorar. Você nunca limpa isso por conta própria. -Sair do bloco `async with` também encerra o subprocesso: fecha o stdin, espera e mata o processo se ele demorar. Você nunca limpa isso por conta própria. +O stderr do processo filho vai para o seu. Para mandá-lo para outro lugar, construa o transporte você mesmo com `stdio_client` (de `mcp`) e passe isso no lugar: `Client(stdio_client(server, errlog=log_file))`. !!! warning O processo filho **não** herda o seu ambiente. Ele recebe uma allow-list mínima (`HOME`, `LOGNAME`, @@ -113,16 +113,16 @@ Sair do bloco `async with` também encerra o subprocesso: fecha o stdin, espera Para o `Client`, tudo acima é a mesma coisa. -Um **transporte** é qualquer gerenciador de contexto assíncrono que produz um par `(read, write)` de streams de mensagens: formalmente, o protocolo `Transport` em `mcp.client`. `Client` resolve seu argumento pelo tipo: um objeto de servidor conecta no próprio processo, uma `str` vira `streamable_http_client(url)` e qualquer outra coisa é aberta diretamente como transporte. É por causa dessa última regra que `stdio_client(...)`, `streamable_http_client(...)` e `sse_client(...)` se encaixam todos no mesmo lugar, e que você pode escrever o seu próprio. +Um **transporte** é qualquer gerenciador de contexto assíncrono que produz um par `(read, write)` de streams de mensagens: formalmente, o protocolo `Transport` em `mcp.client`. `Client` resolve seu argumento pelo tipo: um objeto de servidor conecta no próprio processo, uma `str` vira `streamable_http_client(url)`, um `StdioServerParameters` vira `stdio_client(params)` e qualquer outra coisa é aberta diretamente como transporte. É por causa dessa última regra que `stdio_client(...)`, `streamable_http_client(...)` e `sse_client(...)` se encaixam todos no mesmo lugar, e que você pode escrever o seu próprio. ## Recapitulando {#recap} * `Client(mcp)` (o objeto do servidor) conecta em memória. Use para testes e para embutir. * `Client("http://.../mcp")` (uma URL) conecta por Streamable HTTP, o transporte de produção. * Headers, auth, proxies e timeouts pertencem a um `httpx2.AsyncClient` que você passa a `streamable_http_client(url, http_client=...)`. Não existe o argumento `headers=`. -* stdio é `Client(stdio_client(StdioServerParameters(...)))`, nunca o objeto de parâmetros sozinho. +* stdio é `Client(StdioServerParameters(...))`. Envolva-o em `stdio_client(...)` você mesmo apenas para redirecionar o stderr do processo filho. * O subprocesso recebe um ambiente em allow-list, não o seu; `env=` acrescenta a ele. -* Um transporte é qualquer coisa com que você possa fazer `async with x as (read, write)`. `Client` entrega direto a esse protocolo tudo que não for um objeto de servidor ou uma URL. +* Um transporte é qualquer coisa com que você possa fazer `async with x as (read, write)`. `Client` entrega direto a esse protocolo tudo que não for um objeto de servidor, uma URL ou um `StdioServerParameters`. * Construir um `Client` escolhe o transporte. `async with` o abre. Depois que o transporte está aberto, os dois lados precisam concordar sobre uma versão do protocolo. Normalmente você nunca pensa nisso; quando pensar, **[Versões do protocolo](../protocol-versions.md)** é a página. diff --git a/i18n/pt/pages/deprecated.md b/i18n/pt/pages/deprecated.md index 446da146d2..564d9ae243 100644 --- a/i18n/pt/pages/deprecated.md +++ b/i18n/pt/pages/deprecated.md @@ -1,11 +1,11 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # Funcionalidades descontinuadas {#deprecated-features} -A especificação 2026-07-28 aposenta cinco coisas. O SDK ainda implementa cada uma delas, e cada uma agora carrega um **aviso de descontinuação**. +A especificação 2026-07-28 aposenta cinco coisas. O SDK ainda implementa cada uma delas, e cada uma agora carrega um **aviso de descontinuação**. Um helper do SDK está descontinuado por conta própria e aparece listado [no final](#deprecated-sdk-helpers). A tabela abaixo nomeia cada funcionalidade descontinuada, o motivo de ela estar saindo e o substituto sobre o qual construir. @@ -55,6 +55,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE em seguida. Esses dois só funcionam de ponta a ponta em uma conexão `mode="legacy"` cujo cliente registrou o callback correspondente. +## `ping` em uma sessão legacy {#ping-on-a-legacy-session} + +Um **ping** é uma requisição vazia que qualquer um dos lados pode enviar para conferir se o outro ainda está respondendo. A especificação 2026-07-28 o remove ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)): toda requisição que um cliente moderno envia já prova que o servidor está lá, e um servidor moderno não tem canal para enviar um. Os dois métodos do SDK ainda funcionam em uma sessão da era do handshake. Do cliente: + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +E do servidor, dentro de qualquer 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()` avisa com `MCPDeprecationWarning` a cada chamada. Em uma conexão padrão (`2026-07-28`), o servidor responde `MCPError: Method not found` em vez disso. +* `ctx.session.send_ping()` não carrega aviso nenhum. Em uma conexão moderna, lança o mesmo erro de ausência de canal de retorno (back-channel) que qualquer outra requisição iniciada pelo servidor. +* Nenhum dos lados registra nada para responder a um ping. + +## Notificações de mudança de roots {#roots-change-notifications} + +Um cliente da era 2025 que declarou a capacidade roots pode contar ao servidor que as pastas do seu workspace mudaram enviando `notifications/roots/list_changed`; o servidor responde requisitando `roots/list` de novo. A especificação 2026-07-28 remove a notificação junto com o resto do fluxo de roots no estilo push. No cliente, passar `list_roots_callback=` (**[Callbacks do cliente](client/callbacks.md)**) é o que declara `"roots": {"listChanged": true}`, e uma chamada cumpre essa promessa: + +```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() +``` + +No servidor, é o `Server` de baixo nível que aceita o handler do lado receptor: + +```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` é a lista que o seu `list_roots_callback` retorna. `client.send_roots_list_changed()` avisa, e precisa de um cliente `mode="legacy"`: em uma conexão moderna a notificação é descartada silenciosamente. Mantenha a sessão aberta depois, porque o `roots/list` de acompanhamento do servidor chega por ela. +* `MCPServer` não tem hook para a notificação. No `Server` de baixo nível, `on_roots_list_changed=` registra o handler (descontinuado também, e avisa na construção). A notificação não carrega payload, então o handler chama `ctx.session.list_roots()` para obter a nova lista. + ## Silenciando o aviso {#silencing-the-warning} Não faça isso, em código novo. @@ -75,22 +124,33 @@ A API inteira é essa. Não há uma chave por método, e você não quer uma: o Aplique o filtro no sentido contrário e você ganha um teste de regressão de graça. Adicione `"error::mcp.MCPDeprecationWarning"` à configuração `filterwarnings` do seu pytest e a chamada descontinuada **lança uma exceção** em vez de avisar. Uma ferramenta - chamada `old_log` que ainda chama `ctx.info()` para de passar e começa a reportar: + chamada `old_log` que ainda chama `ctx.info()` para de passar: a chamada volta com + `is_error=True` e `Error executing tool old_log`, e o log capturado do servidor aponta o + culpado: ```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). ``` Uma linha de configuração do pytest, e uma chamada descontinuada nunca mais consegue voltar sorrateiramente ao seu código sem quebrar um teste. +## Helpers descontinuados do SDK {#deprecated-sdk-helpers} + +Estas não são mudanças de especificação, apenas detalhes internos do SDK com um substituto melhor. Avisam com o mesmo `MCPDeprecationWarning` e serão removidos na 3.0. + +| Descontinuado | O que fazer no lugar | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()` e depois `FuncMetadata.call_fn()`. Só código que conduz `FuncMetadata` diretamente (uma subclasse personalizada de `Tool`, digamos) chegou a chamá-lo. | + ## Recapitulando {#recap} * A especificação 2026-07-28 descontinua **roots**, a **amostragem** iniciada pelo servidor e o **logging** de protocolo (todos pela [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), restringe o **progresso** ao sentido servidor para cliente e remove o **`ping`**. * A coluna de substitutos indica o próximo passo: **[Requisições de múltiplas idas e voltas](handlers/multi-round-trip.md)** para amostragem e roots, **[Logging](handlers/logging.md)** para logging, **[Progresso](handlers/progress.md)** para progresso. `ping` não precisa de nada. * Descontinuado é consultivo: sem mudanças no protocolo de transmissão, tudo continua funcionando em sessões pré-2026, e você recebe um `MCPDeprecationWarning` visível (um `UserWarning`, então está ligado por padrão). -* Amostragem e roots precisam, além disso, de um canal de retorno (back-channel) que uma sessão 2026-07-28 não tem. Em uma conexão moderna elas avisam e depois lançam uma exceção. +* Amostragem e roots precisam, além disso, de um canal de retorno que uma sessão 2026-07-28 não tem. Em uma conexão moderna elas avisam e depois lançam uma exceção. * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` silencia a categoria inteira; `"error::mcp.MCPDeprecationWarning"` no pytest a transforma em falha de teste. +* Um helper do SDK, `FuncMetadata.call_fn_with_arg_validation()`, está descontinuado separadamente para remoção na 3.0. * Código novo não deve ser construído sobre nenhuma delas. Todas as outras páginas desta documentação ensinam a API atual. diff --git a/i18n/pt/pages/get-started/real-host.md b/i18n/pt/pages/get-started/real-host.md index 2b8643108c..2e1275f5fd 100644 --- a/i18n/pt/pages/get-started/real-host.md +++ b/i18n/pt/pages/get-started/real-host.md @@ -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 --- # Conecte-se a um host de verdade {#connect-to-a-real-host} @@ -11,7 +11,7 @@ Ou seja, conectar a um host é um ato só: você informa a ele **o comando que i ## Um servidor, todos os hosts {#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" ``` @@ -50,7 +50,7 @@ Um comando só para todos eles porque `uv run --with` resolve o SDK em um ambien E um host nada mais é que uma aplicação com um cliente MCP dentro, então seu próprio código Python pode fazer o papel do host: **[Transportes do cliente](../client/transports.md)** - inicia este mesmo arquivo como subprocesso com `stdio_client(...)`, e **[Testes](testing.md)** + inicia este mesmo arquivo como subprocesso com `Client(StdioServerParameters(...))`, e **[Testes](testing.md)** se conecta a ele em memória, sem processo nenhum. ## Claude Desktop {#claude-desktop} diff --git a/i18n/pt/pages/get-started/testing.md b/i18n/pt/pages/get-started/testing.md index ee9819d9b0..2eb6c0621a 100644 --- a/i18n/pt/pages/get-started/testing.md +++ b/i18n/pt/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # Testes {#testing} @@ -85,8 +85,8 @@ Pronto! Agora você pode estender seus testes para cobrir mais cenários. Duas coisas diferentes podem dar errado, e essa flag só mexe em uma delas. Uma exceção dentro de uma das **suas ferramentas** não é uma falha de protocolo. Ela vira um resultado normal com -`is_error=True`, e o modelo lê a mensagem. `raise_exceptions` não muda isso: com ou -sem ela, `call_tool` retorna o mesmo resultado com `is_error=True`. Há uma página inteira sobre isso: +`is_error=True` (e, se for um `ToolError`, o modelo lê a sua mensagem). `raise_exceptions` não +muda isso: com ou sem ela, `call_tool` retorna o mesmo resultado com `is_error=True`. Há uma página inteira sobre isso: **[Tratamento de erros](../servers/handling-errors.md)**. Uma falha **fora** do corpo de uma ferramenta é diferente. Na conexão que `Client(mcp)` entrega, o diff --git a/i18n/pt/pages/handlers/elicitation.md b/i18n/pt/pages/handlers/elicitation.md index 7178e7ac09..98fce9308e 100644 --- a/i18n/pt/pages/handlers/elicitation.md +++ b/i18n/pt/pages/handlers/elicitation.md @@ -1,6 +1,6 @@ --- translation: - sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] + sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] tool: 1 --- # Elicitação {#elicitation} @@ -90,7 +90,8 @@ Esse schema é o formulário. `Field(description=...)` é o rótulo; um valor pa Um schema de elicitação não é tão expressivo quanto o schema de entrada de uma ferramenta. Só campos planos e primitivos: `str`, `int`, `float`, `bool` ou um `Literal` de strings (que vira um `enum`). Coloque um modelo dentro do modelo e `ctx.elicit` lança uma exceção - antes de qualquer coisa ser enviada ao cliente: + antes de qualquer coisa ser enviada ao cliente. A chamada da ferramenta falha com + `Error executing tool `, e o log do seu servidor tem o motivo: ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -113,8 +114,8 @@ Uma recusa não é um erro. A ferramenta decide o que recusar significa (aqui, n !!! tip A resposta é validada contra seu modelo antes que seu código a veja. Um cliente que envia - `"maybe"` para um `bool` não corrompe sua reserva: a chamada falha com um erro de - incompatibilidade de schema, e seu `if` nem chega a executar. + `"maybe"` para um `bool` não corrompe sua reserva: `ctx.elicit` lança `ValueError`, a + chamada falha, e seu `if` nem chega a executar. ## Envie o usuário para uma URL {#send-the-user-to-a-url} diff --git a/i18n/pt/pages/handlers/logging.md b/i18n/pt/pages/handlers/logging.md index f677986a85..002341a342 100644 --- a/i18n/pt/pages/handlers/logging.md +++ b/i18n/pt/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # Logging {#logging} @@ -54,6 +54,8 @@ O padrão é `"INFO"`. `logging.basicConfig()` nunca substitui handlers que já existem. Se você configurar o logging por conta própria antes de criar o servidor, sua configuração vence. +Você também não precisa de um `try`/`except` em cada handler só para registrar falhas. Quando uma função de ferramenta ou de recurso levanta uma exceção, o SDK faz o log para você. **[Tratando erros](../servers/handling-errors.md#any-other-exception)** explica o que é registrado e em qual nível. + ## Experimente {#try-it} Execute o servidor com o MCP Inspector: diff --git a/i18n/pt/pages/run/index.md b/i18n/pt/pages/run/index.md index 65484fce8e..791f05e4a1 100644 --- a/i18n/pt/pages/run/index.md +++ b/i18n/pt/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # Executando seu servidor {#running-your-server} @@ -72,7 +72,7 @@ Cada transporte tem seus próprios argumentos nomeados, todos em `run()`: * `streamable_http_path`: onde fica o endpoint MCP. Padrão `/mcp`. * `json_response=True`: responde a cada POST com um único corpo JSON em vez de um fluxo SSE. Esse corpo tem espaço para a resposta e nada mais, então uma ferramenta que chama o cliente de volta no meio da requisição (`ctx.elicit()`, amostragem (sampling)) lança `NoBackChannelError` nesse trecho, e as notificações ligadas à chamada em andamento (progresso de `ctx.report_progress()`, mensagens de log por chamada) são descartadas; o fluxo `GET` avulso continua transportando as que não têm relação. * `stateless_http=True`: um transporte novo por requisição, sem rastreamento de sessão. -* `max_request_body_size`: maior corpo de POST aceito, em bytes. O padrão é 4 MiB; requisições maiores +* `max_request_body_size`: maior corpo de requisição aceito, em bytes. O padrão é 4 MiB; requisições maiores recebem HTTP 413 antes do parsing ou da criação da sessão. Aumente apenas quando mensagens MCP legítimas ultrapassarem esse tamanho. * `event_store`, `retry_interval`, `transport_security`: retomada e proteção contra DNS rebinding. Podem esperar até você fazer o deploy em algum lugar que não seja o localhost; **[Deploy e escala](deploy.md)** cobre `transport_security`. diff --git a/i18n/pt/pages/servers/handling-errors.md b/i18n/pt/pages/servers/handling-errors.md index 1bb2ecbe1b..70251c97d4 100644 --- a/i18n/pt/pages/servers/handling-errors.md +++ b/i18n/pt/pages/servers/handling-errors.md @@ -1,13 +1,13 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # Tratando erros {#handling-errors} -Uma ferramenta (tool) pode falhar de duas maneiras, e o SDK trata cada uma de forma bem diferente. +Uma ferramenta (tool) pode falhar de três maneiras, e o SDK trata cada uma de forma diferente. -Lance uma exceção comum e é o **modelo** que a vê. Lance `MCPError` e é o **protocolo** que a vê. +Lance `ToolError` e é o **modelo** que vê a sua mensagem. Lance `MCPError` e é o **protocolo** que a vê. Lance qualquer outra coisa e é um crash: o modelo só fica sabendo que a chamada falhou, e o seu log recebe o traceback. Esta página é sobre essa escolha. @@ -15,11 +15,11 @@ Esta página é sobre essa escolha. Pegue uma ferramenta que faz uma consulta e deixe a consulta não encontrar nada: -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -Não há nada de MCP nessas duas linhas. `get_author` lança um `ValueError` comum, como qualquer função Python faria. +`ToolError`, de `mcp.server.mcpserver.exceptions`, é como uma ferramenta avisa ao modelo que algo deu errado. Chame a ferramenta com um título que não está no catálogo e veja o resultado: @@ -30,13 +30,15 @@ result.structured_content # None ``` * A requisição **foi bem-sucedida**. Há um resultado; nada foi lançado no lado de quem chamou. -* `is_error` é `True`, e a mensagem da sua exceção (prefixada com o nome da ferramenta) está em `content`, exatamente onde o modelo lê. +* `is_error` é `True`, e a sua mensagem (prefixada com o nome da ferramenta) está em `content`, exatamente onde o modelo lê. * `structured_content` é `None`. Uma chamada que falhou não tem valor de retorno para estruturar. -Isso é um **erro de ferramenta**, e é o padrão para *qualquer* exceção que a sua ferramenta lançar. Também é, quase sempre, o que você quer. +Isso é um **erro de ferramenta**, e é quase sempre o que você quer. Quem chama a sua ferramenta é o modelo. Foi ele que escolheu os argumentos. Então um erro de ferramenta é um turno na conversa: o modelo lê *"No book titled 'Nothing' in the catalog."*, percebe que chutou o título errado e chama de novo com um melhor. Você escreveu um `raise` e ganhou um agente que se corrige sozinho. +No servidor, um `ToolError` é uma linha `INFO` no log, sem traceback. Você já esperava por ele, então não há nada para investigar. + !!! tip Nunca faça `return` de uma mensagem de erro em uma ferramenta. Uma string retornada tem `is_error=False`, então, para o modelo (e para toda interface de cliente), parece que a ferramenta funcionou e que aquela string era a resposta. @@ -44,7 +46,7 @@ Quem chama a sua ferramenta é o modelo. Foi ele que escolheu os argumentos. Ent ## Um erro que o modelo não consegue corrigir {#an-error-the-model-cannot-fix} -Agora troque `ValueError` por `MCPError`. +Agora troque `ToolError` por `MCPError`. ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -77,10 +79,10 @@ Agora troque `ValueError` por `MCPError`. Os dois caminhos respondem a duas perguntas diferentes. -* **Lance qualquer exceção** para uma falha de *execução*: aquilo que a sua ferramenta tentou fazer não funcionou. Foi o modelo que escolheu a chamada, então é o modelo que deve ver a consequência e ter a chance de se recuperar. Um título escrito errado, uma API upstream que deu timeout, uma linha que não existe: tudo erro de ferramenta. +* **Lance `ToolError`** para uma falha de *execução*: aquilo que a sua ferramenta tentou fazer não funcionou. Foi o modelo que escolheu a chamada, então é o modelo que deve ver a consequência e ter a chance de se recuperar. Um título escrito errado, uma API upstream que deu timeout, uma linha que não existe: tudo erro de ferramenta. * **Lance `MCPError`** quando a *própria requisição* deve ser rejeitada: o cliente não tem uma capacidade da qual a sua ferramenta depende, o servidor não está em condições de atender ninguém, quem chamou pulou uma etapa obrigatória. Nenhuma nova tentativa do modelo corrige nada disso, então não há nada a ganhar entregando a mensagem a ele. -Uma pergunta decide: **um modelo mais esperto teria evitado isso?** Sim -> exceção comum. Não -> `MCPError`. +Uma pergunta decide: **um modelo mais esperto teria evitado isso?** Sim -> `ToolError`. Não -> `MCPError`. Por esse critério, a segunda versão de `get_author` fez a escolha errada: um título melhor resolve, então o modelo merecia ver a mensagem. Ela está ali para mostrar o mecanismo, não para recomendá-lo. @@ -89,6 +91,25 @@ Por esse critério, a segunda versão de `get_author` fez a escolha errada: um t `data` opcional. O que você colocar neles é o que o cliente recebe: o SDK repassa um `MCPError` lançado tal e qual, em vez de sanitizá-lo. +## Qualquer outra exceção {#any-other-exception} + +Agora tire a verificação e deixe a consulta ao dicionário falhar sozinha: + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]` lança `KeyError`. Você não planejou isso, então o SDK trata como um crash: + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +A chamada ainda retorna `is_error=True`, então o modelo sabe que falhou e pode seguir em frente. O que ele não recebe é o texto da exceção: um `KeyError` do seu código, ou uma pilha de SQL vinda de um driver três bibliotecas abaixo, pode descrever o funcionamento interno do seu servidor, então nunca sai do servidor. + +Quem recebe é você. O servidor registra o crash em `ERROR` com o traceback completo, como `Tool 'get_author' raised an unexpected exception`. Um log de produção em `WARNING`, portanto, fica quieto a cada `ToolError` e se manifesta no instante em que algo está de fato quebrado. + ## Um recurso que não existe {#a-resource-that-doesnt-exist} Recursos fazem a mesma distinção, e vêm com uma exceção nomeada para o caso mais comum. @@ -109,7 +130,7 @@ Quando não consegue, lance `ResourceNotFoundError`. O SDK a transforma no erro } ``` -Repare que aqui não existe um meio-resultado com `is_error=True`. A leitura de um recurso ou retorna conteúdo ou falha: recursos só têm o caminho do protocolo. Templates e todo o resto sobre recursos ficam em **[Recursos](resources.md)**. +Repare que aqui não existe um meio-resultado com `is_error=True`. A leitura de um recurso ou retorna conteúdo ou falha: recursos só têm o caminho do protocolo. `ResourceError` é a mesma coisa para uma falha que não é "não encontrado" (`-32603`, com a sua mensagem), e as duas são uma linha `INFO` no seu log. Qualquer outra exceção, exceto `MCPError`, é um crash: o cliente recebe `-32603` citando apenas a URI, e o traceback vai para o seu log em `ERROR`. Templates e todo o resto sobre recursos ficam em **[Recursos](resources.md)**. ## Erros que você nunca lança {#errors-you-never-raise} @@ -120,19 +141,21 @@ Mande para `get_author` um `title` que não seja uma string e o SDK o rejeita co Isso significa uma classe inteira de instruções `raise` que você não escreve: não revalide as suas próprias anotações de tipo. !!! info - Tudo nesta página é o que um **cliente** vê, e o `Client` em memória com o qual você vai escrever - seus testes vê exatamente a mesma coisa. Nem `raise_exceptions=True` transforma um erro de ferramenta - de volta em traceback: no momento em que essa flag poderia agir, a sua exceção já virou o - resultado com `is_error=True`. Faça o assert no resultado. **[Testes](../get-started/testing.md)** cobre o padrão. + Tudo o que um **cliente** vê nesta página, o `Client` em memória com o qual você vai escrever + seus testes também vê. Nem `raise_exceptions=True` devolve a exceção de uma ferramenta que falhou + a quem chamou: no momento em que essa flag poderia agir, a sua exceção já virou o + resultado com `is_error=True`. Faça o assert no resultado. Se você precisar do traceback de um crash, ele está no + log do servidor, e o `caplog` do pytest o captura. **[Testes](../get-started/testing.md)** cobre o padrão. ## Recapitulando {#recap} -* Lance **qualquer exceção** em uma ferramenta -> a chamada retorna `is_error=True` com a sua mensagem em `content`. O modelo lê e pode tentar de novo. Esse é o padrão. +* Lance **`ToolError`** em uma ferramenta -> a chamada retorna `is_error=True` com a sua mensagem em `content`. O modelo lê e pode tentar de novo. * Lance **`MCPError`** -> a própria chamada falha com um erro JSON-RPC. O modelo não vê nada; quem lida com isso é o host. `code`, `message` e `data` sobrevivem intactos. -* A pergunta que decide: *um modelo mais esperto teria evitado isso?* Sim -> exceção. Não -> `MCPError`. +* A pergunta que decide: *um modelo mais esperto teria evitado isso?* Sim -> `ToolError`. Não -> `MCPError`. +* Qualquer **outra exceção** é um crash -> `is_error=True` só com `Error executing tool ` para o modelo, e um registro `ERROR` com o traceback para você. * `ResourceNotFoundError` em um handler de recurso -> o `-32602` do protocolo, com a URI em `data`. * Argumentos inválidos são rejeitados com base no schema antes de a sua função executar; você não dá `raise` para eles. -* `from mcp import MCPError`; as constantes de código de erro vêm de `mcp.types`. +* Imports: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, e as constantes de código de erro de `mcp.types`. Erros tratados. Isso é tudo o que um servidor *expõe*. O que cada handler pode ler, e fazer de volta ao cliente enquanto executa, é a próxima seção: **[Dentro do seu handler](../handlers/index.md)**. diff --git a/i18n/pt/pages/servers/media.md b/i18n/pt/pages/servers/media.md index 4b6a847921..abe4306abe 100644 --- a/i18n/pt/pages/servers/media.md +++ b/i18n/pt/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # Mídia {#media} @@ -86,6 +86,24 @@ Um sufixo não reconhecido cai no padrão `application/octet-stream`. `Audio` a partir de bytes MP3 desse jeito e o cliente recebe `mime_type="audio/wav"` e, confiando nisso, falha ao decodificar. Quando você passar `data=`, passe `format=`. +## Embutindo um recurso {#embedding-a-resource} + +Uma ferramenta também pode retornar um documento: algum texto ou bytes junto com a URI onde ele mora e um tipo MIME. Isso é um **`EmbeddedResource`**, outro tipo de bloco de conteúdo. Diferente de uma `str` simples, ele diz ao cliente o que é o conteúdo, então o cliente pode mostrá-lo como anexo ou reconhecer um recurso que já conhece. + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines` é um recurso comum (**[Recursos](resources.md)** trata deles). A ferramenta entrega o mesmo documento ao modelo quando pedido, e chamar `guidelines()` diretamente mantém uma única fonte da verdade. +* `EmbeddedResource` e `TextResourceContents` vêm de `mcp.types`. Não há um helper como há para imagens: o bloco que você monta entra no resultado sem alteração, e não há `structured_content`. +* Use a URI sob a qual o recurso está registrado, para que um cliente consiga perceber que o anexo e `brand://guidelines` são o mesmo documento. Qualquer URI é válida, registrada ou não. + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +Para conteúdo binário, use `BlobResourceContents(uri=..., mime_type=..., blob=...)` com os bytes codificados em base64 em `blob`, no lugar de `TextResourceContents`. Para enviar apenas um ponteiro que o cliente pode ler depois com `resources/read`, retorne um `ResourceLink(name=..., uri=...)`; ele também é um bloco de conteúdo. + ## Ícones {#icons} Um `Icon` é metadado, não conteúdo. Ele não carrega a imagem; aponta para uma por meio de uma URI, e um cliente pode buscá-la e mostrá-la ao lado do nome do seu servidor, de uma ferramenta, de um recurso ou de um prompt. @@ -115,6 +133,7 @@ Os ícones de uma ferramenta ficam no objeto `Tool` de `tools/list`; os de um re * Retorne uma `Image` ou um `Audio` de uma ferramenta e o cliente recebe um bloco `ImageContent` / `AudioContent`: seus bytes codificados em base64, com um tipo MIME. * Monte um a partir de um `path=` e deixe o sufixo decidir o tipo MIME, ou a partir de `data=` em memória mais um `format=` explícito. +* Retorne um `EmbeddedResource` para colocar um documento (texto ou um blob em base64, com sua URI e tipo MIME) no resultado, ou um `ResourceLink` para enviar só o ponteiro. * Resultados de mídia não trazem `structured_content` nem schema de saída. * Um `Icon` é um ponteiro: uma URI `src` mais `mime_type`, `sizes` e `theme` opcionais. * `icons=[...]` funciona no servidor, em ferramentas, em recursos e em prompts, e os clientes os encontram nos objetos correspondentes. diff --git a/i18n/pt/pages/servers/prompts.md b/i18n/pt/pages/servers/prompts.md index e2ae6ace32..b84ac28763 100644 --- a/i18n/pt/pages/servers/prompts.md +++ b/i18n/pt/pages/servers/prompts.md @@ -1,6 +1,6 @@ --- translation: - sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094] + sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd] tool: 1 --- # Prompts {#prompts} @@ -139,10 +139,55 @@ A entrada em `prompts/list` agora traz tudo de que um cliente precisa para desen ``` !!! info - Se você leu **[Ferramentas](tools.md)**, já sabe tudo o que está nesta página. O mesmo decorador, a mesma + Se você leu **[Ferramentas](tools.md)**, já sabe tudo até este ponto. O mesmo decorador, a mesma docstring como descrição, o mesmo `Annotated`/`Field`. As únicas coisas que mudam são quem dispara (o usuário) e para onde vai o resultado (para a conversa). +## Mais do que texto {#more-than-text} + +`UserMessage` e `AssistantMessage` também aceitam um bloco de conteúdo, ou um helper `Image` / `Audio`, onde quer que aceitem uma `str`. Dois casos aparecem em prompts: anexar um documento e anexar uma imagem. + +### Incorporando um arquivo {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* O guia de estilo é um recurso em `style://python` (**[Recursos](resources.md)** trata deles), lido de um `style-guide.md` ao lado de `server.py`. Coloque qualquer arquivo Markdown ali. +* `EmbeddedResource(resource=TextResourceContents(...))`, ambos de `mcp.types`, carrega o arquivo com sua URI e seu tipo MIME como a primeira mensagem; a instrução que faz referência a ele vem em seguida, como texto simples. +* Incorporar, em vez de colar o guia na f-string, permite que o cliente o mostre como um anexo e reabra `style://python` depois, e o modelo recebe o arquivo na íntegra. Para um arquivo binário, use `BlobResourceContents` com um `blob` em base64. + +Renderizada, o `content` da primeira mensagem é um bloco `resource`: + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### Anexando uma imagem {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image` é o helper de **[Imagens, áudio e ícones](media.md)**. `UserMessage` o converte em um bloco `ImageContent` (o arquivo codificado em base64, o tipo MIME deduzido a partir de `.png`) quando o prompt é renderizado; `Audio` vira um `AudioContent` do mesmo jeito. +* Coloque qualquer PNG chamado `architecture.png` ao lado de `server.py`. Os argumentos de prompt são strings, então a imagem sempre vem do servidor; `component` só fornece as palavras. + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## Mudando a lista em tempo de execução {#changing-the-list-at-runtime} + +Prompts podem ser adicionados enquanto clientes estão conectados, por exemplo para deixar um usuário salvar uma instrução como uma entrada de menu própria. Registre o prompt e depois notifique: + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` registra uma função exatamente como `@mcp.prompt()` faria, e `mcp.remove_prompt(name)` é o inverso. `add_prompt` mantém uma entrada existente com o mesmo nome em vez de sobrescrevê-la, então a ferramenta remove qualquer entrada antiga primeiro para que salvar seja uma substituição. `prompts/list` reflete a mudança imediatamente. +* `await ctx.notify_prompts_changed()` envia `notifications/prompts/list_changed` a todo cliente `2026-07-28` escutando em um stream `subscriptions/listen` (**[Assinaturas](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` envia ao cliente que fez a chamada quando esse cliente é anterior a 2026 (**[Atendendo clientes legados](../run/legacy-clients.md)**). Chame os dois; cada um não faz nada quando não há ninguém para avisar. +* Um cliente que recebe a notificação chama `prompts/list` de novo. No `Client` Python isso é `async with client.listen(prompts_list_changed=True) as sub:`, que produz um evento `PromptsListChanged`. + ## Recapitulando {#recap} * `@mcp.prompt()` em uma função faz dela um prompt. O nome vem da função, a descrição vem da docstring. @@ -151,5 +196,7 @@ A entrada em `prompts/list` agora traz tudo de que um cliente precisa para desen * Retorne uma `str` e ela vira uma mensagem de usuário. Retorne uma lista de `UserMessage` / `AssistantMessage` para iniciar uma conversa de vários turnos. * `title=` e `Field(description=...)` são o que um cliente coloca na interface dele. * Um argumento obrigatório ausente faz a requisição inteira falhar. Não existe um resultado de erro por prompt. +* Embrulhe um `EmbeddedResource` ou um `Image` em uma `UserMessage` para anexar um documento ou uma imagem. +* Adicione ou remova prompts em tempo de execução com `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`, e depois `await ctx.notify_prompts_changed()` e `await ctx.session.send_prompt_list_changed()`. O autocomplete do lado do servidor para os argumentos de um prompt (ou de um template de recurso) é assunto de **[Completions](completions.md)**. diff --git a/i18n/pt/pages/servers/structured-output.md b/i18n/pt/pages/servers/structured-output.md index aa2d364bcb..d2a98855ae 100644 --- a/i18n/pt/pages/servers/structured-output.md +++ b/i18n/pt/pages/servers/structured-output.md @@ -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 --- # Saída estruturada {#structured-output} @@ -105,7 +105,7 @@ Nem toda forma merece uma classe. Um `TypedDict` produz o mesmo schema: --8<-- "docs_src/structured_output/tutorial003.py" ``` -Um `TypedDict` é um `dict` comum em tempo de execução, então é isso que você monta e retorna. O schema, a validação e o `structured_content` são idênticos aos da versão com `BaseModel` (menos as descrições, para as quais o `TypedDict` não tem lugar). +Um `TypedDict` é um `dict` comum em tempo de execução, então é isso que você monta e retorna. O schema, a validação e o `structured_content` seguem as mesmas regras da versão com `BaseModel`: adicione uma docstring à classe ou `Annotated[..., Field(description=...)]` e elas viram as descrições, e uma chave `NotRequired` que você deixa de fora do dict fica de fora do `structured_content`. ## Uma dataclass {#a-dataclass} @@ -187,18 +187,19 @@ Você não percebe enquanto monta o valor à mão: o Pydantic já garantiu que o A anotação promete `WeatherData`. A resposta do serviço upstream parou de enviar `humidity`. !!! check - Chame `get_weather` e ela não entrega discretamente ao cliente um objeto pela metade. A chamada falha, - e as primeiras linhas do erro dão o nome do campo: + Chame `get_weather` e ela não entrega discretamente ao cliente um objeto pela metade. A chamada falha: + o cliente recebe `is_error=True` com `Error executing tool get_weather`, então o modelo sabe que a + chamada falhou em vez de ler, com toda a confiança, um clima que não existe. O nome do campo é para você, + no log do servidor em nível `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] ``` - Esse texto volta como o resultado da ferramenta com `is_error=True`, então o modelo sabe que a chamada - falhou em vez de ler, com toda a confiança, um clima que não existe. - Retornar um `dict` comum de uma ferramenta `-> WeatherData` não tem problema, aliás. É exatamente isso que `json.loads` produziu. A validação é feita sobre o valor, não sobre o tipo Python. ## Desativando {#opting-out} @@ -213,6 +214,10 @@ Sem `output_schema`, sem wrapper, sem validação. `structured_content` é `None O oposto, `structured_output=True`, transforma a detecção automática em exigência: uma ferramenta cujo tipo de retorno não consegue produzir um schema levanta uma exceção no momento do import em vez de recair para texto. +## Blocos de conteúdo e mídia {#content-blocks-and-media} + +Blocos de conteúdo e mídia (`TextContent`, `EmbeddedResource`, `Image`, `Audio` e companhia, sozinhos, como itens de uma `list`, `tuple` ou `Sequence`, ou como membros de uma união) já ficam de fora para você: eles são para o modelo ler, então a detecção automática não deriva nenhum schema deles (**[Imagens, áudio e ícones](media.md)** cobre `Image` e `Audio`). `structured_output=True` ainda força um para as classes de bloco de conteúdo. + ## Uma classe sem anotações de tipo {#a-class-without-type-hints} Existe um jeito de acabar sem estrutura sem ter pedido por isso: retornar uma classe que **não tem anotações no corpo**. @@ -245,6 +250,6 @@ Existe um jeito de acabar sem estrutura sem ter pedido por isso: retornar uma cl * Escalares, listas, tuplas e uniões são envolvidos em `{"result": ...}`. Modelos, `TypedDict`s, dataclasses, classes anotadas e `dict[str, ...]` já são objetos e ficam como estão. * Todo resultado carrega `content` (texto, para o modelo) **e** `structured_content` (dados, para a aplicação). * O que você retorna é validado contra o schema. Uma divergência vira um erro de ferramenta, não um resultado corrompido. -* `structured_output=False` deixa uma ferramenta de fora. Uma classe sem anotações de tipo fica de fora em silêncio; fique atento a isso. +* `structured_output=False` deixa uma ferramenta de fora. Blocos de conteúdo, `Image` e `Audio` ficam de fora por padrão; uma classe sem anotações de tipo fica de fora em silêncio, então fique atento a isso. Agora você domina tudo o que uma ferramenta pode dizer de volta. A seguir, a segunda primitiva: **[Recursos](resources.md)**. diff --git a/i18n/pt/pages/servers/tools.md b/i18n/pt/pages/servers/tools.md index cc17259ff5..eb08f5ff44 100644 --- a/i18n/pt/pages/servers/tools.md +++ b/i18n/pt/pages/servers/tools.md @@ -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 --- # Ferramentas {#tools} @@ -39,6 +39,8 @@ A partir dessas anotações de tipo, o SDK gera um JSON Schema e o envia ao clie Os dois argumentos estão em `required` porque nenhum deles tem valor padrão. Você vai resolver isso daqui a pouco. (As chaves `title` são artefatos do Pydantic; as propriedades, seus tipos e `required` são o contrato.) +Também não há chave `$schema`: o MCP trata um schema sem ela como **JSON Schema 2020-12**, que é o que o Pydantic gera. Então não há nada para escolher até você escrever schemas à mão no **[Server de baixo nível](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**. + !!! tip Aqui, as anotações de tipo não são documentação. Elas são **o contrato**. Se um cliente enviar `"limit": "ten"`, o SDK rejeita isso antes mesmo de a sua função executar. diff --git a/i18n/pt/pages/servers/uri-templates.md b/i18n/pt/pages/servers/uri-templates.md index 6ad4554117..a66f441d89 100644 --- a/i18n/pt/pages/servers/uri-templates.md +++ b/i18n/pt/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # Templates de URI e segurança de caminhos {#uri-templates-and-path-safety} @@ -174,7 +174,7 @@ conhecer o limite do seu sandbox. Para acesso ao sistema de arquivos, use `safe_join` para resolver o caminho e verificar que ele continua dentro do seu diretório base: -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` @@ -216,9 +216,10 @@ de arquivos, `safe_join` continua sendo a fronteira de contenção. !!! tip Se o seu handler não consegue atender à requisição (o arquivo não - existe, o id é desconhecido), levante uma exceção. O SDK a transforma - em uma resposta de erro. Veja **[Tratamento de erros](handling-errors.md)** para a - diferença entre um erro de protocolo e um erro de ferramenta. + existe, o id é desconhecido), levante `ResourceNotFoundError`, como + `read_manual` faz acima. O cliente recebe `-32602` com a sua mensagem + e a URI. Já uma exceção inesperada vira um `-32603` genérico. Veja + **[Tratamento de erros](handling-errors.md#a-resource-that-doesnt-exist)**. ## Recursos no Server de baixo nível {#resources-on-the-low-level-server} diff --git a/i18n/pt/pages/troubleshooting.md b/i18n/pt/pages/troubleshooting.md index fef6a22828..ed7b4b7ed6 100644 --- a/i18n/pt/pages/troubleshooting.md +++ b/i18n/pt/pages/troubleshooting.md @@ -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 --- # Solução de problemas {#troubleshooting} @@ -81,11 +81,11 @@ async def main() -> None: `__aexit__` é a desconexão, e é por isso que não existe um `client.close()` para esquecer. **[Testes](get-started/testing.md)** se baseia exatamente nesse padrão. -## `Error executing tool : ` e `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `, `Error executing tool ` e `Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} Você está lendo um **resultado**, não uma exceção. `call_tool` não lançou exceção, e nunca vai lançar para uma ferramenta que falha. -Chame `forecast` para uma cidade que o servidor não conhece, e a exceção que ela lança volta com a requisição marcada como *bem-sucedida*: +Chame `forecast` para uma cidade que o servidor não conhece, e o `ToolError` que ela lança volta com a requisição marcada como *bem-sucedida*: ```python result.is_error # True @@ -97,6 +97,8 @@ result.structured_content # None A correção está no seu cliente: **verifique `result.is_error`**. Um `try/except` em volta de `call_tool` não captura nenhum desses, porque não há nada para capturar. Isso é proposital, e é a coisa mais útil desta página para internalizar: foi o *modelo* que escolheu a chamada, então é o modelo que recebe a mensagem e uma chance de tentar de novo. **[Tratamento de erros](servers/handling-errors.md)** tem a história completa, incluindo o caminho do `MCPError` que *de fato* lança. +A forma seca, `Error executing tool ` sem mensagem nenhuma, significa que a ferramenta **quebrou**: uma exceção que ela não previu escapou dela (ou o valor de retorno não passou no schema de saída), e o texto dessa exceção fica fora da rede. O traceback está no **log do servidor** em `ERROR`, como `Tool '' 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} Você escreveu `@mcp.tool` em vez de `@mcp.tool()`. `tool()` é uma *fábrica* de decoradores: sem os parênteses, o Python entrega a sua função ao parâmetro `name=` dela. @@ -409,7 +411,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## Recapitulando {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup` nunca é o erro. Leia a **última linha**; capturar `MCPError` *dentro* do bloco `async with Client(...)` pula o embrulho por completo. -* `call_tool` não lança exceção para uma ferramenta que falha. `Error executing tool ...` e `Unknown tool: ...` são resultados: verifique `result.is_error`. +* `call_tool` não lança exceção para uma ferramenta que falha. `Error executing tool ...` e `Unknown tool: ...` são resultados: verifique `result.is_error`. Nenhuma mensagem depois do nome da ferramenta significa que ela quebrou, e o traceback está no log do servidor. * `Client must be used within an async context manager` -> use `async with`. `Use @tool() instead of @tool` -> adicione os parênteses. * `Tool already exists:` no log do servidor é o único sinal de que duas ferramentas com o mesmo nome viraram uma só. * Um 421, três grafias: `Server returned an error response` (o `Client` python), `421 Misdirected Request` / `Invalid Host header` (todo o resto), `Invalid Host header: ` (o log do servidor). Correção: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`. diff --git a/i18n/pt/pages/whats-new.md b/i18n/pt/pages/whats-new.md index c4565dc2c4..6957e00143 100644 --- a/i18n/pt/pages/whats-new.md +++ b/i18n/pt/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # O que há de novo na v2 {#whats-new-in-v2} @@ -47,9 +47,9 @@ A v1 entregava três camadas aninhadas: um gerenciador de contexto de transporte --8<-- "docs_src/client/tutorial001.py" ``` -`Client` recebe um objeto de servidor (em memória, sem transporte: é o cenário dos testes), uma URL (Streamable HTTP) ou qualquer gerenciador de contexto de transporte, como `stdio_client(...)`. Entrar no `async with` conecta e negocia a versão do protocolo, seja qual for a era que o servidor fale; `client.server_capabilities` e `client.protocol_version` simplesmente estão lá depois disso, e `client.server_info` também, quando o servidor se identifica (agora ele é `Implementation | None`, já que na era 2026 a identidade é opcional). Os callbacks de amostragem e de elicitação que você registrou na v1 continuam funcionando (o corpo deles passa pela mesma renomeação de atributos para snake_case que todo o resto desta página), agora também respondem às requisições-dentro-de-resultados no estilo 2026 (abaixo), e rodam de forma concorrente em vez de um por vez. `ClientSession` continua por baixo para quem quer a superfície de baixo nível, e `client.session` a entrega para você; ela também mudou (roda sobre o novo motor de dispatcher, e algumas das próprias assinaturas dela mudaram), então leia o **[Guia de migração](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** antes de descer de nível. +`Client` recebe um objeto de servidor (em memória, sem transporte: é o cenário dos testes), uma URL (Streamable HTTP), um `StdioServerParameters` (um subprocesso stdio) ou qualquer outro gerenciador de contexto de transporte, como `sse_client(...)`. Entrar no `async with` conecta e negocia a versão do protocolo, seja qual for a era que o servidor fale; `client.server_capabilities` e `client.protocol_version` simplesmente estão lá depois disso, e `client.server_info` também, quando o servidor se identifica (agora ele é `Implementation | None`, já que na era 2026 a identidade é opcional). Os callbacks de amostragem e de elicitação que você registrou na v1 continuam funcionando (o corpo deles passa pela mesma renomeação de atributos para snake_case que todo o resto desta página), agora também respondem às requisições-dentro-de-resultados no estilo 2026 (abaixo), e rodam de forma concorrente em vez de um por vez. `ClientSession` continua por baixo para quem quer a superfície de baixo nível, e `client.session` a entrega para você; ela também mudou (roda sobre o novo motor de dispatcher, e algumas das próprias assinaturas dela mudaram), então leia o **[Guia de migração](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** antes de descer de nível. -**[O Client](client/index.md)** o apresenta, **[Transportes do cliente](client/transports.md)** cobre as três formas de conexão, **[Callbacks do cliente](client/callbacks.md)** cobre os callbacks em si, e **[Testes](get-started/testing.md)** mostra o padrão em memória que substitui o helper `create_connected_server_and_client_session()` da v1. +**[O Client](client/index.md)** o apresenta, **[Transportes do cliente](client/transports.md)** cobre as quatro formas de conexão, **[Callbacks do cliente](client/callbacks.md)** cobre os callbacks em si, e **[Testes](get-started/testing.md)** mostra o padrão em memória que substitui o helper `create_connected_server_and_client_session()` da v1. ### O `Server` de baixo nível foi reconstruído, não renomeado {#the-low-level-server-was-rebuilt-not-renamed} @@ -135,7 +135,7 @@ Nesses tipos, todo atributo Python agora é snake_case: `result.is_error`, `tool As renomeações se anunciam sozinhas. Estas aqui, não: * **Funções síncronas rodam em uma thread de trabalho.** Uma ferramenta `def` (ou recurso, prompt ou resolvedor) não bloqueia mais o loop de eventos; a contrapartida é que o corpo dela não roda mais *na* thread do loop de eventos, o que importa para código com afinidade de thread. Handlers `async def` ficam intocados. **[Guia de migração](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**. -* **`MCPError` (o `McpError` da v1) lançado dentro de uma ferramenta agora é um erro de protocolo.** O modelo nunca o vê. Toda outra exceção continua virando um resultado `is_error=True` que o modelo pode ler e ao qual pode reagir. **[Tratando erros](servers/handling-errors.md)** explica a divisão. +* **`MCPError` (o `McpError` da v1) lançado dentro de uma ferramenta agora é um erro de protocolo.** O modelo nunca o vê. Toda outra exceção continua virando um resultado `is_error=True`, mas só a mensagem de um `ToolError` chega ao modelo: qualquer outra exceção agora aparece como `Error executing tool `, com o traceback no log do seu servidor. **[Tratando erros](servers/handling-errors.md)** explica a divisão. * **Os resultados são validados antes de sair.** Uma `Tool` montada à mão cujo `input_schema` é `{}` agora falha em `tools/list` (a especificação exige `"type": "object"`). Servidores construídos com `@mcp.tool()` nunca veem isso; o SDK escreve os schemas deles. * **O seu cliente valida o que recebe.** `list_tools()` e `call_tool()` conferem a resposta do servidor contra a versão de protocolo negociada, então um servidor quase válido que o parsing tolerante da v1 aceitava agora lança `pydantic.ValidationError`. Se você se conecta a servidores que não controla, espere ser você quem os descobre; o **[Guia de migração](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)** tem os detalhes. * **Templates de URI agora são RFC 6570 de verdade.** `{+path}`, `{?query}` e companhia funcionam, a correspondência é exata em vez de frouxa à base de regex, e path traversal nos valores extraídos é rejeitado por padrão. Templates mais rígidos falham no momento da decoração, não na primeira requisição. **[Templates de URI](servers/uri-templates.md)**. diff --git a/i18n/ru/pages/advanced/low-level-server.md b/i18n/ru/pages/advanced/low-level-server.md index bbee2af34c..a982fb0fbd 100644 --- a/i18n/ru/pages/advanced/low-level-server.md +++ b/i18n/ru/pages/advanced/low-level-server.md @@ -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 --- # Низкоуровневый Server {#the-low-level-server} @@ -116,6 +116,17 @@ asyncio.run(main()) Сервер никогда не сравнивает эти два поля. А вот `Client` из этого SDK сравнивает: верните `structured_content`, не соответствующий объявленной вами `output_schema`, и `call_tool` выбросит `RuntimeError`, который начинается с `Invalid structured content returned by tool search_books` и дальше цитирует ошибку `jsonschema`. Пообещать схему легко; соблюдать её — ваша забота. Вся лестница возвращаемых типов и схем — на странице **[Структурированный вывод](../servers/structured-output.md)**. +## Диалект — JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12} + +`input_schema` и `output_schema` — это JSON Schema, и [спецификация MCP](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) фиксирует диалект: схема без ключа `$schema` — это **JSON Schema 2020-12**. Схемы, которые генерирует `MCPServer`, полагаются на это умолчание (Pydantic пишет 2020-12 и опускает ключ), и от написанного вручную словаря ожидается то же самое, так что доступен весь набор ключевых слов 2020-12: + +```python title="server.py" hl_lines="8 14-15" +--8<-- "docs_src/lowlevel/tutorial007.py" +``` + +* Корень `input_schema` должен быть `"type": "object"`. Рядом с ним `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, `$defs` с локальными `$ref` и остальные ключевые слова 2020-12 доходят до клиента ровно в том виде, в каком написаны. +* Ключ `$schema` не нужен. Добавляйте его только чтобы выбрать более старый черновик: `Client` из этого SDK, который проверяет `structured_content` по `output_schema` инструмента, выбирает валидатор по `$schema` и использует 2020-12, когда ключа нет. + ## `_meta`: для приложения, не для модели {#\_meta-for-the-application-not-the-model} `content` — это та часть ответа, которую читает модель. `structured_content` — тот же ответ в виде типизированных данных. `_meta` — третий канал: данные, которые едут вместе с результатом для **клиентского приложения** и вообще не являются частью ответа. @@ -167,7 +178,7 @@ asyncio.run(main()) --8<-- "docs_src/lowlevel/tutorial006.py" ``` -* Первый аргумент — строка метода. У уведомлений есть двойник, `add_notification_handler`. +* Первый аргумент — строка метода. У уведомлений есть двойник, `add_notification_handler`. Его обработчики срабатывают на stdio и на HTTP-подключениях поколения с рукопожатием; на пути Streamable HTTP версии `2026-07-28` POST-запрос клиента с уведомлением подтверждается кодом `202` и не передаётся обработчикам, потому что эта редакция не определяет уведомлений от клиента к серверу по HTTP. * `params_type` — модель, по которой входящие `params` проверяются **до** запуска вашего обработчика, так что пользовательские методы *получают* ту проверку, которой нет у инструментов. Наследуйтесь от `RequestParams`, чтобы поле `_meta` разбиралось так же, как у любого другого метода. * Обработчик возвращает `BaseModel`, `dict` или `None`. SDK сериализует это в результат JSON-RPC. diff --git a/i18n/ru/pages/advanced/middleware.md b/i18n/ru/pages/advanced/middleware.md index 8ecfa14a66..9db08e7a97 100644 --- a/i18n/ru/pages/advanced/middleware.md +++ b/i18n/ru/pages/advanced/middleware.md @@ -1,6 +1,6 @@ --- translation: - sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c] + sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43] tool: 1 --- # Middleware {#middleware} @@ -42,7 +42,7 @@ tools/call took 0.1 ms В этом и суть. Middleware оборачивает **каждое** входящее сообщение: * Установку подключения: `server/discover` или, в сессии старого поколения, `initialize` и `notifications/initialized`. -* Каждый запрос и каждое уведомление. Для уведомления `ctx.request_id is None`, `call_next(ctx)` возвращает `None`, а всё, что вернёте вы, отбрасывается. +* Каждый запрос и каждое уведомление, доходящие до сервера. Для уведомления `ctx.request_id is None`, `call_next(ctx)` возвращает `None`, а всё, что вернёте вы, отбрасывается. (В транспорте Streamable HTTP редакции `2026-07-28` POST-запрос клиента с уведомлением подтверждается кодом `202` на транспортном уровне и никогда не передаётся на обработку, так что до middleware он тоже не доходит; эта редакция не определяет уведомлений от клиента к серверу по HTTP.) * Даже метод, для которого у сервера нет обработчика: `call_next` выбрасывает `MCPError(-32601, "Method not found")` *сквозь* middleware по пути к клиенту. ## Что можно делать внутри {#what-you-can-do-inside-one} @@ -75,7 +75,7 @@ SDK поставляет ровно один слой middleware, и он уже ## Итоги {#recap} * Middleware — это `async (ctx, call_next) -> result`; его передают как `MCPServer(middleware=[...])` (или добавляют в `mcp.middleware`), а в низкоуровневом `Server` добавляют в `server.middleware`. -* Middleware оборачивает **каждое** входящее сообщение (`server/discover`, `initialize`, запросы, уведомления, неизвестные методы) и выполняется начиная с внешнего слоя. +* Middleware оборачивает **каждое** входящее сообщение, доходящее до сервера (`server/discover`, `initialize`, запросы, уведомления, неизвестные методы), и выполняется начиная с внешнего слоя. * `ctx.request_id is None` — так уведомление отличают от запроса. * Чтобы отклонить одно сообщение, выбросьте исключение вместо вызова `call_next`; подключение это переживёт. * Собственная трассировка OpenTelemetry в SDK — тоже middleware, уже в списке. См. **[OpenTelemetry](../run/opentelemetry.md)**. diff --git a/i18n/ru/pages/client/index.md b/i18n/ru/pages/client/index.md index dd494c182d..5a86d3dc10 100644 --- a/i18n/ru/pages/client/index.md +++ b/i18n/ru/pages/client/index.md @@ -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 --- # Объект Client {#the-client} @@ -27,9 +27,10 @@ translation: * Экземпляр `MCPServer` (или низкоуровневого `Server`): подключение **внутри процесса**. * Строка с URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, основной вариант для реального развёртывания. -* **Транспорт**: всё, что можно использовать как `async with ... as (read, write)`, например `stdio_client(...)`, оборачивающий подпроцесс. +* `StdioServerParameters`: команда, которая запускается как **подпроцесс**; общение с ним идёт через его stdin и stdout. +* **Транспорт**: всё, что можно использовать как `async with ... as (read, write)`, например `streamable_http_client(url, http_client=...)` поверх вашего собственного HTTP-клиента. -Всё остальное на этой странице одинаково для всех трёх вариантов. Заголовкам, подпроцессам, тайм-аутам и протоколу `Transport` посвящена отдельная страница: **[Транспорты клиента](transports.md)**. +Всё остальное на этой странице одинаково для всех четырёх вариантов. Заголовкам, подпроцессам, тайм-аутам и протоколу `Transport` посвящена отдельная страница: **[Транспорты клиента](transports.md)**. ### Что есть у подключённого клиента {#whats-on-a-connected-client} @@ -85,7 +86,7 @@ tool.description # 'Search the catalog by title or author.' `call_tool(name, arguments)` запускает инструмент и возвращает `CallToolResult`. -```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 @@ result.is_error # False !!! check Запросите у `lookup_book` `"Solaris"` (название, которого нет в каталоге), и функция выбросит - `ValueError`. Вызов всё равно завершится нормально: + `ToolError`. Вызов всё равно завершится нормально: ```python result.is_error # True @@ -125,9 +126,10 @@ result.is_error # False result.structured_content # None ``` - Сообщение исключения попало в `content`, где **модель** может его прочитать и попробовать снова. Так - и задумано: ошибка инструмента — часть диалога, а не крах. Всегда проверяйте `is_error`, - прежде чем доверять `structured_content`. + Сообщение `ToolError` попало в `content`, где **модель** может его прочитать и попробовать снова. Так + и задумано: ошибка инструмента — часть диалога, а не крах. (Если бы инструмент упал с + каким-то другим исключением, в `content` было бы только `Error executing tool lookup_book`.) Всегда проверяйте + `is_error`, прежде чем доверять `structured_content`. !!! warning `is_error=True` покрывает не только ваш собственный `raise`. Запросите инструмент, которого у сервера вообще нет diff --git a/i18n/ru/pages/client/transports.md b/i18n/ru/pages/client/transports.md index 41d6914cf5..1c24a01ebe 100644 --- a/i18n/ru/pages/client/transports.md +++ b/i18n/ru/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # Клиентские транспорты {#client-transports} @@ -87,15 +87,15 @@ translation: Сервер **stdio** — это подпроцесс. Клиент запускает его, пишет JSON-RPC в его stdin и читает JSON-RPC из его stdout. Именно так десктопный хост запускает сервер на вашей машине: хост — это *и есть* этот код плюс UI, а страница **[Подключение к реальному хосту](../get-started/real-host.md)** показывает те же отношения со стороны хоста, в виде файла конфигурации. -Опишите процесс с помощью `StdioServerParameters`, превратите его в транспорт с помощью `stdio_client` и передайте *его* в `Client`: +Опишите процесс с помощью `StdioServerParameters` и передайте его в `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` не принимает. `StdioServerParameters` — это конфигурация; `stdio_client(server)` — транспорт, который умеет запускать по ней процесс. Всегда оборачивайте. +Вход в блок запускает процесс. Выход из него завершает подпроцесс: закрывает stdin, ждёт, убивает, если тот задерживается. Убирать за ним самостоятельно не нужно. -Выход из блока `async with` заодно завершает подпроцесс: закрывает stdin, ждёт, убивает, если тот задерживается. Убирать за ним самостоятельно не нужно. +stderr дочернего процесса идёт в ваш. Чтобы направить его куда-то ещё, соберите транспорт сами с помощью `stdio_client` (из `mcp`) и передайте вместо этого его: `Client(stdio_client(server, errlog=log_file))`. !!! warning Дочерний процесс **не** наследует ваше окружение. Он получает минимальный разрешённый список (`HOME`, `LOGNAME`, @@ -113,16 +113,16 @@ translation: Для `Client` всё перечисленное — одно и то же. -**Транспорт** — это любой асинхронный контекстный менеджер, который отдаёт пару потоков сообщений `(read, write)`: формально — протокол `Transport` из `mcp.client`. `Client` разрешает свой аргумент по типу: объект сервера подключается внутри процесса, `str` превращается в `streamable_http_client(url)`, а всё остальное используется как транспорт напрямую. Благодаря последнему правилу `stdio_client(...)`, `streamable_http_client(...)` и `sse_client(...)` подходят в одно и то же место — и вы можете написать свой. +**Транспорт** — это любой асинхронный контекстный менеджер, который отдаёт пару потоков сообщений `(read, write)`: формально — протокол `Transport` из `mcp.client`. `Client` разрешает свой аргумент по типу: объект сервера подключается внутри процесса, `str` превращается в `streamable_http_client(url)`, `StdioServerParameters` — в `stdio_client(params)`, а всё остальное используется как транспорт напрямую. Благодаря последнему правилу `stdio_client(...)`, `streamable_http_client(...)` и `sse_client(...)` подходят в одно и то же место — и вы можете написать свой. ## Итоги {#recap} * `Client(mcp)` (объект сервера) подключается в памяти. Используйте для тестов и для встраивания. * `Client("http://.../mcp")` (URL) подключается по Streamable HTTP, транспорту для продакшена. * Заголовки, аутентификация, прокси и таймауты задаются на `httpx2.AsyncClient`, который передаётся в `streamable_http_client(url, http_client=...)`. Именованного аргумента `headers=` нет. -* stdio — это `Client(stdio_client(StdioServerParameters(...)))`, и никогда не объект параметров сам по себе. +* stdio — это `Client(StdioServerParameters(...))`. Оборачивайте его в `stdio_client(...)` сами, только чтобы перенаправить stderr дочернего процесса. * Подпроцесс получает окружение из разрешённого списка, а не ваше; `env=` добавляет к нему. -* Транспорт — это всё, с чем можно написать `async with x as (read, write)`. `Client` передаёт всё, что не объект сервера и не URL, прямо в этот протокол. +* Транспорт — это всё, с чем можно написать `async with x as (read, write)`. `Client` передаёт всё, что не объект сервера, не URL и не `StdioServerParameters`, прямо в этот протокол. * Создание `Client` выбирает транспорт. `async with` его открывает. Когда транспорт открыт, двум сторонам нужно договориться о версии протокола. Обычно думать об этом не приходится; когда всё-таки придётся, нужная страница — **[Версии протокола](../protocol-versions.md)**. diff --git a/i18n/ru/pages/deprecated.md b/i18n/ru/pages/deprecated.md index 57c29e1c7e..ba399dd31a 100644 --- a/i18n/ru/pages/deprecated.md +++ b/i18n/ru/pages/deprecated.md @@ -1,11 +1,11 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # Устаревшие возможности {#deprecated-features} -Спецификация 2026-07-28 выводит из обращения пять возможностей. SDK по-прежнему реализует каждую из них, и каждая теперь выдаёт **предупреждение об устаревании**. +Спецификация 2026-07-28 выводит из обращения пять возможностей. SDK по-прежнему реализует каждую из них, и каждая теперь выдаёт **предупреждение об устаревании**. Один вспомогательный метод SDK объявлен устаревшим по собственным причинам и описан [в конце страницы](#deprecated-sdk-helpers). В таблице ниже перечислены все устаревшие возможности, причина, по которой каждая уходит, и замена, на которую стоит опираться. @@ -57,6 +57,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE только на подключении с `mode="legacy"`, клиент которого зарегистрировал соответствующий колбэк. +## `ping` в сессии старого поколения {#ping-on-a-legacy-session} + +**Ping** — это пустой запрос, который любая из сторон может отправить, чтобы проверить, что другая всё ещё отвечает. Спецификация 2026-07-28 его удаляет ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)): каждый запрос современного клиента и так доказывает, что сервер на месте, а у современного сервера нет канала, чтобы отправить свой. Оба метода SDK по-прежнему работают в сессии поколения с рукопожатием. Со стороны клиента: + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +А со стороны сервера, внутри любого обработчика: + +```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()` при каждом вызове выдаёт `MCPDeprecationWarning`. На подключении по умолчанию (`2026-07-28`) сервер вместо этого отвечает `MCPError: Method not found`. +* `ctx.session.send_ping()` предупреждения не выдаёт. На современном подключении он выбрасывает ту же ошибку об отсутствии обратного канала (back-channel), что и любой другой запрос по инициативе сервера. +* Чтобы отвечать на ping, ни одной из сторон ничего регистрировать не нужно. + +## Уведомления об изменении корневых каталогов {#roots-change-notifications} + +Клиент поколения 2025, объявивший возможность корневых каталогов, может сообщить серверу, что папки его рабочей области изменились, отправив `notifications/roots/list_changed`; в ответ сервер заново запрашивает `roots/list`. Спецификация 2026-07-28 удаляет это уведомление вместе со всей остальной push-схемой работы с корневыми каталогами. На клиенте именно передача `list_roots_callback=` (**[Колбэки клиента](client/callbacks.md)**) объявляет `"roots": {"listChanged": true}`, а выполняет это обещание один вызов: + +```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() +``` + +На стороне сервера принимающий обработчик регистрируется в низкоуровневом классе `Server`: + +```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` — это список, который возвращает ваш `list_roots_callback`. `client.send_roots_list_changed()` выдаёт предупреждение, и ему нужен клиент с `mode="legacy"`: на современном подключении уведомление молча отбрасывается. После этого держите сессию открытой: последующий запрос `roots/list` от сервера придёт именно по ней. +* У `MCPServer` нет точки подключения для этого уведомления. В низкоуровневом `Server` обработчик регистрируется параметром `on_roots_list_changed=` (он тоже устаревший и предупреждает при создании объекта). Полезной нагрузки уведомление не несёт, поэтому за новым списком обработчик вызывает `ctx.session.list_roots()`. + ## Отключение предупреждения {#silencing-the-warning} В новом коде — не отключайте. @@ -77,23 +126,33 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning) Разверните фильтр в обратную сторону — и получите бесплатный регрессионный тест. Добавьте `"error::mcp.MCPDeprecationWarning"` в параметр `filterwarnings` конфигурации pytest, и устаревший вызов будет **выбрасывать исключение**, а не предупреждать. - Инструмент `old_log`, который всё ещё вызывает `ctx.info()`, перестаёт проходить тест - и начинает сообщать: + Инструмент с именем `old_log`, который всё ещё вызывает `ctx.info()`, перестаёт проходить + тест: вызов возвращается с `is_error=True` и текстом `Error executing tool old_log`, а + перехваченный лог сервера называет виновника: ```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). ``` Одна строка в конфигурации pytest — и устаревший вызов уже не сможет незаметно вернуться в кодовую базу, не провалив тест. +## Устаревшие вспомогательные методы SDK {#deprecated-sdk-helpers} + +Это не изменения спецификации, а лишь внутренние детали SDK, у которых появилась лучшая замена. Они выдают то же предупреждение `MCPDeprecationWarning` и будут удалены в версии 3.0. + +| Устарело | Что делать вместо этого | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()`, а затем `FuncMetadata.call_fn()`. Его вызывал только код, работающий с `FuncMetadata` напрямую (скажем, собственный подкласс `Tool`). | + ## Итоги {#recap} * Спецификация 2026-07-28 объявляет устаревшими **корневые каталоги**, **сэмплирование** по инициативе сервера и протокольное **логирование** (всё — [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), ограничивает **прогресс** направлением от сервера к клиенту и удаляет **`ping`**. * Столбец с заменами указывает, куда идти дальше: **[Многораундовые запросы](handlers/multi-round-trip.md)** — для сэмплирования и корневых каталогов, **[Логирование](handlers/logging.md)** — для логирования, **[Прогресс](handlers/progress.md)** — для прогресса. `ping` не требует вообще ничего. * Устаревание носит рекомендательный характер: в передаваемых данных ничего не меняется, всё продолжает работать в сессиях до 2026 года, и появляется заметное предупреждение `MCPDeprecationWarning` (это `UserWarning`, поэтому оно включено по умолчанию). -* Сэмплированию и корневым каталогам дополнительно нужен обратный канал (back-channel), которого в сессии 2026-07-28 нет. На современном подключении они выдают предупреждение, а затем выбрасывают исключение. +* Сэмплированию и корневым каталогам дополнительно нужен обратный канал, которого в сессии 2026-07-28 нет. На современном подключении они выдают предупреждение, а затем выбрасывают исключение. * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` заглушает всю категорию; `"error::mcp.MCPDeprecationWarning"` в pytest превращает её в провал теста. +* Один вспомогательный метод SDK, `FuncMetadata.call_fn_with_arg_validation()`, объявлен устаревшим отдельно и будет удалён в версии 3.0. * Новый код не следует строить ни на одной из этих возможностей. Все остальные страницы этой документации описывают актуальный API. diff --git a/i18n/ru/pages/get-started/real-host.md b/i18n/ru/pages/get-started/real-host.md index 2bc8af97c9..b37d198c18 100644 --- a/i18n/ru/pages/get-started/real-host.md +++ b/i18n/ru/pages/get-started/real-host.md @@ -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 --- # Подключение к настоящему хосту {#connect-to-a-real-host} @@ -11,13 +11,13 @@ translation: ## Один сервер, любой хост {#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" ``` Два инструмента и ресурс, один файл. Три вещи в этом файле важны для каждого хоста ниже: -* `mcp.run()` без аргументов запускает **stdio**-сервер: он блокируется, читает сообщения протокола из stdin и пишет их в stdout. Это тот транспорт, на котором говорят все хосты на этой странице. Хост запускает ваш файл как дочерний процесс и владеет обоими каналами, поэтому подключение всегда сводится к «вот команда». Порт выбирать не нужно, и ничто на нём не слушает. +* `mcp.run()` без аргументов запускает **stdio**-сервер: он блокирует выполнение, читает сообщения протокола из stdin и пишет их в stdout. Это тот транспорт, на котором говорят все хосты на этой странице. Хост запускает ваш файл как дочерний процесс и владеет обоими каналами, поэтому подключение всегда сводится к «вот команда». Порт выбирать не нужно, и ничто на нём не слушает. * `run()` стоит под `if __name__ == "__main__":`. Всё, что ниже, **импортирует** этот файл, а не выполняет его, так что незащищённый `run()` запускал бы сервер в тот момент, когда что-нибудь загружает модуль. * Объект сервера — глобальная переменная уровня модуля с именем `mcp`. Это имя ищет `mcp run` (`server` и `app` тоже подходят). Назовёте иначе — придётся указать имя явно: `mcp run server.py:bookshop`. @@ -51,7 +51,7 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py А хост — это всего лишь приложение с MCP-клиентом внутри, так что роль хоста может сыграть ваш собственный код на Python: **[Клиентские транспорты](../client/transports.md)** - запускают этот же файл как подпроцесс через `stdio_client(...)`, а + запускают этот же файл как подпроцесс через `Client(StdioServerParameters(...))`, а **[Тестирование](testing.md)** подключается к нему в памяти вообще без процесса. ## Claude Desktop {#claude-desktop} diff --git a/i18n/ru/pages/get-started/testing.md b/i18n/ru/pages/get-started/testing.md index 402f9e0a68..1f4d623e41 100644 --- a/i18n/ru/pages/get-started/testing.md +++ b/i18n/ru/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # Тестирование {#testing} @@ -84,9 +84,10 @@ async def test_call_add_tool(client: Client): Пойти не так могут две разные вещи, и этот флаг касается только одной из них. Исключение внутри одного из **ваших инструментов** — не сбой протокола. Оно превращается в -обычный результат с `is_error=True`, и модель читает сообщение. `raise_exceptions` этого не -меняет: с ним или без него `call_tool` возвращает один и тот же результат с `is_error=True`. -Этому посвящена целая страница: **[Обработка ошибок](../servers/handling-errors.md)**. +обычный результат с `is_error=True` (а если это был `ToolError`, модель читает ваше сообщение). +`raise_exceptions` этого не меняет: с ним или без него `call_tool` возвращает один и тот же +результат с `is_error=True`. Этому посвящена целая страница: +**[Обработка ошибок](../servers/handling-errors.md)**. Сбой **вне** тела инструмента — другое дело. На подключении, которое даёт `Client(mcp)`, сервер очищает его до обобщённого `"Internal server error"`, прежде чем оно дойдёт до клиента. Детали diff --git a/i18n/ru/pages/handlers/elicitation.md b/i18n/ru/pages/handlers/elicitation.md index 9ac0be4b4d..15da570f73 100644 --- a/i18n/ru/pages/handlers/elicitation.md +++ b/i18n/ru/pages/handlers/elicitation.md @@ -1,6 +1,6 @@ --- translation: - sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] + sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] tool: 1 --- # Элицитация {#elicitation} @@ -89,7 +89,8 @@ translation: !!! warning Схема элицитации не так выразительна, как входная схема инструмента. Только плоские примитивные поля: `str`, `int`, `float`, `bool` или `Literal` из строк (он становится `enum`). - Вложите модель в модель — и `ctx.elicit` выбросит исключение ещё до того, как что-либо уйдёт клиенту: + Вложите модель в модель — и `ctx.elicit` выбросит исключение ещё до того, как что-либо уйдёт клиенту. + Вызов инструмента завершится ошибкой `Error executing tool `, а причина будет в логе сервера: ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -112,8 +113,8 @@ translation: !!! tip Ответ проверяется по вашей модели до того, как его увидит ваш код. Клиент, приславший - `"maybe"` вместо `bool`, не испортит бронирование: вызов завершится ошибкой - несоответствия схеме, а ваш `if` так и не выполнится. + `"maybe"` вместо `bool`, не испортит бронирование: `ctx.elicit` выбросит `ValueError`, вызов + завершится ошибкой, а ваш `if` так и не выполнится. ## Отправка пользователя по URL {#send-the-user-to-a-url} diff --git a/i18n/ru/pages/handlers/logging.md b/i18n/ru/pages/handlers/logging.md index 45e0172dba..276ae292ff 100644 --- a/i18n/ru/pages/handlers/logging.md +++ b/i18n/ru/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # Логирование {#logging} @@ -55,6 +55,8 @@ translation: `logging.basicConfig()` никогда не заменяет уже существующие обработчики. Если настроить логирование самостоятельно до создания сервера, ваша конфигурация имеет приоритет. +Не нужен и `try`/`except` в каждом обработчике только ради того, чтобы зафиксировать сбой. Когда функция инструмента или ресурса выбрасывает исключение, SDK записывает его в лог за вас. Что именно попадает в лог и на каком уровне, объясняется на странице **[Обработка ошибок](../servers/handling-errors.md#any-other-exception)**. + ## Попробуйте сами {#try-it} Запустите сервер через MCP Inspector: diff --git a/i18n/ru/pages/run/index.md b/i18n/ru/pages/run/index.md index 491564361c..879daf7259 100644 --- a/i18n/ru/pages/run/index.md +++ b/i18n/ru/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # Запуск сервера {#running-your-server} @@ -72,7 +72,7 @@ Inspector делает ровно то же, что и настоящий хос * `streamable_http_path`: где находится конечная точка MCP. По умолчанию `/mcp`. * `json_response=True`: отвечать на каждый POST одним JSON-телом вместо SSE-потока. В этом теле есть место только для ответа и ничего больше, поэтому инструмент, который обращается к клиенту посреди запроса (`ctx.elicit()`, сэмплирование (sampling)), на этом участке выбрасывает `NoBackChannelError`, а уведомления, привязанные к выполняющемуся вызову (ход выполнения от `ctx.report_progress()`, лог-сообщения отдельного вызова), отбрасываются; отдельный поток `GET` по-прежнему доставляет не связанные с вызовом уведомления. * `stateless_http=True`: свежий транспорт на каждый запрос, без отслеживания сессий. -* `max_request_body_size`: максимальный принимаемый размер тела POST в байтах. По умолчанию 4 МиБ; более крупные запросы +* `max_request_body_size`: максимальный принимаемый размер тела запроса в байтах. По умолчанию 4 МиБ; более крупные запросы получают HTTP 413 ещё до разбора и создания сессии. Увеличивайте его, только если легитимные MCP-сообщения превышают этот размер. * `event_store`, `retry_interval`, `transport_security`: возобновляемость и защита от DNS-rebinding. Они могут подождать, пока вы не развернётесь где-то кроме localhost; `transport_security` разобран на странице **[Развёртывание и масштабирование](deploy.md)**. diff --git a/i18n/ru/pages/servers/handling-errors.md b/i18n/ru/pages/servers/handling-errors.md index e64a1c9fa7..898f3be629 100644 --- a/i18n/ru/pages/servers/handling-errors.md +++ b/i18n/ru/pages/servers/handling-errors.md @@ -1,13 +1,13 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # Обработка ошибок {#handling-errors} -Инструмент может завершиться неудачей двумя способами, и SDK обрабатывает их совершенно по-разному. +Инструмент может завершиться неудачей тремя способами, и SDK обрабатывает каждый из них по-своему. -Выбросьте обычное исключение — и его увидит **модель**. Выбросьте `MCPError` — и его увидит **протокол**. +Выбросьте `ToolError` — и ваше сообщение увидит **модель**. Выбросьте `MCPError` — и его увидит **протокол**. Выбросьте что-то другое — и это уже сбой: модель узнает только, что вызов не удался, а трассировка попадёт в ваш лог. Эта страница о том, как выбрать. @@ -15,11 +15,11 @@ translation: Возьмём инструмент, который что-то ищет, и пусть поиск ничего не найдёт: -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -В этих двух строках нет ничего специфичного для MCP. `get_author` выбрасывает обычный `ValueError`, как любая функция на Python. +`ToolError` из `mcp.server.mcpserver.exceptions` — это способ, которым инструмент сообщает модели, что что-то пошло не так. Вызовите его с названием, которого нет в каталоге, и посмотрите на результат: @@ -30,12 +30,14 @@ result.structured_content # None ``` * Запрос **выполнен успешно**. Результат есть; на вызывающей стороне ничего не выброшено. -* `is_error` равен `True`, а сообщение вашего исключения (с префиксом в виде имени инструмента) лежит в `content` — ровно там, где читает модель. +* `is_error` равен `True`, а ваше сообщение (с префиксом в виде имени инструмента) лежит в `content` — ровно там, где читает модель. * `structured_content` равен `None`. У неудачного вызова нет возвращаемого значения, которое можно было бы структурировать. -Это **ошибка инструмента**, и так по умолчанию обрабатывается *любое* исключение, выброшенное инструментом. И почти всегда это именно то, что нужно. +Это **ошибка инструмента**, и почти всегда это именно то, что нужно. -Ваш инструмент вызывает модель. Она же выбрала аргументы. Поэтому ошибка инструмента — это реплика в диалоге: модель читает *«No book titled 'Nothing' in the catalog.»*, понимает, что ошиблась с названием, и вызывает инструмент снова с более подходящим. Вы написали один `raise` и получили агента, который исправляет себя сам. +Вызывает ваш инструмент именно модель. Она же выбрала аргументы. Поэтому ошибка инструмента — это реплика в диалоге: модель читает *«No book titled 'Nothing' in the catalog.»*, понимает, что ошиблась с названием, и вызывает инструмент снова с более подходящим. Вы написали один `raise` и получили агента, который исправляет себя сам. + +На сервере `ToolError` — это одна строка уровня `INFO` в логе, без трассировки. Вы её предвидели, так что расследовать нечего. !!! tip Никогда не возвращайте сообщение об ошибке из инструмента через `return`. У возвращённой строки @@ -44,7 +46,7 @@ result.structured_content # None ## Ошибка, которую модель исправить не может {#an-error-the-model-cannot-fix} -Теперь замените `ValueError` на `MCPError`. +Теперь замените `ToolError` на `MCPError`. ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -77,10 +79,10 @@ result.structured_content # None Два пути отвечают на два разных вопроса. -* **Выбрасывайте любое исключение** при сбое *выполнения*: то, что инструмент пытался сделать, не получилось. Вызов выбрала модель, значит, модель и должна увидеть последствия и получить шанс исправиться. Опечатка в названии, тайм-аут внешнего API, несуществующая строка в таблице — всё это ошибки инструмента. +* **Выбрасывайте `ToolError`** при сбое *выполнения*: то, что инструмент пытался сделать, не получилось. Вызов выбрала модель, значит, модель и должна увидеть последствия и получить шанс исправиться. Опечатка в названии, тайм-аут внешнего API, несуществующая строка в таблице — всё это ошибки инструмента. * **Выбрасывайте `MCPError`**, когда отклонить нужно *сам запрос*: у клиента нет возможности, от которой зависит инструмент, сервер не в состоянии обслуживать кого бы то ни было, вызывающая сторона пропустила обязательный шаг. Никакая повторная попытка модели ничего из этого не исправит, так что передавать ей сообщение бессмысленно. -Решает один вопрос: **могла бы более умная модель этого избежать?** Да -> обычное исключение. Нет -> `MCPError`. +Решает один вопрос: **могла бы более умная модель этого избежать?** Да -> `ToolError`. Нет -> `MCPError`. По этому критерию вторая версия `get_author` выбрала неверно: правильное название всё исправляет, значит, модель заслуживала увидеть сообщение. Она здесь, чтобы показать механизм, а не чтобы его рекомендовать. @@ -89,6 +91,25 @@ result.structured_content # None полезную нагрузку `data`. Что бы вы в них ни положили, именно это и получит клиент: SDK передаёт выброшенный `MCPError` дословно, не очищая его. +## Любое другое исключение {#any-other-exception} + +Теперь уберите проверку и дайте поиску по словарю упасть самому: + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]` выбрасывает `KeyError`. Вы этого не предусмотрели, поэтому SDK считает это сбоем: + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +Вызов по-прежнему возвращает `is_error=True`, так что модель знает, что он не удался, и может двигаться дальше. Чего она не получает, так это текста исключения: `KeyError` из вашего кода или гора SQL из драйвера тремя библиотеками ниже могут описывать внутреннее устройство сервера, поэтому за его пределы этот текст не выходит. + +Вместо модели его получаете вы. Сервер пишет сбой в лог на уровне `ERROR` с полной трассировкой — как `Tool 'get_author' raised an unexpected exception`. Поэтому в эксплуатации лог на уровне `WARNING` молчит при каждом `ToolError` и подаёт голос в тот момент, когда что-то действительно сломалось. + ## Ресурс, которого не существует {#a-resource-that-doesnt-exist} Ресурсы проводят ту же границу и для частого случая поставляются с одним именованным исключением. @@ -109,7 +130,7 @@ result.structured_content # None } ``` -Обратите внимание: здесь нет полурезультата с `is_error=True`. Чтение ресурса либо возвращает содержимое, либо завершается ошибкой — у ресурсов есть только протокольный путь. Шаблоны и всё остальное о ресурсах — на странице **[Ресурсы](resources.md)**. +Обратите внимание: здесь нет полурезультата с `is_error=True`. Чтение ресурса либо возвращает содержимое, либо завершается ошибкой — у ресурсов есть только протокольный путь. `ResourceError` — то же самое для сбоя, который не сводится к «не найдено» (`-32603`, ваше сообщение), и оба оставляют в вашем логе одну строку уровня `INFO`. Любое другое исключение, кроме `MCPError`, — это сбой: клиент получает `-32603` с указанием одного лишь URI, а трассировка уходит в ваш лог на уровне `ERROR`. Шаблоны и всё остальное о ресурсах — на странице **[Ресурсы](resources.md)**. ## Ошибки, которые вы никогда не выбрасываете {#errors-you-never-raise} @@ -120,19 +141,21 @@ result.structured_content # None Это целый класс операторов `raise`, которые писать не нужно: не проверяйте повторно собственные аннотации типов. !!! info - Всё на этой странице — это то, что видит **клиент**, и `Client` в памяти, с которым вы будете - писать тесты, видит ровно то же самое. Даже `raise_exceptions=True` не превращает ошибку инструмента - обратно в трассировку: к моменту, когда этот флаг мог бы сработать, ваше исключение уже стало - результатом с `is_error=True`. Проверяйте результат. Этот приём описан на странице **[Тестирование](../get-started/testing.md)**. + Всё, что на этой странице видит **клиент**, видит и `Client` в памяти, с которым вы будете + писать тесты. Даже `raise_exceptions=True` не возвращает исключение упавшего + инструмента вызывающей стороне: к моменту, когда этот флаг мог бы сработать, ваше исключение уже стало + результатом с `is_error=True`. Проверяйте результат. Если нужна трассировка сбоя, она в логе + сервера, и `caplog` из pytest её перехватывает. Этот приём описан на странице **[Тестирование](../get-started/testing.md)**. ## Итоги {#recap} -* Выбрасываете **любое исключение** в инструменте -> вызов возвращает `is_error=True` с вашим сообщением в `content`. Модель читает его и может повторить попытку. Это поведение по умолчанию. +* Выбрасываете **`ToolError`** в инструменте -> вызов возвращает `is_error=True` с вашим сообщением в `content`. Модель читает его и может повторить попытку. * Выбрасываете **`MCPError`** -> сам вызов завершается ошибкой JSON-RPC. Модель ничего не видит; разбирается хост. `code`, `message` и `data` доходят без изменений. -* Решающий вопрос: *могла бы более умная модель этого избежать?* Да -> исключение. Нет -> `MCPError`. +* Решающий вопрос: *могла бы более умная модель этого избежать?* Да -> `ToolError`. Нет -> `MCPError`. +* Любое **другое исключение** — это сбой -> `is_error=True`, где для модели только `Error executing tool `, а для вас — запись уровня `ERROR` с трассировкой. * `ResourceNotFoundError` из обработчика ресурса -> протокольный `-32602` с URI в `data`. * Некорректные аргументы отклоняются по схеме до запуска вашей функции; `raise` для них не нужен. -* `from mcp import MCPError`; константы кодов ошибок — из `mcp.types`. +* Импорты: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, а константы кодов ошибок — из `mcp.types`. С ошибками разобрались. Это всё, что сервер *предоставляет*. Что каждый обработчик может прочитать и что сделать в сторону клиента во время выполнения — в следующем разделе: **[Внутри обработчика](../handlers/index.md)**. diff --git a/i18n/ru/pages/servers/media.md b/i18n/ru/pages/servers/media.md index 3cd5cca98c..2a1294bed3 100644 --- a/i18n/ru/pages/servers/media.md +++ b/i18n/ru/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # Медиа {#media} @@ -86,6 +86,24 @@ result.structured_content # None `Audio` из байтов MP3 — и клиенту сообщат `mime_type="audio/wav"`, после чего он честно не сможет это декодировать. Передаёте `data=` — передавайте и `format=`. +## Встраивание ресурса {#embedding-a-resource} + +Инструмент может вернуть и документ: текст или байты вместе с URI, по которому он находится, и MIME-типом. Это **`EmbeddedResource`**, ещё один вид блока содержимого. В отличие от обычной `str` он сообщает клиенту, что это за содержимое, и клиент может показать его как вложение или узнать ресурс, который ему уже знаком. + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines` — обычный ресурс (о них — на странице **[Ресурсы](resources.md)**). Инструмент по запросу отдаёт модели тот же документ, а прямой вызов `guidelines()` сохраняет единый источник истины. +* `EmbeddedResource` и `TextResourceContents` берутся из `mcp.types`. Вспомогательного класса, как для изображений, нет: собранный вами блок попадает в результат как есть, а `structured_content` отсутствует. +* Используйте тот URI, под которым ресурс зарегистрирован, чтобы клиент мог понять, что вложение и `brand://guidelines` — один и тот же документ. Допустим любой URI, зарегистрированный или нет. + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +Для двоичного содержимого вместо `TextResourceContents` используйте `BlobResourceContents(uri=..., mime_type=..., blob=...)` с байтами, закодированными в base64 в поле `blob`. Чтобы отправить только указатель, по которому клиент позже сможет выполнить `resources/read`, верните вместо этого `ResourceLink(name=..., uri=...)` — это тоже блок содержимого. + ## Иконки {#icons} `Icon` — это метаданные, а не содержимое. Изображение он не несёт: он указывает на него через URI, а клиент может загрузить его и показать рядом с именем сервера, инструментом, ресурсом или промптом. @@ -115,6 +133,7 @@ client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_ * Верните `Image` или `Audio` из инструмента — и клиент получит блок `ImageContent` / `AudioContent`: ваши байты в кодировке base64 с MIME-типом. * Собирайте их из `path=`, и тогда MIME-тип определит расширение, или из данных в памяти через `data=` с явным `format=`. +* Верните `EmbeddedResource`, чтобы поместить в результат документ (текст или blob в base64 вместе с его URI и MIME-типом), или `ResourceLink`, чтобы отправить только указатель. * У медиарезультатов нет ни `structured_content`, ни схемы выходных данных. * `Icon` — это указатель: URI в `src` плюс необязательные `mime_type`, `sizes` и `theme`. * `icons=[...]` работает на сервере, инструментах, ресурсах и промптах, а клиенты находят их в соответствующих объектах. diff --git a/i18n/ru/pages/servers/prompts.md b/i18n/ru/pages/servers/prompts.md index 3ee0fbec2a..77607be82b 100644 --- a/i18n/ru/pages/servers/prompts.md +++ b/i18n/ru/pages/servers/prompts.md @@ -1,6 +1,6 @@ --- translation: - sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094] + sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd] tool: 1 --- # Промпты {#prompts} @@ -140,9 +140,54 @@ uv run mcp dev server.py ``` !!! info - Если вы читали страницу **[Инструменты](tools.md)**, то уже знаете всё, что здесь написано. Тот же декоратор, + Если вы читали страницу **[Инструменты](tools.md)**, всё сказанное до этого места вам уже знакомо. Тот же декоратор, та же строка документации в роли описания, те же `Annotated`/`Field`. Меняется только то, кто - запускает промпт (пользователь) и куда идёт результат (в диалог). + запускает промпт (пользователь), и куда идёт результат (в диалог). + +## Больше, чем текст {#more-than-text} + +`UserMessage` и `AssistantMessage` везде, где принимают `str`, принимают также блок содержимого или вспомогательный объект `Image` / `Audio`. В промптах встречаются два случая: вложить документ и вложить картинку. + +### Встраивание файла {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* Руководство по стилю — это ресурс по адресу `style://python` (о ресурсах — на странице **[Ресурсы](resources.md)**), который читается из файла `style-guide.md` рядом с `server.py`. Положите туда любой файл Markdown. +* `EmbeddedResource(resource=TextResourceContents(...))` (оба из `mcp.types`) несёт файл вместе с его URI и MIME-типом первым сообщением; запрос, который на него ссылается, идёт следом обычным текстом. +* Встраивание, в отличие от вставки руководства прямо в f-строку, позволяет клиенту показать его как вложение и позже снова открыть `style://python`, а модель получает файл дословно. Для двоичного файла используйте `BlobResourceContents` с `blob` в base64. + +После рендеринга `content` первого сообщения — это блок `resource`: + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### Вложение изображения {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image` — вспомогательный класс со страницы **[Изображения, аудио и иконки](media.md)**. `UserMessage` преобразует его в блок `ImageContent` (файл в base64, MIME-тип угадывается по `.png`) при рендеринге промпта; `Audio` точно так же становится `AudioContent`. +* Положите рядом с `server.py` любой PNG с именем `architecture.png`. Аргументы промпта — строки, поэтому картинка всегда берётся с сервера; `component` даёт только слова. + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## Изменение списка во время работы {#changing-the-list-at-runtime} + +Промпты можно добавлять, пока клиенты подключены, — например, чтобы пользователь мог сохранить инструкцию как собственный пункт меню. Зарегистрируйте промпт, затем отправьте уведомление: + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` регистрирует функцию ровно так же, как это сделал бы `@mcp.prompt()`, а `mcp.remove_prompt(name)` — обратная операция. `add_prompt` сохраняет существующую запись с тем же именем, а не перезаписывает её, поэтому инструмент сначала удаляет старую, чтобы сохранение работало как замена. `prompts/list` отражает изменение сразу. +* `await ctx.notify_prompts_changed()` отправляет `notifications/prompts/list_changed` каждому клиенту `2026-07-28`, который слушает поток `subscriptions/listen` (**[Подписки](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` отправляет его вызывающему клиенту, если тот старше поколения 2026 (**[Обслуживание клиентов старого поколения](../run/legacy-clients.md)**). Вызывайте оба; каждый ничего не делает, когда сообщать некому. +* Клиент, получивший уведомление, снова вызывает `prompts/list`. В классе `Client` на Python это `async with client.listen(prompts_list_changed=True) as sub:`, который выдаёт событие `PromptsListChanged`. ## Итоги {#recap} @@ -152,5 +197,7 @@ uv run mcp dev server.py * Верните `str` — и она станет одним сообщением пользователя. Верните список `UserMessage` / `AssistantMessage`, чтобы задать многоходовой диалог. * `title=` и `Field(description=...)` — это то, что клиент показывает в интерфейсе. * Отсутствующий обязательный аргумент проваливает весь запрос. Отдельного результата с ошибкой у промпта нет. +* Оберните `EmbeddedResource` или `Image` в `UserMessage`, чтобы вложить документ или картинку. +* Добавляйте и удаляйте промпты во время работы через `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`, затем вызывайте `await ctx.notify_prompts_changed()` и `await ctx.session.send_prompt_list_changed()`. Автодополнение аргументов промпта (или шаблона ресурса) на стороне сервера — на странице **[Автодополнение](completions.md)**. diff --git a/i18n/ru/pages/servers/structured-output.md b/i18n/ru/pages/servers/structured-output.md index 043fca2e90..b335db6a2f 100644 --- a/i18n/ru/pages/servers/structured-output.md +++ b/i18n/ru/pages/servers/structured-output.md @@ -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 --- # Структурированный вывод {#structured-output} @@ -105,7 +105,7 @@ result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions --8<-- "docs_src/structured_output/tutorial003.py" ``` -Во время выполнения `TypedDict` — обычный `dict`, его вы и собираете и возвращаете. Схема, валидация и `structured_content` идентичны варианту с `BaseModel` (за вычетом описаний, которые в `TypedDict` разместить негде). +Во время выполнения `TypedDict` — обычный `dict`, его вы и собираете и возвращаете. Схема, валидация и `structured_content` подчиняются тем же правилам, что и в варианте с `BaseModel`: добавьте docstring класса или `Annotated[..., Field(description=...)]` — и они станут описаниями, а ключ `NotRequired`, который вы не включили в словарь, не попадёт и в `structured_content`. ## Dataclass {#a-dataclass} @@ -187,18 +187,19 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} Аннотация обещает `WeatherData`. Ответ вышестоящего сервиса перестал присылать `humidity`. !!! check - Вызовите `get_weather` — и он не передаст клиенту молча полупустой объект. Вызов завершается ошибкой, - и первые же строки ошибки называют поле: + Вызовите `get_weather` — и он не передаст клиенту молча полупустой объект. Вызов завершается ошибкой: + клиент получает `is_error=True` с текстом `Error executing tool get_weather`, так что модель знает, что + вызов не удался, а не уверенно читает погоду, которой нет. Имя поля — для вас, в логе сервера + на уровне `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] ``` - Этот текст возвращается как результат инструмента с `is_error=True`, так что модель знает, что вызов - не удался, а не уверенно читает погоду, которой нет. - Кстати, вернуть обычный `dict` из инструмента с `-> WeatherData` вполне допустимо. Именно это и выдал `json.loads`. Проверяется значение, а не тип Python. ## Отказ от структурированного вывода {#opting-out} @@ -213,6 +214,10 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} Обратный вариант, `structured_output=True`, превращает автоматическое определение в требование: инструмент, по возвращаемому типу которого нельзя построить схему, выбрасывает исключение при импорте, а не переключается на текст. +## Блоки контента и медиа {#content-blocks-and-media} + +Блоки контента и медиа (`TextContent`, `EmbeddedResource`, `Image`, `Audio` и им подобные — сами по себе, как элементы `list`, `tuple` или `Sequence` либо как варианты объединения) исключаются из структурированного вывода за вас: они предназначены для чтения моделью, поэтому автоматическое определение не выводит по ним схему (об `Image` и `Audio` — на странице **[Изображения, аудио и значки](media.md)**). `structured_output=True` по-прежнему принудительно строит схему для классов блоков контента. + ## Класс без аннотаций типов {#a-class-without-type-hints} Есть один способ оказаться без структурированного вывода, не прося об этом: вернуть класс, в **теле которого нет аннотаций**. @@ -242,9 +247,9 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} ## Итоги {#recap} * **Аннотация возвращаемого типа** — это выходная схема. Она публикуется в `tools/list` как `output_schema`. -* Скаляры, списки, кортежи и объединения оборачиваются в `{"result": ...}`. Модели, `TypedDict`, dataclass, классы с аннотациями и `dict[str, ...]` уже являются объектами и остаются как есть. +* Скаляры, списки, кортежи и объединения оборачиваются в `{"result": ...}`. Модели, `TypedDict`, dataclass, классы с аннотациями и `dict[str, ...]` — уже объекты и остаются как есть. * Каждый результат несёт `content` (текст, для модели) **и** `structured_content` (данные, для приложения). * Возвращаемое значение проверяется на соответствие схеме. Несоответствие — это ошибка инструмента, а не испорченный результат. -* `structured_output=False` отключает структурированный вывод для инструмента. Класс без аннотаций типов отключает его молча — следите за этим. +* `structured_output=False` отключает структурированный вывод для инструмента. Блоки контента, `Image` и `Audio` отключают его по умолчанию; класс без аннотаций типов отключает его молча — следите за этим. Теперь вы владеете всем, что инструмент может сказать в ответ. Дальше — второй примитив: **[Ресурсы](resources.md)**. diff --git a/i18n/ru/pages/servers/tools.md b/i18n/ru/pages/servers/tools.md index 83266ea0c4..a020dc5e4c 100644 --- a/i18n/ru/pages/servers/tools.md +++ b/i18n/ru/pages/servers/tools.md @@ -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} @@ -39,6 +39,8 @@ translation: Оба аргумента попали в `required`, потому что ни у одного нет значения по умолчанию. Сейчас это исправим. (Ключи `title` — артефакты Pydantic; контракт составляют свойства, их типы и `required`.) +Ключа `$schema` тоже нет: схему без него MCP рассматривает как **JSON Schema 2020-12**, а Pydantic генерирует именно её, так что выбирать нечего — пока не придётся писать схемы вручную для **[низкоуровневого Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**. + !!! tip Аннотации типов здесь не документация. Это и есть **контракт**. Если клиент пришлёт `"limit": "ten"`, SDK отклонит вызов ещё до того, как запустится функция. diff --git a/i18n/ru/pages/servers/uri-templates.md b/i18n/ru/pages/servers/uri-templates.md index 1e19a5ad80..1222fea243 100644 --- a/i18n/ru/pages/servers/uri-templates.md +++ b/i18n/ru/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # Шаблоны URI и безопасность путей {#uri-templates-and-path-safety} @@ -170,14 +170,14 @@ SDK поддерживает подмножество, подобранное д песочницы. Для доступа к файловой системе используйте `safe_join`, чтобы разрешить путь и убедиться, что он остаётся внутри базового каталога: -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` `safe_join` ловит выход через символические ссылки, последовательности `..` и трюки с абсолютными путями, которые простая строковая проверка пропустила бы. Если разрешённый путь выходит за `DOCS_ROOT`, функция -выбрасывает `PathEscapeError`, которое доходит до клиента как +выбрасывает исключение `PathEscapeError`, которое доходит до клиента как `ResourceError`. ### Когда настройки по умолчанию мешают {#when-the-defaults-get-in-the-way} @@ -213,9 +213,10 @@ SDK поддерживает подмножество, подобранное д !!! tip Если обработчик не может выполнить запрос (файла нет, идентификатор - неизвестен), выбросьте исключение. SDK превратит его в ответ с - ошибкой. О разнице между ошибкой протокола и ошибкой инструмента - см. **[Обработка ошибок](handling-errors.md)**. + неизвестен), выбросьте `ResourceNotFoundError`, как делает `read_manual` + выше. Клиент получит `-32602` с вашим сообщением и URI. Непредвиденное + исключение вместо этого превращается в общую ошибку `-32603`. См. + **[Обработка ошибок](handling-errors.md#a-resource-that-doesnt-exist)**. ## Ресурсы на низкоуровневом Server {#resources-on-the-low-level-server} diff --git a/i18n/ru/pages/troubleshooting.md b/i18n/ru/pages/troubleshooting.md index 8632798445..189c9d0a0e 100644 --- a/i18n/ru/pages/troubleshooting.md +++ b/i18n/ru/pages/troubleshooting.md @@ -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 --- # Устранение неполадок {#troubleshooting} @@ -81,11 +81,11 @@ async def main() -> None: `__aexit__` — это отключение, и именно поэтому нет `client.close()`, который можно забыть вызвать. Страница **[Тестирование](get-started/testing.md)** построена ровно на этом шаблоне. -## `Error executing tool : ` и `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `, `Error executing tool ` и `Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} Перед вами **результат**, а не исключение. `call_tool` ничего не выбросил и никогда не выбросит для инструмента, завершившегося с ошибкой. -Вызовите `forecast` для города, которого сервер не знает, — и исключение, которое он выбрасывает, вернётся вместе с запросом, помеченным как *успешный*: +Вызовите `forecast` для города, которого сервер не знает, — и `ToolError`, которое он выбрасывает, вернётся вместе с запросом, помеченным как *успешный*: ```python result.is_error # True @@ -97,6 +97,8 @@ result.structured_content # None Исправление — на стороне клиента: **проверяйте `result.is_error`**. `try/except` вокруг `call_tool` не поймает ничего из этого, потому что ловить нечего. Так задумано, и это самая полезная мысль на всей странице, которую стоит усвоить: вызов выбрала *модель*, поэтому именно модель получает сообщение и шанс попробовать снова. Подробнее — на странице **[Обработка ошибок](servers/handling-errors.md)**, включая путь через `MCPError`, который *действительно* выбрасывает исключение. +Краткая форма, `Error executing tool ` без сообщения, означает, что инструмент **упал**: из него вышло исключение, которого он не предусмотрел (или возвращённое значение не прошло выходную схему), и текст этого исключения в передаваемые данные не попадает. Трассировка — в **логе сервера** на уровне `ERROR`, в виде `Tool '' 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} Вы написали `@mcp.tool` вместо `@mcp.tool()`. `tool()` — это *фабрика* декораторов: без скобок Python передаёт вашу функцию в её параметр `name=`. @@ -409,7 +411,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## Итоги {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup` — никогда не сама ошибка. Читайте **последнюю строку**; перехват `MCPError` *внутри* блока `async with Client(...)` полностью избавляет от обёртки. -* `call_tool` не выбрасывает исключение для инструмента, завершившегося с ошибкой. `Error executing tool ...` и `Unknown tool: ...` — это результаты: проверяйте `result.is_error`. +* `call_tool` не выбрасывает исключение для инструмента, завершившегося с ошибкой. `Error executing tool ...` и `Unknown tool: ...` — это результаты: проверяйте `result.is_error`. Нет сообщения после имени инструмента — значит, он упал, а трассировка в логе сервера. * `Client must be used within an async context manager` -> используйте `async with`. `Use @tool() instead of @tool` -> добавьте скобки. * `Tool already exists:` в логе сервера — единственный признак того, что два одноимённых инструмента схлопнулись в один. * Один 421, три написания: `Server returned an error response` (`Client` на Python), `421 Misdirected Request` / `Invalid Host header` (всё остальное), `Invalid Host header: ` (лог сервера). Исправление: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`. diff --git a/i18n/ru/pages/whats-new.md b/i18n/ru/pages/whats-new.md index 7eff12cc2c..2e9f324ae5 100644 --- a/i18n/ru/pages/whats-new.md +++ b/i18n/ru/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # Что нового в v2 {#whats-new-in-v2} @@ -46,9 +46,9 @@ v1 выдавала три вложенных слоя: контекстный --8<-- "docs_src/client/tutorial001.py" ``` -`Client` принимает объект сервера (в памяти, без транспорта — это сценарий для тестов), URL (Streamable HTTP) или любой контекстный менеджер транспорта, например `stdio_client(...)`. Вход в `async with` подключается и согласует версию протокола, на каком бы поколении ни говорил сервер; после этого `client.server_capabilities` и `client.protocol_version` просто доступны, как и `client.server_info`, когда сервер себя идентифицирует (теперь это `Implementation | None`, поскольку в поколении 2026 идентификация необязательна). Колбэки сэмплирования и элицитации, зарегистрированные в v1, по-прежнему работают (их тела затрагивает то же переименование атрибутов в snake_case, что и всё остальное на этой странице), теперь они ещё и отвечают на запросы внутри результатов в стиле 2026 (см. ниже) и выполняются параллельно, а не по одному. `ClientSession` по-прежнему лежит в основе для тех, кому нужна низкоуровневая поверхность, и `client.session` её отдаёт; она тоже изменилась (работает на новом движке-диспетчере, и некоторые её собственные сигнатуры поменялись), так что прежде чем спускаться на этот уровень, прочитайте **[Руководство по миграции](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**. +`Client` принимает объект сервера (в памяти, без транспорта — это сценарий для тестов), URL (Streamable HTTP), `StdioServerParameters` (подпроцесс stdio) или любой другой контекстный менеджер транспорта, например `sse_client(...)`. Вход в `async with` подключается и согласует версию протокола, на каком бы поколении ни говорил сервер; после этого `client.server_capabilities` и `client.protocol_version` просто доступны, как и `client.server_info`, когда сервер себя идентифицирует (теперь это `Implementation | None`, поскольку в поколении 2026 идентификация необязательна). Колбэки сэмплирования и элицитации, зарегистрированные в v1, по-прежнему работают (их тела затрагивает то же переименование атрибутов в snake_case, что и всё остальное на этой странице), теперь они ещё и отвечают на запросы внутри результатов в стиле 2026 (см. ниже) и выполняются параллельно, а не по одному. `ClientSession` по-прежнему лежит в основе для тех, кому нужна низкоуровневая поверхность, и `client.session` её отдаёт; она тоже изменилась (работает на новом движке-диспетчере, и некоторые её собственные сигнатуры поменялись), так что прежде чем спускаться на этот уровень, прочитайте **[Руководство по миграции](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**. -Страница **[Объект Client](client/index.md)** знакомит с ним, **[Транспорты клиента](client/transports.md)** описывает три формы подключения, **[Колбэки клиента](client/callbacks.md)** — сами колбэки, а **[Тестирование](get-started/testing.md)** показывает шаблон работы в памяти, который заменяет вспомогательную функцию `create_connected_server_and_client_session()` из v1. +Страница **[Объект Client](client/index.md)** знакомит с ним, **[Транспорты клиента](client/transports.md)** описывает четыре формы подключения, **[Колбэки клиента](client/callbacks.md)** — сами колбэки, а **[Тестирование](get-started/testing.md)** показывает шаблон работы в памяти, который заменяет вспомогательную функцию `create_connected_server_and_client_session()` из v1. ### Низкоуровневый `Server` перестроен, а не переименован {#the-low-level-server-was-rebuilt-not-renamed} @@ -125,7 +125,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB ### Настройка транспорта переехала в `run()` {#transport-configuration-moved-to-run} -`MCPServer(...)` описывает, чем ваш сервер *является*: имя, инструкции, жизненный цикл (lifespan), авторизацию. То, как он *обслуживается*, теперь относится к `run()` и сборщикам приложений — туда ушли `host`, `port`, `stateless_http`, `json_response`, пути эндпоинтов и `transport_security` (`MCPServer("x", port=9000)` — это `TypeError`). Перегрузки типизированы по транспортам, так что редактор подскажет, какие параметры принимает `stdio`, а какие — `streamable-http`. Одно удаление стоит знать: `mount_path` больше нет; поддерживаемый способ обслуживать под префиксом — монтировать ASGI-приложение. +`MCPServer(...)` описывает, *что такое* ваш сервер: его имя, инструкции, жизненный цикл (lifespan), авторизация. То, как он *обслуживается*, теперь относится к `run()` и сборщикам приложений — туда ушли `host`, `port`, `stateless_http`, `json_response`, пути эндпоинтов и `transport_security` (`MCPServer("x", port=9000)` — это `TypeError`). Перегрузки типизированы по транспортам, так что редактор подскажет, какие параметры принимает `stdio`, а какие — `streamable-http`. Одно удаление стоит знать: `mount_path` больше нет; поддерживаемый способ обслуживать под префиксом — монтировать ASGI-приложение. Параметры описаны на странице **[Запуск сервера](run/index.md)**; монтирование — на странице **[Добавление в существующее приложение](run/asgi.md)**. @@ -134,7 +134,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB Переименования заявляют о себе сами. А вот эти изменения — нет: * **Синхронные функции выполняются в рабочем потоке.** Инструмент (а также ресурс, промпт или резолвер), объявленный через `def`, больше не блокирует цикл событий; плата за это — его тело больше не выполняется *в* потоке цикла событий, что важно для кода, привязанного к потоку. Обработчики `async def` не затронуты. **[Руководство по миграции](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**. -* **`MCPError` (`McpError` в v1), выброшенный внутри инструмента, теперь ошибка протокола.** Модель его никогда не видит. Любое другое исключение по-прежнему становится результатом с `is_error=True`, который модель может прочитать и на который может отреагировать. Разграничение — на странице **[Обработка ошибок](servers/handling-errors.md)**. +* **`MCPError` (`McpError` в v1), выброшенный внутри инструмента, теперь ошибка протокола.** Модель его никогда не видит. Любое другое исключение по-прежнему становится результатом с `is_error=True`, но до модели доходит только сообщение `ToolError`: любое другое исключение теперь читается как `Error executing tool `, а трассировка попадает в лог сервера. Разграничение — на странице **[Обработка ошибок](servers/handling-errors.md)**. * **Результаты проверяются перед отправкой.** Собранный вручную `Tool`, у которого `input_schema` равна `{}`, теперь проваливает `tools/list` (спецификация требует `"type": "object"`). Серверы, построенные на `@mcp.tool()`, с этим не сталкиваются: их схемы пишет SDK. * **Ваш клиент проверяет то, что получает.** `list_tools()` и `call_tool()` сверяют ответ сервера с согласованной версией протокола, так что не совсем валидный сервер, который терпел снисходительный разбор v1, теперь вызывает `pydantic.ValidationError`. Если подключаетесь к серверам, которые не контролируете, будьте готовы оказаться тем, кто их обнаружит; подробности — в **[Руководстве по миграции](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)**. * **Шаблоны URI теперь настоящий RFC 6570.** `{+path}`, `{?query}` и им подобные работают, сопоставление точное, а не приблизительное через регулярные выражения, и обход путей в извлечённых значениях по умолчанию отклоняется. Более строгие шаблоны падают в момент декорирования, а не на первом запросе. **[Шаблоны URI](servers/uri-templates.md)**. @@ -148,7 +148,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB * **Транспорт WebSocket** с обеих сторон и дополнение `mcp[ws]`. Он никогда не входил в спецификацию MCP. * API **экспериментальных Tasks** (`mcp.*.experimental`). 2026-07-28 выносит задачи из ядра протокола в официальное расширение ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)), которое этот SDK пока не реализует. -* `mcp.shared.version`, `mcp.shared.progress` и `mcp.shared.session` (с заглушкой `RequestResponder`, которую импортировали аннотации `message_handler` в v1) как пути импорта. (`mcp.types` **не** удалён: он остаётся постоянным псевдонимом отдельного пакета `mcp_types`.) +* `mcp.shared.version`, `mcp.shared.progress` и `mcp.shared.session` (с заглушкой `RequestResponder`, которую импортировали аннотации `message_handler` в v1) как пути импорта. (`mcp.types` *не* удалён: он остаётся постоянным псевдонимом отдельного пакета `mcp_types`.) * Устаревшее написание `streamablehttp_client` и колбэк `get_session_id` у `streamable_http_client` (который теперь отдаёт ровно два потока). * `McpError`, переименованный в **`MCPError`** с прямым конструктором `(code, message, data)`. * `MCPServer.get_context()`, `mount_path=`, а также методы-декораторы, ContextVar и словари обработчиков низкоуровневого `Server`. diff --git a/i18n/tr/pages/advanced/low-level-server.md b/i18n/tr/pages/advanced/low-level-server.md index f49dd2a98c..42a7a211bc 100644 --- a/i18n/tr/pages/advanced/low-level-server.md +++ b/i18n/tr/pages/advanced/low-level-server.md @@ -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 --- # Düşük seviyeli Server {#the-low-level-server} @@ -116,6 +116,17 @@ Bu genellenebilir. Düşük seviyeli bir işleyiciden fırlatılan istisna **her Sunucu bu iki alanı asla karşılaştırmaz. Bu SDK'nın `Client`'ı karşılaştırır: bildirdiğiniz `output_schema`'yı karşılamayan bir `structured_content` döndürün, `call_tool` `Invalid structured content returned by tool search_books` ile başlayıp `jsonschema` hatasını alıntılayarak devam eden bir `RuntimeError` fırlatır. Bir şema vaat etmek ucuzdur; sözünüzü tutmak size kalır. Dönüş türleri ve şemaların tüm basamakları **[Yapılandırılmış çıktı](../servers/structured-output.md)** sayfasında. +## Lehçe: JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12} + +`input_schema` ve `output_schema` birer JSON Schema'dır ve lehçeyi [MCP belirtimi](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) sabitler: `$schema` anahtarı olmayan bir şema **JSON Schema 2020-12**'dir. `MCPServer`'ın ürettiği şemalar bu varsayılana dayanır (Pydantic 2020-12 yazar ve anahtarı koymaz); elle yazılmış bir dict de aynı kurala tabidir. Yani 2020-12 söz dağarcığının tamamı kullanılabilir: + +```python title="server.py" hl_lines="8 14-15" +--8<-- "docs_src/lowlevel/tutorial007.py" +``` + +* `input_schema`'nın kökü `"type": "object"` olmalıdır. Onun yanında `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, yerel `$ref`'lerle `$defs` ve 2020-12 anahtar sözcüklerinin geri kalanı istemciye tam yazıldığı gibi ulaşır. +* `$schema` anahtarı gerekmez. Yalnızca daha eski bir taslağı seçmek için ekleyin: `structured_content`'i bir aracın `output_schema`'sına göre doğrulayan bu SDK'nın `Client`'ı, doğrulayıcısını `$schema`'ya göre seçer ve hiç yoksa 2020-12 kullanır. + ## `_meta`: model için değil, uygulama için {#\_meta-for-the-application-not-the-model} `content`, yanıtın modelin okuduğu kısmıdır. `structured_content`, aynı yanıtın tür bilgisi taşıyan veri halidir. `_meta` üçüncü kanaldır: yanıtın hiçbir şekilde parçası olmadan, **istemci uygulama** için sonuçla birlikte yolculuk eden veri. @@ -167,7 +178,7 @@ Yapıcı, MCP'nin tanımladığı metotları kapsar. `add_request_handler` geri --8<-- "docs_src/lowlevel/tutorial006.py" ``` -* İlk argüman metot dizesidir. Bildirimlerin bir ikizi vardır: `add_notification_handler`. +* İlk argüman metot dizesidir. Bildirimlerin bir ikizi vardır: `add_notification_handler`. Onun işleyicileri stdio'da ve el sıkışma neslinden HTTP bağlantılarında tetiklenir; `2026-07-28` Streamable HTTP yolunda istemcinin bildirim POST'u `202` ile onaylanır ve işleyiciye iletilmez, çünkü o revizyon HTTP üzerinden istemciden sunucuya hiçbir bildirim tanımlamaz. * `params_type`, gelen `params`'ın işleyiciniz çalışmadan **önce** doğrulandığı modeldir; yani özel metotlar, araçların almadığı doğrulamayı *alır*. `_meta` alanının diğer her metotta olduğu gibi ayrıştırılması için `RequestParams`'tan alt sınıf türetin. * İşleyici bir `BaseModel`, bir `dict` ya da `None` döndürür. SDK bunu JSON-RPC sonucuna serileştirir. diff --git a/i18n/tr/pages/advanced/middleware.md b/i18n/tr/pages/advanced/middleware.md index 203b1e421c..05fa7b6642 100644 --- a/i18n/tr/pages/advanced/middleware.md +++ b/i18n/tr/pages/advanced/middleware.md @@ -1,6 +1,6 @@ --- translation: - sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c] + sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43] tool: 1 --- # Middleware {#middleware} @@ -21,7 +21,7 @@ Onu `async (ctx, call_next)` biçiminde yazar ve `server.middleware` listesine e ## Bir zamanlama middleware'i {#a-timing-middleware} -Bir sunucu, bir araç ve her mesajın ne kadar sürdüğünü loglayan bir middleware: +Bir sunucu, bir araç ve her mesajın ne kadar sürdüğünü log'a yazan bir middleware: ```python title="server.py" hl_lines="39-45 49" --8<-- "docs_src/middleware/tutorial001.py" @@ -38,7 +38,7 @@ Bir sunucu, bir araç ve her mesajın ne kadar sürdüğünü loglayan bir middl ### Deneyin {#try-it} -Bir istemci bağlayın, araçları listeleyin, birini çağırın. Logunuzda **üç** satır var: +Bir istemci bağlayın, araçları listeleyin, birini çağırın. Log'unuzda **üç** satır var: ```text server/discover took 18.3 ms @@ -53,8 +53,11 @@ istemeden önce, istemcinin bağlantıyı kurmak için gönderdiği istek. * Bağlantı kurulumu: `server/discover` ya da eski nesil bir oturumda `initialize` ve `notifications/initialized`. -* Her istek ve her bildirim. Bir bildirimde `ctx.request_id is None` olur, - `call_next(ctx)` `None` döndürür ve sizin döndürdüğünüz her şey atılır. +* Sunucuya ulaşan her istek ve her bildirim. Bir bildirimde + `ctx.request_id is None` olur, `call_next(ctx)` `None` döndürür ve sizin döndürdüğünüz her şey atılır. + (`2026-07-28` Streamable HTTP yolunda istemcinin bildirim POST'u aktarım katmanında `202` ile + onaylanır ve hiçbir zaman işlenmek üzere iletilmez; bu yüzden middleware'e de ulaşmaz. O revizyon + HTTP üzerinden istemciden sunucuya hiçbir bildirim tanımlamaz.) * Sunucunun işleyicisi olmayan bir metot bile: `call_next`, `MCPError(-32601, "Method not found")` istisnasını istemciye giderken middleware'inizin *içinden* fırlatır. @@ -110,7 +113,7 @@ aklınıza bile gelmez. Bir exporter kurana kadar hiçbir şey yapmaz ve kendi s * Bir middleware `async (ctx, call_next) -> result` biçimindedir; `MCPServer(middleware=[...])` olarak geçirilir (ya da `mcp.middleware` listesine eklenir), alt düzey `Server`'da ise `server.middleware` listesine eklenir. -* Gelen **her** mesajı sarar (`server/discover`, `initialize`, istekler, bildirimler, +* Sunucuya ulaşan **her** gelen mesajı sarar (`server/discover`, `initialize`, istekler, bildirimler, bilinmeyen metotlar) ve dıştan içe doğru çalışır. * Bir bildirimi bir istekten `ctx.request_id is None` ile ayırt edersiniz. * Tek bir mesajı reddetmek için `call_next`'i çağırmak yerine istisna fırlatın; bağlantı ayakta kalır. diff --git a/i18n/tr/pages/client/index.md b/i18n/tr/pages/client/index.md index 45c9503bd6..2cc12da86a 100644 --- a/i18n/tr/pages/client/index.md +++ b/i18n/tr/pages/client/index.md @@ -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 --- # İstemci {#the-client} @@ -27,9 +27,10 @@ Tek bir yaşam döngüsü olan tek bir nesnedir: oluşturun, `async with` bloğu * Bir `MCPServer` (veya düşük seviyeli `Server`) örneği: **süreç içinde** bağlanır. * Bir URL dizesi (`Client("http://localhost:8000/mcp")`): Streamable HTTP, yani üretim yolu. -* Bir **aktarım**: `async with ... as (read, write)` ile kullanabileceğiniz herhangi bir şey; örneğin bir alt süreci saran `stdio_client(...)`. +* Bir `StdioServerParameters`: **alt süreç** olarak başlatılacak komut; onunla stdin ve stdout'u üzerinden konuşulur. +* Bir **aktarım**: `async with ... as (read, write)` ile kullanabileceğiniz herhangi bir şey; örneğin kendi HTTP istemcinizi saran `streamable_http_client(url, http_client=...)`. -Bu sayfadaki geri kalan her şey üçünde de aynıdır. Başlıklar, alt süreçler, zaman aşımları ve `Transport` protokolünün kendi sayfası var: **[İstemci aktarımları](transports.md)**. +Bu sayfadaki geri kalan her şey dördünde de aynıdır. Başlıklar, alt süreçler, zaman aşımları ve `Transport` protokolünün kendi sayfası var: **[İstemci aktarımları](transports.md)**. ### Bağlı bir istemcide bulunanlar {#whats-on-a-connected-client} @@ -85,7 +86,7 @@ Bu şema, bir arayüzün argüman formu oluşturması için gereken her şeydir; `call_tool(name, arguments)` aracı çalıştırır ve size bir `CallToolResult` geri verir. -```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 @@ Tek dönüş değeri, okunacak üç şey. Her birinin tüketicisi farklı. !!! check `lookup_book`'tan `"Solaris"`'i isteyin (katalogda olmayan bir başlık); fonksiyon - `ValueError` fırlatır. Çağrı yine de normal biçimde döner: + `ToolError` fırlatır. Çağrı yine de normal biçimde döner: ```python result.is_error # True @@ -125,9 +126,10 @@ Tek dönüş değeri, okunacak üç şey. Her birinin tüketicisi farklı. result.structured_content # None ``` - İstisnanın mesajı `content`'e düştü; **model** onu orada okuyup yeniden deneyebilir. Bu - kasıtlıdır: bir araç hatası çökme değil, konuşmanın bir parçasıdır. `structured_content`'e - güvenmeden önce her zaman `is_error`'a bakın. + `ToolError`'ın mesajı `content`'e düştü; **model** onu orada okuyup yeniden deneyebilir. Bu + kasıtlıdır: bir araç hatası çökme değil, konuşmanın bir parçasıdır. (Araç başka bir istisnayla + çökmüş olsaydı `content`'te yalnızca `Error executing tool lookup_book` yazardı.) + `structured_content`'e güvenmeden önce her zaman `is_error`'a bakın. !!! warning `is_error=True`, kendi `raise`'inizden fazlasını kapsar. Sunucuda hiç olmayan bir araç isteyin diff --git a/i18n/tr/pages/client/transports.md b/i18n/tr/pages/client/transports.md index 967b5a73d8..552ede3095 100644 --- a/i18n/tr/pages/client/transports.md +++ b/i18n/tr/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # İstemci aktarımları {#client-transports} @@ -86,15 +86,15 @@ ortam değişkenlerini ayarlayın ya da `httpx2.AsyncClient`'ınıza açıkça b Bir **stdio** sunucusu bir alt süreçtir. İstemci onu başlatır, stdin'ine JSON-RPC yazar ve stdout'undan JSON-RPC okur. Bir masaüstü host'un makinenizde bir sunucuyu çalıştırma biçimi budur: bir host, bu kod artı bir kullanıcı arayüzü*dür* ve **[Gerçek bir host'a bağlanma](../get-started/real-host.md)**, aynı ilişkinin host'un tarafından, bir yapılandırma dosyası olarak görülen halidir. -Süreci `StdioServerParameters` ile tanımlayın, `stdio_client` ile bir aktarıma dönüştürün ve `Client`'a *onu* verin: +Süreci `StdioServerParameters` ile tanımlayın ve `Client`'a verin: -```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`, parametre nesnesini tek başına kabul etmez. `StdioServerParameters` yapılandırmadır; `stdio_client(server)` ise ondan bir süreç başlatmayı bilen aktarımdır. Her zaman sarın. +Bloğa girmek süreci başlatır. Bloktan çıkmak alt süreci kapatır: stdin'i kapatır, bekler, oyalanıyorsa sonlandırır. Onu hiçbir zaman kendiniz temizlemezsiniz. -`async with` bloğundan çıkmak alt süreci de kapatır: stdin'i kapat, bekle, oyalanıyorsa sonlandır. Onu hiçbir zaman kendiniz temizlemezsiniz. +Alt sürecin stderr'i sizinkine gider. Başka bir yere göndermek için aktarımı `stdio_client` ile (`mcp` içinden) kendiniz oluşturun ve onun yerine bunu geçirin: `Client(stdio_client(server, errlog=log_file))`. !!! warning Alt süreç ortamınızı **devralmaz**. Minimal bir izin listesi alır (POSIX'te `HOME`, `LOGNAME`, @@ -112,16 +112,16 @@ Süreci `StdioServerParameters` ile tanımlayın, `stdio_client` ile bir aktarı `Client` için yukarıdakilerin hepsi aynı şeydir. -Bir **aktarım**, `(read, write)` mesaj akışı çifti veren herhangi bir asenkron bağlam yöneticisidir: resmi olarak `mcp.client` içindeki `Transport` protokolü. `Client`, argümanını türüne göre çözümler: bir sunucu nesnesi süreç içinde bağlanır, bir `str` `streamable_http_client(url)` olur ve geri kalan her şeye doğrudan bir aktarım olarak girilir. `stdio_client(...)`, `streamable_http_client(...)` ve `sse_client(...)`'in hepsinin aynı yuvaya oturmasının ve kendinizinkini yazabilmenizin nedeni bu son kuraldır. +Bir **aktarım**, `(read, write)` mesaj akışı çifti veren herhangi bir asenkron bağlam yöneticisidir: resmi olarak `mcp.client` içindeki `Transport` protokolü. `Client`, argümanını türüne göre çözümler: bir sunucu nesnesi süreç içinde bağlanır, bir `str` `streamable_http_client(url)` olur, bir `StdioServerParameters` `stdio_client(params)` olur ve geri kalan her şeye doğrudan bir aktarım olarak girilir. `stdio_client(...)`, `streamable_http_client(...)` ve `sse_client(...)`'in hepsinin aynı yuvaya oturmasının ve kendinizinkini yazabilmenizin nedeni bu son kuraldır. ## Özet {#recap} * `Client(mcp)` (sunucu nesnesi) bellek içinde bağlanır. Testler ve gömme için kullanın. * `Client("http://.../mcp")` (bir URL), üretim aktarımı olan Streamable HTTP üzerinden bağlanır. * Başlıklar, kimlik doğrulama, vekil sunucular ve zaman aşımları, `streamable_http_client(url, http_client=...)`'a geçirdiğiniz bir `httpx2.AsyncClient` üzerinde yer alır. `headers=` anahtar sözcüğü yoktur. -* stdio `Client(stdio_client(StdioServerParameters(...)))`'tır; asla tek başına parametre nesnesi değil. +* stdio, `Client(StdioServerParameters(...))` demektir. Onu `stdio_client(...)` ile yalnızca alt sürecin stderr'ini başka yere yönlendirmek için kendiniz sarın. * Alt süreç sizinkini değil, izin listesine göre oluşturulmuş bir ortam alır; `env=` buna ekleme yapar. -* Bir aktarım, `async with x as (read, write)` yapabildiğiniz herhangi bir şeydir. `Client`, sunucu nesnesi ya da URL olmayan her şeyi doğrudan bu protokole verir. +* Bir aktarım, `async with x as (read, write)` yapabildiğiniz herhangi bir şeydir. `Client`, sunucu nesnesi, URL ya da `StdioServerParameters` olmayan her şeyi doğrudan bu protokole verir. * Bir `Client` oluşturmak aktarımı seçer. Onu `async with` açar. Aktarım açıldıktan sonra iki tarafın bir protokol sürümünde anlaşması gerekir. Normalde bunu hiç düşünmezsiniz; düşünmeniz gerektiğinde gidilecek sayfa **[Protokol sürümleri](../protocol-versions.md)**'dir. diff --git a/i18n/tr/pages/deprecated.md b/i18n/tr/pages/deprecated.md index 75acd06964..af116483ee 100644 --- a/i18n/tr/pages/deprecated.md +++ b/i18n/tr/pages/deprecated.md @@ -1,11 +1,11 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # Kullanım dışı özellikler {#deprecated-features} -2026-07-28 spesifikasyonu beş şeyi emekliye ayırıyor. SDK hâlâ hepsini uygular ve artık her biri bir **kullanım dışı bırakma uyarısı** taşır. +2026-07-28 spesifikasyonu beş şeyi emekliye ayırıyor. SDK hâlâ hepsini uygular ve artık her biri bir **kullanım dışı bırakma uyarısı** taşır. Bir SDK yardımcısı ise kendi gerekçesiyle kullanım dışı bırakıldı ve [sayfanın sonunda](#deprecated-sdk-helpers) listeleniyor. Aşağıdaki tablo kullanım dışı bırakılan her özelliği, neden gittiğini ve yerine neyin üzerine inşa etmeniz gerektiğini gösterir. @@ -55,6 +55,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE Bu ikisi yalnızca, istemcisi eşleşen callback'i kaydetmiş bir `mode="legacy"` bağlantısında uçtan uca çalışır. +## Eski nesil oturumda `ping` {#ping-on-a-legacy-session} + +**Ping**, iki taraftan herhangi birinin karşı tarafın hâlâ yanıt verip vermediğini kontrol etmek için gönderebildiği boş bir istektir. 2026-07-28 spesifikasyonu onu kaldırır ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)): modern bir istemcinin gönderdiği her istek sunucunun orada olduğunu zaten kanıtlar, modern bir sunucunun ise ping gönderecek bir kanalı yoktur. Her iki SDK yöntemi de el sıkışma neslinden bir oturumda hâlâ çalışır. İstemciden: + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +Sunucudan ise herhangi bir işleyicinin içinde: + +```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()` her çağrıda `MCPDeprecationWarning` ile uyarır. Varsayılan (`2026-07-28`) bağlantıda sunucu bunun yerine `MCPError: Method not found` yanıtını verir. +* `ctx.session.send_ping()` uyarı taşımaz. Modern bir bağlantıda, sunucunun başlattığı diğer tüm istekler gibi aynı geri kanal yok (back-channel) hatasını fırlatır. +* İki taraf da ping'e yanıt vermek için herhangi bir şey kaydetmez. + +## Kök dizin değişikliği bildirimleri {#roots-change-notifications} + +Kök dizinler yeteneğini bildirmiş 2025 neslinden bir istemci, çalışma alanı klasörlerinin değiştiğini sunucuya `notifications/roots/list_changed` göndererek söyleyebilir; sunucu da karşılık olarak `roots/list`'i yeniden ister. 2026-07-28 spesifikasyonu bu bildirimi, push tarzı kök dizin akışının geri kalanıyla birlikte kaldırır. İstemcide `"roots": {"listChanged": true}` bildirimini yapan şey `list_roots_callback=` geçirmektir (**[İstemci callback'leri](client/callbacks.md)**) ve tek bir çağrı bu sözü tutar: + +```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() +``` + +Sunucuda alıcı işleyiciyi düşük seviyeli `Server` alır: + +```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`, `list_roots_callback`'inizin döndürdüğü listedir. `client.send_roots_list_changed()` uyarır ve `mode="legacy"` bir istemci gerektirir: modern bir bağlantıda bildirim sessizce düşürülür. Ardından oturumu açık tutun, çünkü sunucunun takip eden `roots/list` isteği o oturum üzerinden gelir. +* `MCPServer`'da bu bildirim için bir kanca yoktur. Düşük seviyeli `Server`'da işleyiciyi `on_roots_list_changed=` kaydeder (o da kullanım dışıdır ve oluşturma sırasında uyarır). Bildirim yük taşımaz, bu yüzden işleyici yeni liste için `ctx.session.list_roots()`'u çağırır. + ## Uyarıyı susturma {#silencing-the-warning} Yeni kodda susturmayın. @@ -75,22 +124,32 @@ API'nin tamamı bu. Yöntem başına bir anahtar yok, zaten istemezsiniz de: tek Filtreyi ters yönde çalıştırın, bedava bir regresyon testi elde edersiniz. pytest yapılandırmanızdaki `filterwarnings` ayarına `"error::mcp.MCPDeprecationWarning"` ekleyin; kullanım dışı çağrı uyarmak yerine **istisna fırlatır**. Hâlâ `ctx.info()`'yu - çağıran `old_log` adlı bir araç artık geçmez ve şunu bildirmeye başlar: + çağıran `old_log` adlı bir araç artık geçmez: çağrı `Error executing tool old_log` ile + `is_error=True` olarak döner ve yakalanan sunucu log'u suçluyu adıyla gösterir: ```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). ``` Tek satır pytest yapılandırmasıyla, kullanım dışı bir çağrı bir testi başarısız kılmadan kod tabanınıza bir daha asla sızamaz. +## Kullanım dışı SDK yardımcıları {#deprecated-sdk-helpers} + +Bunlar spesifikasyon değişikliği değil, yalnızca daha iyi bir alternatifi olan SDK iç ayrıntılarıdır. Aynı `MCPDeprecationWarning` ile uyarırlar ve 3.0'da kaldırılacaklar. + +| Kullanım dışı | Bunun yerine ne yaparsınız | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()`, ardından `FuncMetadata.call_fn()`. Bunu yalnızca `FuncMetadata`'yı doğrudan kullanan kod (örneğin özel bir `Tool` alt sınıfı) çağırırdı. | + ## Özet {#recap} * 2026-07-28 spesifikasyonu **kök dizinleri**, sunucunun başlattığı **örneklemeyi** ve protokol üzerinden **log tutmayı** kullanım dışı bırakır (hepsi [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), **ilerlemeyi** sunucudan istemciye yönle sınırlar ve **`ping`**'i kaldırır. * Yerine geçenler sütunu sizi ileriye yönlendirir: örnekleme ve kök dizinler için **[Çok turlu istekler](handlers/multi-round-trip.md)**, log tutma için **[Log tutma](handlers/logging.md)**, ilerleme için **[İlerleme](handlers/progress.md)**. `ping` için hiçbir şey gerekmez. * Kullanım dışı bırakma tavsiye niteliğindedir: iletilen veride değişiklik yok, her şey 2026 öncesi oturumlarda çalışmaya devam eder ve görünür bir `MCPDeprecationWarning` alırsınız (bir `UserWarning`, dolayısıyla varsayılan olarak açık). -* Örnekleme ve kök dizinler ayrıca, 2026-07-28 oturumunda bulunmayan bir geri kanala (back-channel) ihtiyaç duyar. Modern bir bağlantıda önce uyarır, sonra istisna fırlatırlar. +* Örnekleme ve kök dizinler ayrıca, 2026-07-28 oturumunda bulunmayan bir geri kanala ihtiyaç duyar. Modern bir bağlantıda önce uyarır, sonra istisna fırlatırlar. * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` tüm kategoriyi susturur; pytest'te `"error::mcp.MCPDeprecationWarning"` bunu bir test hatasına dönüştürür. +* Bir SDK yardımcısı, `FuncMetadata.call_fn_with_arg_validation()`, 3.0'da kaldırılmak üzere ayrıca kullanım dışı bırakıldı. * Yeni kod bunların hiçbiri üzerine kurulmamalıdır. Bu belgelerdeki diğer tüm sayfalar güncel API'yi anlatır. diff --git a/i18n/tr/pages/get-started/real-host.md b/i18n/tr/pages/get-started/real-host.md index e940100e44..b76e4c7c67 100644 --- a/i18n/tr/pages/get-started/real-host.md +++ b/i18n/tr/pages/get-started/real-host.md @@ -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 --- # Gerçek bir host'a bağlanma {#connect-to-a-real-host} @@ -11,15 +11,15 @@ Yani bir host'a bağlanmak tek bir eylemdir: ona **sunucunuzu başlatan komutu** ## Tek sunucu, her 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" ``` İki araç ve bir kaynak, tek dosya. Bu dosyayla ilgili üç şey aşağıdaki her host için önemlidir: -* Argümansız `mcp.run()` bir **stdio** sunucusu başlatır: bloklar, protokol mesajlarını stdin'den okur ve stdout'a yazar. Bu sayfadaki her host'un konuştuğu aktarım budur. Host dosyanızı bir alt süreç olarak başlatır ve bu iki kanalın sahibidir; bağlanmanın her zaman yalnızca "işte komut" olmasının nedeni de budur. Hiçbir zaman port seçmezsiniz ve hiçbir şey bir portu dinlemez. -* `run()`, `if __name__ == "__main__":` altındadır. Aşağıdaki her şey bu dosyayı çalıştırmak yerine **import eder**; bu yüzden korumasız bir `run()`, modülü herhangi bir şey yüklediği anda bir sunucu başlatırdı. -* Sunucu nesnesi, `mcp` adında modül düzeyinde bir globaldir. `mcp run`'ın aradığı ad budur (`server` ve `app` de olur). Başka bir ad verirseniz açıkça belirtirsiniz: `mcp run server.py:bookshop`. +* Argümansız `mcp.run()` bir **stdio** sunucusu başlatır: bloke olur, protokol mesajlarını stdin'den okur ve stdout'a yazar. Bu sayfadaki her host'un konuştuğu aktarım budur. Host dosyanızı bir alt süreç olarak başlatır ve bu iki kanalın sahibidir; bağlanmanın her zaman yalnızca "işte komut" olmasının nedeni de budur. Hiçbir zaman port seçmezsiniz ve hiçbir şey bir portu dinlemez. +* `run()`, `if __name__ == "__main__":` altındadır. Aşağıdaki her şey bu dosyayı çalıştırmak yerine **import eder**; bu yüzden korumasız bir `run()`, herhangi bir şey modülü yüklediği anda bir sunucu başlatırdı. +* Sunucu nesnesi, `mcp` adında modül düzeyinde bir globaldir. `mcp run`'ın aradığı ad budur (`server` ve `app` de olur). Başka bir ad verirseniz adı açıkça belirtirsiniz: `mcp run server.py:bookshop`. Bu, bu sayfadaki son Python satırı. Buradan aşağısı tamamen host yapılandırması. @@ -33,7 +33,7 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py Hepsi için tek komut, çünkü `uv run --with` SDK'yı anında yeni bir ortama çözümler: herhangi bir dizinden çalışır, ne bir projeye ne de etkinleştirilecek bir sanal ortama ihtiyaç duyar. Bu, burada başka her yerden daha önemlidir; çünkü host sunucunuzu sizin kabuğunuzdan değil, *kendi* çalışma dizininden ve neredeyse boş bir ortamla başlatır. -Bu aynı zamanda `mcp install`'un sizin için Claude Desktop'ın yapılandırmasına yazdığı komuttur (aşağıda). Böylece elle yazdığınız ile aracın ürettiği, aracın eklediği tam sürüm sabitlemesi dışında örtüşür. +Bu aynı zamanda `mcp install`'un sizin için Claude Desktop'ın yapılandırmasına yazdığı komuttur (aşağıda). Yani elle yazdığınız ile aracın ürettiği, aracın eklediği tam sürüm sabitlemesi dışında örtüşür. !!! tip "Host `uv`'yi bulamazsa" Host sunucunuzu asgari bir `PATH` ile başlatır ve `uv` bunun üzerinde olmayabilir. Yalın @@ -43,15 +43,15 @@ Bu aynı zamanda `mcp install`'un sizin için Claude Desktop'ın yapılandırmas !!! note "Bu sayfa yerel senaryoyu anlatır" Buradaki her şey sunucunuzu host'un bulunduğu makinede çalıştırır: host dosyanızı stdio üzerinden başlatır. Kişisel ya da tek makinelik bir araç için bu tam olarak doğru olandır. - Dosyanıza sahip *olmayan* insanlara bir sunucu vermek için komut değil **URL** dağıtırsınız: + Dosyanız *olmayan* insanlara bir sunucu vermek için komut değil **URL** dağıtırsınız: aynı `mcp` nesnesi, Streamable HTTP üzerinden sunulur. **[Sunucunuzu çalıştırma](../run/index.md)** bu kararı tek bir tabloda verir, **[Dağıtım ve ölçekleme](../run/deploy.md)** ise oradan gerçek bir ana bilgisayar adına giden yoldur. - Ve host, içinde bir MCP istemcisi olan bir uygulamadan başka bir şey değildir; bu yüzden kendi + Host da içinde bir MCP istemcisi olan bir uygulamadan başka bir şey değildir; bu yüzden kendi Python kodunuz host rolünü oynayabilir: **[İstemci aktarımları](../client/transports.md)** - bu aynı dosyayı `stdio_client(...)` ile bir alt süreç olarak başlatır, **[Test etme](testing.md)** - ise ona hiç süreç olmadan bellek içinde bağlanır. + bu aynı dosyayı `Client(StdioServerParameters(...))` ile bir alt süreç olarak başlatır, + **[Test etme](testing.md)** ise ona hiç süreç olmadan bellek içinde bağlanır. ## Claude Desktop {#claude-desktop} diff --git a/i18n/tr/pages/get-started/testing.md b/i18n/tr/pages/get-started/testing.md index 5683ee57c5..a5ce1e0c74 100644 --- a/i18n/tr/pages/get-started/testing.md +++ b/i18n/tr/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # Test etme {#testing} @@ -85,9 +85,9 @@ async def test_call_add_tool(client: Client): İki farklı şey ters gidebilir ve bu bayrak yalnızca birine dokunur. **Araçlarınızdan** birinin içindeki bir istisna, protokol hatası değildir. `is_error=True` taşıyan -normal bir sonuca dönüşür ve model mesajı okur. `raise_exceptions` bunu değiştirmez: onunla da -onsuz da `call_tool` aynı `is_error=True` sonucunu döndürür. Bu konuda ayrı bir sayfa var: -**[Hataları ele alma](../servers/handling-errors.md)**. +normal bir sonuca dönüşür (istisna bir `ToolError` ise model sizin mesajınızı okur). `raise_exceptions` +bunu değiştirmez: onunla da onsuz da `call_tool` aynı `is_error=True` sonucunu döndürür. Bu konuda +ayrı bir sayfa var: **[Hataları ele alma](../servers/handling-errors.md)**. Araç gövdesinin **dışındaki** bir hata ise farklıdır. `Client(mcp)`'nin size verdiği bağlantıda sunucu, istemci görmeden önce onu genel bir `"Internal server error"` mesajına dönüştürerek diff --git a/i18n/tr/pages/handlers/elicitation.md b/i18n/tr/pages/handlers/elicitation.md index 9202175190..a1123316fb 100644 --- a/i18n/tr/pages/handlers/elicitation.md +++ b/i18n/tr/pages/handlers/elicitation.md @@ -1,6 +1,6 @@ --- translation: - sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] + sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] tool: 1 --- # Elicitation {#elicitation} @@ -90,7 +90,8 @@ Bu şema formun kendisidir. `Field(description=...)` etikettir; bir varsayılan Bir elicitation şeması, bir aracın girdi şeması kadar ifade gücüne sahip değildir. Yalnızca düz, ilkel alanlar: `str`, `int`, `float`, `bool` veya dizelerden oluşan bir `Literal` (bir `enum`'a dönüşür). Modelin içine bir model koyarsanız `ctx.elicit`, istemciye hiçbir - şey gönderilmeden önce istisna fırlatır: + şey gönderilmeden önce istisna fırlatır. Araç çağrısı `Error executing tool ` ile + başarısız olur, nedeni de sunucu log'unuzdadır: ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -113,8 +114,8 @@ Ret bir hata değildir. Reddetmenin ne anlama geldiğine araç karar verir (bura !!! tip Yanıt, kodunuz görmeden önce modelinize göre doğrulanır. Bir `bool` için `"maybe"` gönderen - bir istemci rezervasyonunuzu bozmaz: çağrı bir şema uyuşmazlığı hatasıyla başarısız olur, - `if`'iniz hiç çalışmaz. + bir istemci rezervasyonunuzu bozmaz: `ctx.elicit` `ValueError` fırlatır, çağrı başarısız + olur ve `if`'iniz hiç çalışmaz. ## Kullanıcıyı bir URL'ye gönderme {#send-the-user-to-a-url} diff --git a/i18n/tr/pages/handlers/logging.md b/i18n/tr/pages/handlers/logging.md index 55cd045cc0..b484c2df66 100644 --- a/i18n/tr/pages/handlers/logging.md +++ b/i18n/tr/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # Log tutma {#logging} @@ -55,6 +55,8 @@ Varsayılan değer `"INFO"`. `logging.basicConfig()` hâlihazırda var olan işleyicileri asla değiştirmez. Sunucuyu oluşturmadan önce log yapılandırmasını kendiniz yaparsanız sizin yapılandırmanız geçerli olur. +Yalnızca hataları kaydetmek için her işleyiciye bir `try`/`except` koymanız da gerekmez. Bir araç ya da kaynak fonksiyonu istisna fırlattığında SDK bunu sizin yerinize loglar. Neyin hangi düzeyde loglandığını **[Hataları ele alma](../servers/handling-errors.md#any-other-exception)** sayfası açıklar. + ## Deneyin {#try-it} Sunucuyu MCP Inspector ile çalıştırın: diff --git a/i18n/tr/pages/run/index.md b/i18n/tr/pages/run/index.md index 541ab79cc7..b4c206212c 100644 --- a/i18n/tr/pages/run/index.md +++ b/i18n/tr/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # Sunucunuzu çalıştırma {#running-your-server} @@ -72,7 +72,7 @@ Her aktarımın kendi anahtar sözcük argümanları vardır ve hepsi `run()` ü * `streamable_http_path`: MCP endpoint'inin bulunduğu yol. Varsayılan `/mcp`. * `json_response=True`: her POST'a SSE akışı yerine tek bir JSON gövdesiyle yanıt verir. Bu gövdede yanıttan başka hiçbir şeye yer yoktur; bu yüzden istek sırasında istemciye geri çağrı yapan bir araç (`ctx.elicit()`, örnekleme (sampling)) bu ayakta `NoBackChannelError` fırlatır ve sürmekte olan çağrıya bağlı bildirimler (`ctx.report_progress()` ile bildirilen ilerleme, çağrıya özel log mesajları) düşürülür; bağımsız `GET` akışı ilgisiz olanları taşımaya devam eder. * `stateless_http=True`: istek başına yeni bir aktarım, oturum takibi yok. -* `max_request_body_size`: bayt cinsinden kabul edilen en büyük POST gövdesi. Varsayılan olarak 4 MiB; +* `max_request_body_size`: bayt cinsinden kabul edilen en büyük istek gövdesi. Varsayılan olarak 4 MiB; daha büyük istekler, ayrıştırma veya oturum oluşturma öncesinde HTTP 413 alır. Bunu yalnızca meşru MCP mesajları bu boyutu aştığında yükseltin. * `event_store`, `retry_interval`, `transport_security`: kaldığı yerden devam edebilme ve DNS rebinding koruması. localhost dışında bir yere dağıtım yapana kadar bekleyebilirler; `transport_security` konusunu **[Dağıtım ve ölçekleme](deploy.md)** ele alır. diff --git a/i18n/tr/pages/servers/handling-errors.md b/i18n/tr/pages/servers/handling-errors.md index 79fe2a8639..2632700d84 100644 --- a/i18n/tr/pages/servers/handling-errors.md +++ b/i18n/tr/pages/servers/handling-errors.md @@ -1,13 +1,13 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # Hataları ele alma {#handling-errors} -Bir araç iki şekilde başarısız olabilir ve SDK bu ikisini çok farklı ele alır. +Bir araç üç şekilde başarısız olabilir ve SDK her birini farklı ele alır. -Sıradan bir istisna fırlatırsanız bunu **model** görür. `MCPError` fırlatırsanız bunu **protokol** görür. +`ToolError` fırlatırsanız mesajınızı **model** görür. `MCPError` fırlatırsanız bunu **protokol** görür. Başka herhangi bir şey fırlatırsanız bu bir çökmedir: model yalnızca çağrının başarısız olduğunu öğrenir, traceback ise log'unuza düşer. Bu sayfa, hangisini seçeceğinizle ilgili. @@ -15,11 +15,11 @@ Bu sayfa, hangisini seçeceğinizle ilgili. Bir şeyi arayıp bulan bir araç düşünün; arama sonuçsuz kalsın: -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -Bu iki satırda MCP'ye özgü hiçbir şey yok. `get_author`, herhangi bir Python fonksiyonunun yapacağı gibi düz bir `ValueError` fırlatır. +`mcp.server.mcpserver.exceptions` içindeki `ToolError`, bir aracın modele bir şeylerin ters gittiğini söyleme yoludur. Katalogda olmayan bir başlıkla çağırın ve sonuca bakın: @@ -30,13 +30,15 @@ result.structured_content # None ``` * İstek **başarılı oldu**. Ortada bir sonuç var; çağıran tarafta hiçbir şey fırlatılmadı. -* `is_error` değeri `True`; istisnanızın mesajı (başına araç adı eklenmiş olarak) `content`'te, tam da modelin okuduğu yerde. +* `is_error` değeri `True`; mesajınız (başına araç adı eklenmiş olarak) `content`'te, tam da modelin okuduğu yerde. * `structured_content` değeri `None`. Başarısız bir çağrının yapılandırılacak bir dönüş değeri yoktur. -Bu bir **araç hatasıdır** ve aracınızın fırlattığı *her* istisna için varsayılan davranış budur. Neredeyse her zaman istediğiniz şey de budur. +Bu bir **araç hatasıdır** ve neredeyse her zaman istediğiniz şey de budur. Aracınızı çağıran modeldir. Argümanları o seçti. Bu yüzden araç hatası, konuşmada bir tur demektir: model *"No book titled 'Nothing' in the catalog."* mesajını okur, başlığı yanlış tahmin ettiğini anlar ve daha iyi bir başlıkla tekrar çağırır. Tek bir `raise` yazdınız ve kendi kendini düzelten bir ajan elde ettiniz. +Sunucuda bir `ToolError`, log'da tek bir `INFO` satırıdır; traceback yoktur. Bunu zaten bekliyordunuz, bu yüzden araştırılacak bir şey yok. + !!! tip Bir araçtan hata mesajını asla `return` ile döndürmeyin. Döndürülen bir dizenin `is_error=False` değeri vardır; bu yüzden modele (ve her istemci arayüzüne) araç çalışmış ve yanıt o dizeymiş gibi görünür. @@ -44,7 +46,7 @@ Aracınızı çağıran modeldir. Argümanları o seçti. Bu yüzden araç hatas ## Modelin düzeltemeyeceği bir hata {#an-error-the-model-cannot-fix} -Şimdi `ValueError` yerine `MCPError` koyun. +Şimdi `ToolError` yerine `MCPError` koyun. ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -77,10 +79,10 @@ Aracınızı çağıran modeldir. Argümanları o seçti. Bu yüzden araç hatas İki yol, iki farklı soruyu yanıtlar. -* *Yürütme* başarısızlığı için **herhangi bir istisna fırlatın**: aracınızın yapmaya çalıştığı şey işe yaramadı. Çağrıyı model seçti, bu yüzden sonucunu da model görmeli ve toparlanma şansı bulmalı. Yanlış yazılmış bir başlık, zaman aşımına uğrayan bir dış API, var olmayan bir satır: hepsi araç hatası. +* *Yürütme* başarısızlığı için **`ToolError` fırlatın**: aracınızın yapmaya çalıştığı şey işe yaramadı. Çağrıyı model seçti, bu yüzden sonucunu da model görmeli ve toparlanma şansı bulmalı. Yanlış yazılmış bir başlık, zaman aşımına uğrayan bir dış API, var olmayan bir satır: hepsi araç hatası. * *İsteğin kendisi* reddedilmesi gerektiğinde **`MCPError` fırlatın**: istemcide aracınızın bağımlı olduğu bir yetenek eksik, sunucu kimseye hizmet verecek durumda değil, çağıran taraf zorunlu bir adımı atlamış. Modelin hiçbir yeniden denemesi bunları düzeltmez; bu yüzden mesajı ona vermenin bir kazancı yok. -Kararı tek bir soru verir: **daha akıllı bir model bundan kaçınabilir miydi?** Evet -> sıradan istisna. Hayır -> `MCPError`. +Kararı tek bir soru verir: **daha akıllı bir model bundan kaçınabilir miydi?** Evet -> `ToolError`. Hayır -> `MCPError`. Bu ölçüte göre `get_author`'ın ikinci sürümü yanlış seçim yaptı: daha iyi bir başlık sorunu çözer, yani model mesajı görmeyi hak ediyordu. O sürüm size mekanizmayı göstermek için orada, onu önermek için değil. @@ -89,6 +91,25 @@ Bu ölçüte göre `get_author`'ın ikinci sürümü yanlış seçim yaptı: dah bir `data` yükü alır. Bunlara ne koyarsanız istemci onu alır: SDK, fırlatılan bir `MCPError`'ı temizlemek yerine olduğu gibi iletir. +## Başka herhangi bir istisna {#any-other-exception} + +Şimdi denetimi çıkarın ve sözlük aramasının kendi kendine başarısız olmasına izin verin: + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]`, `KeyError` fırlatır. Bunu planlamadınız, bu yüzden SDK onu bir çökme olarak ele alır: + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +Çağrı yine `is_error=True` döndürür; yani model başarısız olduğunu bilir ve yoluna devam edebilir. Almadığı şey istisnanın metnidir: kodunuzdan gelen bir `KeyError` ya da üç kütüphane alttaki bir sürücüden gelen bir yığın SQL, sunucunuzun iç yapısını ele verebilir; bu yüzden sunucudan asla çıkmaz. + +Onu siz alırsınız. Sunucu çökmeyi tam traceback ile `ERROR` düzeyinde, `Tool 'get_author' raised an unexpected exception` olarak log'a yazar. Bu yüzden `WARNING` düzeyindeki bir üretim log'u her `ToolError` boyunca sessiz kalır ve bir şey gerçekten bozulduğu anda sesini çıkarır. + ## Var olmayan bir kaynak {#a-resource-that-doesnt-exist} Kaynaklar da aynı çizgiyi çeker ve yaygın durum için adlandırılmış bir istisna sunar. @@ -109,7 +130,7 @@ Yanıtlayamadığında `ResourceNotFoundError` fırlatın. SDK bunu, spesifikasy } ``` -Burada `is_error=True` taşıyan yarım bir sonuç olmadığına dikkat edin. Bir kaynak okuması ya içerik döndürür ya da başarısız olur: kaynakların yalnızca protokol yolu vardır. Şablonlar ve kaynaklarla ilgili diğer her şey **[Kaynaklar](resources.md)** sayfasında. +Burada `is_error=True` taşıyan yarım bir sonuç olmadığına dikkat edin. Bir kaynak okuması ya içerik döndürür ya da başarısız olur: kaynakların yalnızca protokol yolu vardır. `ResourceError`, "bulunamadı" olmayan bir başarısızlık için aynı şeydir (`-32603`, sizin mesajınız); ikisi de log'unuzda tek bir `INFO` satırıdır. `MCPError` dışındaki diğer her istisna bir çökmedir: istemci yalnızca URI'yi belirten `-32603` alır, traceback ise `ERROR` düzeyinde log'unuza gider. Şablonlar ve kaynaklarla ilgili diğer her şey **[Kaynaklar](resources.md)** sayfasında. ## Hiç fırlatmadığınız hatalar {#errors-you-never-raise} @@ -120,19 +141,21 @@ Hatalı bir argüman fonksiyonunuza asla ulaşmaz. Bu, yazmadığınız koca bir `raise` ifadesi sınıfı demektir: kendi tür ipuçlarınızı yeniden doğrulamayın. !!! info - Bu sayfadaki her şey bir **istemcinin** gördüğüdür; testleri yazarken kullanacağınız bellek içi - `Client` da tam olarak aynı şeyi görür. `raise_exceptions=True` bile bir araç hatasını tekrar - traceback'e çevirmez: o bayrak devreye girebilecek noktaya geldiğinde istisnanız çoktan - `is_error=True` sonucuna dönüşmüştür. Doğrulamayı sonuç üzerinde yapın. **[Test etme](../get-started/testing.md)** sayfası bu kalıbı anlatır. + Bu sayfada bir **istemcinin** gördüğü her şeyi, testleri yazarken kullanacağınız bellek içi + `Client` da görür. `raise_exceptions=True` bile başarısız olan bir + aracın istisnasını çağırana geri vermez: o bayrak devreye girebilecek noktaya geldiğinde istisnanız çoktan + `is_error=True` sonucuna dönüşmüştür. Doğrulamayı sonuç üzerinde yapın. Bir çökmenin traceback'ine ihtiyacınız varsa o + sunucunun log'undadır ve pytest'in `caplog`'u onu yakalar. **[Test etme](../get-started/testing.md)** sayfası bu kalıbı anlatır. ## Özet {#recap} -* Bir araçta **herhangi bir istisna** fırlatın -> çağrı, mesajınız `content`'te olacak şekilde `is_error=True` döndürür. Model bunu okur ve yeniden deneyebilir. Varsayılan budur. +* Bir araçta **`ToolError`** fırlatın -> çağrı, mesajınız `content`'te olacak şekilde `is_error=True` döndürür. Model bunu okur ve yeniden deneyebilir. * **`MCPError`** fırlatın -> çağrının kendisi bir JSON-RPC hatasıyla başarısız olur. Model hiçbir şey görmez; bununla host ilgilenir. `code`, `message` ve `data` bozulmadan ulaşır. -* Belirleyici soru: *daha akıllı bir model bundan kaçınabilir miydi?* Evet -> istisna. Hayır -> `MCPError`. +* Belirleyici soru: *daha akıllı bir model bundan kaçınabilir miydi?* Evet -> `ToolError`. Hayır -> `MCPError`. +* Diğer **her istisna** bir çökmedir -> model için yalnızca `Error executing tool ` içeren `is_error=True`, sizin için ise traceback'li bir `ERROR` kaydı. * Bir kaynak işleyicisinden `ResourceNotFoundError` -> protokolün `-32602` kodu, URI `data`'da. * Hatalı argümanlar, fonksiyonunuz çalışmadan önce şemaya göre reddedilir; bunlar için `raise` yazmazsınız. -* `from mcp import MCPError`; hata kodu sabitleri `mcp.types`'tan gelir. +* İçe aktarmalar: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError` ve `mcp.types`'tan hata kodu sabitleri. Hatalar halloldu. Bir sunucunun *sunduğu* her şey bu kadar. Her işleyicinin çalışırken neleri okuyabildiği ve istemciye geri neler yapabildiği bir sonraki bölümde: **[İşleyicinin içinde](../handlers/index.md)**. diff --git a/i18n/tr/pages/servers/media.md b/i18n/tr/pages/servers/media.md index 95fcb7ac5d..8f193097bd 100644 --- a/i18n/tr/pages/servers/media.md +++ b/i18n/tr/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # Medya {#media} @@ -86,6 +86,24 @@ Tanımadığı bir uzantı `application/octet-stream`'e geri düşer. MP3 baytlarından bu şekilde bir `Audio` oluşturursanız istemciye `mime_type="audio/wav"` söylenir ve o da sadakatle çözmeyi başaramaz. `data=` geçirdiğinizde `format=` da geçirin. +## Bir kaynağı gömme {#embedding-a-resource} + +Bir araç bir belge de döndürebilir: bulunduğu URI ve bir MIME türüyle birlikte bir miktar metin ya da bayt. Bu bir **`EmbeddedResource`**'tur, bir başka içerik bloğu türü. Düz bir `str`'den farklı olarak istemciye içeriğin ne olduğunu söyler; böylece istemci onu bir ek olarak gösterebilir ya da zaten bildiği bir kaynağı tanıyabilir. + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines` sıradan bir kaynaktır (bunları **[Kaynaklar](resources.md)** sayfası anlatır). Araç, istek üzerine aynı belgeyi modele verir ve `guidelines()`'ı doğrudan çağırmak tek bir doğruluk kaynağını korur. +* `EmbeddedResource` ve `TextResourceContents`, `mcp.types` modülünden gelir. Görsellerdeki gibi bir yardımcı yoktur: oluşturduğunuz blok sonuca olduğu gibi girer ve `structured_content` yoktur. +* Kaynağın kaydedildiği URI'yi kullanın; böylece istemci ekin ve `brand://guidelines` kaynağının aynı belge olduğunu anlayabilir. Kayıtlı olsun olmasın her URI geçerlidir. + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +İkili içerik için `TextResourceContents` yerine, baytları base64 ile kodlayıp `blob` alanına koyarak `BlobResourceContents(uri=..., mime_type=..., blob=...)` kullanın. Yalnızca istemcinin daha sonra `resources/read` ile okuyabileceği bir işaretçi göndermek için bunun yerine bir `ResourceLink(name=..., uri=...)` döndürün; o da bir içerik bloğudur. + ## Simgeler {#icons} `Icon` içerik değil, meta veridir. Görseli taşımaz; bir URI ile ona işaret eder ve istemci onu getirip sunucunuzun adının, bir aracın, bir kaynağın veya bir prompt'un yanında gösterebilir. @@ -115,6 +133,7 @@ Bir aracın simgeleri `tools/list`'ten gelen `Tool` nesnesinde, bir kaynağınki * Bir araçtan `Image` veya `Audio` döndürün; istemci bir `ImageContent` / `AudioContent` bloğu alır: base64 ile kodlanmış baytlarınız ve bir MIME türü. * Bunu bir `path=` ile oluşturup MIME türünü uzantının belirlemesine bırakın ya da bellekteki `data=` ile açık bir `format=` kullanın. +* Sonuca bir belge (URI'si ve MIME türüyle birlikte metin ya da base64 blob) koymak için bir `EmbeddedResource`, yalnızca işaretçiyi göndermek için bir `ResourceLink` döndürün. * Medya sonuçları `structured_content` ve çıktı şeması taşımaz. * `Icon` bir işaretçidir: bir `src` URI'si ile isteğe bağlı `mime_type`, `sizes` ve `theme`. * `icons=[...]` sunucuda, araçlarda, kaynaklarda ve prompt'larda çalışır; istemciler bunları eşleşen nesnelerde bulur. diff --git a/i18n/tr/pages/servers/prompts.md b/i18n/tr/pages/servers/prompts.md index 5a3f2ac472..23af83dc6e 100644 --- a/i18n/tr/pages/servers/prompts.md +++ b/i18n/tr/pages/servers/prompts.md @@ -1,6 +1,6 @@ --- translation: - sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094] + sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd] tool: 1 --- # Prompt'lar {#prompts} @@ -139,10 +139,55 @@ Sonuncusuna dikkat edin. Bir `assistant` turunu önceden doldurmak, yönlendirme ``` !!! info - **[Araçlar](tools.md)** sayfasını okuduysanız bu sayfadaki her şeyi zaten biliyorsunuz. Aynı dekoratör, + **[Araçlar](tools.md)** sayfasını okuduysanız buraya kadarki her şeyi zaten biliyorsunuz. Aynı dekoratör, açıklama olarak aynı docstring, aynı `Annotated`/`Field`. Değişen tek şey onu kimin tetiklediği (kullanıcı) ve sonucun nereye gittiğidir (konuşmaya). +## Metinden fazlası {#more-than-text} + +`UserMessage` ve `AssistantMessage`, `str` kabul ettikleri her yerde bir içerik bloğunu ya da bir `Image` / `Audio` yardımcısını da kabul eder. Prompt'larda iki durum öne çıkar: bir belge eklemek ve bir resim eklemek. + +### Dosya gömme {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* Stil kılavuzu `style://python` adresindeki bir kaynaktır (bunları **[Kaynaklar](resources.md)** sayfası anlatır) ve `server.py` dosyasının yanındaki `style-guide.md` dosyasından okunur. Oraya herhangi bir Markdown dosyası koyun. +* Her ikisi de `mcp.types` modülünden gelen `EmbeddedResource(resource=TextResourceContents(...))`, dosyayı URI'si ve MIME türüyle birlikte ilk mesaj olarak taşır; ona atıfta bulunan istek düz metin olarak ardından gelir. +* Kılavuzu f-string'e yapıştırmak yerine gömmek, istemcinin onu bir ek olarak göstermesini ve `style://python` kaynağını daha sonra yeniden açabilmesini sağlar; model de dosyayı olduğu gibi alır. İkili bir dosya için base64 `blob` içeren `BlobResourceContents` kullanın. + +İşlendiğinde ilk mesajın `content` alanı bir `resource` bloğudur: + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### Görsel ekleme {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image`, **[Görseller, ses ve simgeler](media.md)** sayfasındaki yardımcıdır. Prompt işlendiğinde `UserMessage` onu bir `ImageContent` bloğuna dönüştürür (dosya base64 ile kodlanır, MIME türü `.png` uzantısından tahmin edilir); `Audio` da aynı şekilde bir `AudioContent` olur. +* `server.py` dosyasının yanına `architecture.png` adında herhangi bir PNG koyun. Prompt argümanları dizedir, bu yüzden resim her zaman sunucudan gelir; `component` yalnızca sözcükleri sağlar. + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## Listeyi çalışma zamanında değiştirme {#changing-the-list-at-runtime} + +İstemciler bağlıyken prompt eklenebilir; örneğin bir kullanıcının bir talimatı kendine ait bir menü girdisi olarak kaydetmesine izin vermek için. Prompt'u kaydedin, ardından bildirin: + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` bir fonksiyonu tıpkı `@mcp.prompt()`'un yapacağı gibi kaydeder; `mcp.remove_prompt(name)` ise bunun tersidir. `add_prompt` aynı ada sahip mevcut bir girdinin üzerine yazmak yerine onu korur; bu yüzden araç, kaydetmenin değiştirme anlamına gelmesi için önce varsa eskisini kaldırır. `prompts/list` değişikliği hemen yansıtır. +* `await ctx.notify_prompts_changed()`, bir `subscriptions/listen` akışını dinleyen her `2026-07-28` istemcisine `notifications/prompts/list_changed` gönderir (**[Abonelikler](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` ise çağıran istemci 2026 öncesiyse bildirimi ona gönderir (**[Eski nesil istemcilere hizmet verme](../run/legacy-clients.md)**). İkisini de çağırın; haber verecek kimse yoksa her biri hiçbir şey yapmaz. +* Bildirimi alan bir istemci `prompts/list`'i yeniden çağırır. Python `Client`'ında bu, bir `PromptsListChanged` olayı üreten `async with client.listen(prompts_list_changed=True) as sub:` biçimindedir. + ## Özet {#recap} * Bir fonksiyonun üzerindeki `@mcp.prompt()` onu bir prompt yapar. Ad fonksiyondan, açıklama docstring'den gelir. @@ -151,5 +196,7 @@ Sonuncusuna dikkat edin. Bir `assistant` turunu önceden doldurmak, yönlendirme * Bir `str` döndürün, tek bir kullanıcı mesajına dönüşür. Çok turlu bir konuşmanın temelini atmak için `UserMessage` / `AssistantMessage` listesi döndürün. * `title=` ve `Field(description=...)`, bir istemcinin arayüzüne koyduğu şeylerdir. * Eksik bir zorunlu argüman isteğin tamamını başarısız kılar. Prompt'a özgü bir hata sonucu yoktur. +* Bir belge veya resim eklemek için bir `EmbeddedResource` ya da `Image` nesnesini `UserMessage` içine sarın. +* Çalışma zamanında `mcp.add_prompt(...)` / `mcp.remove_prompt(...)` ile prompt ekleyin veya kaldırın, ardından `await ctx.notify_prompts_changed()` ve `await ctx.session.send_prompt_list_changed()` çağırın. Bir prompt'un (veya bir kaynak şablonunun) argümanları için sunucu tarafı otomatik tamamlama **[Tamamlamalar](completions.md)** sayfasındadır. diff --git a/i18n/tr/pages/servers/structured-output.md b/i18n/tr/pages/servers/structured-output.md index 37153dbd16..e14d7e571c 100644 --- a/i18n/tr/pages/servers/structured-output.md +++ b/i18n/tr/pages/servers/structured-output.md @@ -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 --- # Yapılandırılmış çıktı {#structured-output} @@ -105,7 +105,7 @@ Her biçim bir sınıfı hak etmez. Bir `TypedDict` aynı şemayı üretir: --8<-- "docs_src/structured_output/tutorial003.py" ``` -`TypedDict` çalışma zamanında düz bir `dict`'tir; siz de onu oluşturup döndürürsünüz. Şema, doğrulama ve `structured_content`, `BaseModel` sürümüyle birebir aynıdır (`TypedDict`'te yeri olmayan açıklamalar hariç). +`TypedDict` çalışma zamanında düz bir `dict`'tir; siz de onu oluşturup döndürürsünüz. Şema, doğrulama ve `structured_content`, `BaseModel` sürümüyle aynı kurallara uyar: bir sınıf docstring'i ya da `Annotated[..., Field(description=...)]` ekleyin, bunlar şemadaki tanımlar (description) olur; dict'in dışında bıraktığınız bir `NotRequired` anahtar da `structured_content`'in dışında kalır. ## Bir dataclass {#a-dataclass} @@ -188,17 +188,18 @@ Açıklama `WeatherData` vaat ediyor. Üst servisin yanıtı `humidity` gönderm !!! check `get_weather`'ı çağırdığınızda istemciye sessizce yarı boş bir nesne vermez. Çağrı başarısız - olur ve hatanın ilk satırları alanın adını verir: + olur: istemci `Error executing tool get_weather` iletisiyle birlikte `is_error=True` alır; böylece + model, var olmayan bir hava durumunu kendinden emin biçimde okumak yerine çağrının başarısız + olduğunu bilir. Alanın adı ise sizin için, sunucu log'unda `ERROR` düzeyinde: ```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] ``` - Bu metin, `is_error=True` ile araç sonucu olarak geri döner; böylece model, var olmayan bir hava - durumunu kendinden emin biçimde okumak yerine çağrının başarısız olduğunu bilir. - Bu arada, `-> WeatherData` bir araçtan düz bir `dict` döndürmek sorun değil. `json.loads`'un ürettiği tam olarak buydu. Doğrulama Python türüne değil, değere uygulanır. ## Devre dışı bırakma {#opting-out} @@ -213,6 +214,10 @@ Bazen dönüş açıklaması protokol için değil, tür denetleyiciniz içindir Tersi olan `structured_output=True`, otomatik algılamayı bir zorunluluğa çevirir: dönüş türü şema üretemeyen bir araç, metne geri düşmek yerine içe aktarma anında istisna fırlatır. +## İçerik blokları ve medya {#content-blocks-and-media} + +İçerik blokları ve medya (`TextContent`, `EmbeddedResource`, `Image`, `Audio` ve benzerleri; tek başlarına, bir `list`, `tuple` ya da `Sequence`'in öğeleri olarak veya bir union'ın kolları olarak) sizin yerinize devre dışı bırakılır: bunlar modelin okuması içindir, bu yüzden otomatik algılama onlardan şema türetmez (`Image` ve `Audio`'yu **[Görseller, ses ve simgeler](media.md)** sayfası anlatır). `structured_output=True`, içerik bloğu sınıfları için yine de bir şemayı zorunlu kılar. + ## Tür ipucu olmayan bir sınıf {#a-class-without-type-hints} İstemeden yapılandırılmamış sonuca varmanın bir yolu vardır: **gövdesinde hiç açıklama olmayan** bir sınıf döndürmek. @@ -245,6 +250,6 @@ Tersi olan `structured_output=True`, otomatik algılamayı bir zorunluluğa çev * Skalerler, listeler, tuple'lar ve union'lar `{"result": ...}` içine sarılır. Modeller, `TypedDict`'ler, dataclass'lar, açıklamalı sınıflar ve `dict[str, ...]` zaten nesnedir ve oldukları gibi kalırlar. * Her sonuç hem `content` (metin, model için) **hem de** `structured_content` (veri, uygulama için) taşır. * Döndürdüğünüz şey şemaya göre doğrulanır. Uyuşmazlık bozuk bir sonuç değil, bir araç hatasıdır. -* `structured_output=False` bir aracı devre dışı bırakır. Tür ipucu olmayan bir sınıf sessizce devre dışı kalır; buna dikkat edin. +* `structured_output=False` bir aracı devre dışı bırakır. İçerik blokları, `Image` ve `Audio` varsayılan olarak devre dışıdır; tür ipucu olmayan bir sınıf sessizce devre dışı kalır, buna dikkat edin. Artık bir aracın geri söyleyebileceği her şeye hâkimsiniz. Sırada ikinci ilkel yapı var: **[Kaynaklar](resources.md)**. diff --git a/i18n/tr/pages/servers/tools.md b/i18n/tr/pages/servers/tools.md index b12947a61c..3ae9b8842d 100644 --- a/i18n/tr/pages/servers/tools.md +++ b/i18n/tr/pages/servers/tools.md @@ -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 --- # Araçlar {#tools} @@ -39,6 +39,8 @@ SDK bu tür ipuçlarından bir JSON Schema üretir ve `tools/list` sırasında i Hiçbirinin varsayılan değeri olmadığı için iki argüman da `required` içinde. Bunu birazdan düzelteceksiniz. (`title` anahtarları Pydantic'in ürettiği kalıntılardır; sözleşmeyi oluşturan şey özellikler, türleri ve `required`'dır.) +`$schema` anahtarı da yok: MCP, bu anahtarı taşımayan bir şemayı **JSON Schema 2020-12** olarak kabul eder; Pydantic'in ürettiği de budur. Bu yüzden **[alt düzey Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)** üzerinde şemaları elle yazana kadar seçmeniz gereken bir şey yoktur. + !!! tip Tür ipuçları burada dokümantasyon değildir. **Sözleşmenin ta kendisidir**. Bir istemci `"limit": "ten"` gönderirse SDK bunu, fonksiyonunuz daha çalışmadan reddeder. diff --git a/i18n/tr/pages/servers/uri-templates.md b/i18n/tr/pages/servers/uri-templates.md index b24445fc74..dfbc634639 100644 --- a/i18n/tr/pages/servers/uri-templates.md +++ b/i18n/tr/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # URI şablonları ve yol güvenliği {#uri-templates-and-path-safety} @@ -169,7 +169,7 @@ Yerleşik denetimler yaygın durumları durdurur ama sizin sandbox sınırınız bilemez. Dosya sistemi erişimi için yolu çözümlemek ve temel dizininizin içinde kaldığını doğrulamak üzere `safe_join` kullanın: -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` @@ -209,10 +209,11 @@ Bu denetimler sezgisel bir ön süzgeçtir; dosya sistemi erişimi için kapsama sınırı `safe_join` olmaya devam eder. !!! tip - İşleyiciniz isteği karşılayamıyorsa (dosya yok, kimlik bilinmiyor) bir - istisna fırlatın. SDK bunu bir hata yanıtına dönüştürür. Protokol hatası - ile araç hatası arasındaki fark için **[Hataları ele alma](handling-errors.md)** - sayfasına bakın. + İşleyiciniz isteği karşılayamıyorsa (dosya yok, kimlik bilinmiyor), yukarıda + `read_manual`'ın yaptığı gibi `ResourceNotFoundError` fırlatın. İstemci, + mesajınız ve URI ile birlikte `-32602` alır. Beklenmedik bir istisna ise + bunun yerine genel bir `-32603` olur. Bkz. + **[Hataları ele alma](handling-errors.md#a-resource-that-doesnt-exist)**. ## Düşük seviyeli Server üzerinde kaynaklar {#resources-on-the-low-level-server} diff --git a/i18n/tr/pages/troubleshooting.md b/i18n/tr/pages/troubleshooting.md index 03aecb12e5..3362b53054 100644 --- a/i18n/tr/pages/troubleshooting.md +++ b/i18n/tr/pages/troubleshooting.md @@ -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 --- # Sorun giderme {#troubleshooting} @@ -81,11 +81,11 @@ async def main() -> None: `__aexit__` ise bağlantının kesilmesidir; unutulacak bir `client.close()` olmamasının nedeni de budur. **[Test etme](get-started/testing.md)** tam olarak bu kalıp üzerine kuruludur. -## `Error executing tool : ` ve `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `, `Error executing tool ` ve `Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} Okuduğunuz şey bir istisna değil, bir **sonuç**. `call_tool` istisna fırlatmadı ve başarısız olan bir araç için hiçbir zaman fırlatmaz. -`forecast`'i sunucunun tanımadığı bir şehir için çağırın; fırlattığı istisna, istek *başarılı* olarak işaretlenmiş halde geri döner: +`forecast`'i sunucunun tanımadığı bir şehir için çağırın; fırlattığı `ToolError`, istek *başarılı* olarak işaretlenmiş halde geri döner: ```python result.is_error # True @@ -97,6 +97,8 @@ result.structured_content # None Çözüm istemcinizde: **`result.is_error`'ı kontrol edin**. `call_tool` etrafındaki bir `try/except` bunların hiçbirini yakalamaz, çünkü yakalanacak bir şey yoktur. Bu kasıtlıdır ve bu sayfada içselleştirilecek en yararlı tek şeydir: çağrıyı *model* seçti, bu yüzden mesajı ve yeniden deneme şansını da model alır. Ayrıntıların tamamı, *gerçekten* istisna fırlatan `MCPError` yolu dahil, **[Hataları ele alma](servers/handling-errors.md)** sayfasında. +Yalın biçim, yani mesajsız `Error executing tool `, aracın **çöktüğü** anlamına gelir: öngörmediği bir istisna ondan kaçmıştır (ya da dönüş değeri çıktı şemasını geçememiştir) ve o istisnanın metni ağ üzerinden gönderilmez. Traceback, `ERROR` düzeyinde, `Tool '' raised an unexpected exception` olarak **sunucunun log'undadır**. + ## `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} `@mcp.tool()` yerine `@mcp.tool` yazdınız. `tool()` bir dekoratör *fabrikasıdır*: parantezler olmadan Python, fonksiyonunuzu onun `name=` parametresine verir. @@ -410,7 +412,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## Özet {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup` hiçbir zaman asıl hata değildir. **Son satırı** okuyun; `MCPError`'ı `async with Client(...)` bloğunun *içinde* yakalamak sarmalamayı tamamen atlar. -* `call_tool` başarısız olan bir araç için istisna fırlatmaz. `Error executing tool ...` ve `Unknown tool: ...` birer sonuçtur: `result.is_error`'ı kontrol edin. +* `call_tool` başarısız olan bir araç için istisna fırlatmaz. `Error executing tool ...` ve `Unknown tool: ...` birer sonuçtur: `result.is_error`'ı kontrol edin. Araç adından sonra mesaj yoksa araç çökmüş demektir ve traceback sunucu log'undadır. * `Client must be used within an async context manager` -> `async with` kullanın. `Use @tool() instead of @tool` -> parantezleri ekleyin. * Sunucu log'undaki `Tool already exists:`, aynı adlı iki aracın teke indiğinin tek işaretidir. * Tek 421, üç yazım: `Server returned an error response` (python `Client`), `421 Misdirected Request` / `Invalid Host header` (geri kalan her şey), `Invalid Host header: ` (sunucu log'u). Çözüm: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`. diff --git a/i18n/tr/pages/whats-new.md b/i18n/tr/pages/whats-new.md index e7c27be4f6..6380cf3600 100644 --- a/i18n/tr/pages/whats-new.md +++ b/i18n/tr/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # v2'deki yenilikler {#whats-new-in-v2} @@ -46,9 +46,9 @@ v1 size iç içe üç katman veriyordu: ham akışlar üreten bir aktarım bağl --8<-- "docs_src/client/tutorial001.py" ``` -`Client` bir sunucu nesnesi (bellek içi, aktarım yok: test senaryosu), bir URL (Streamable HTTP) ya da `stdio_client(...)` gibi herhangi bir aktarım bağlam yöneticisi alır. `async with` bloğuna girmek bağlantıyı kurar ve sunucu hangi nesli konuşuyorsa ona göre protokol sürümünde anlaşır; ardından `client.server_capabilities` ve `client.protocol_version` hazırdır, sunucu kendini tanıttığında `client.server_info` da öyle (artık `Implementation | None` türünde, çünkü 2026 neslinde kimlik isteğe bağlı). v1'de kaydettiğiniz örnekleme ve elicitation callback'leri hâlâ çalışır (gövdeleri, bu sayfadaki her şey gibi aynı snake_case öznitelik yeniden adlandırmasını görür); artık 2026 tarzı sonuç-içinde-isteklere de (aşağıda) yanıt verirler ve teker teker değil eşzamanlı çalışırlar. Düşük düzey yüzeyi isteyenler için `ClientSession` hâlâ altta duruyor ve `client.session` onu size verir; o da taşındı (yeni dispatcher motoru üzerinde çalışır ve kendi imzalarından bazıları değişti), bu yüzden aşağı inmeden önce **[Geçiş kılavuzu](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** sayfasını okuyun. +`Client` bir sunucu nesnesi (bellek içi, aktarım yok: test senaryosu), bir URL (Streamable HTTP), bir `StdioServerParameters` (bir stdio alt süreci) ya da `sse_client(...)` gibi başka herhangi bir aktarım bağlam yöneticisi alır. `async with` bloğuna girmek bağlantıyı kurar ve sunucu hangi nesli konuşuyorsa ona göre protokol sürümünde anlaşır; ardından `client.server_capabilities` ve `client.protocol_version` hazırdır, sunucu kendini tanıttığında `client.server_info` da öyle (artık `Implementation | None` türünde, çünkü 2026 neslinde kimlik isteğe bağlı). v1'de kaydettiğiniz örnekleme ve elicitation callback'leri hâlâ çalışır (gövdeleri, bu sayfadaki her şey gibi aynı snake_case öznitelik yeniden adlandırmasını görür); artık 2026 tarzı sonuç-içinde-isteklere de (aşağıda) yanıt verirler ve teker teker değil eşzamanlı çalışırlar. Düşük düzey yüzeyi isteyenler için `ClientSession` hâlâ altta duruyor ve `client.session` onu size verir; o da taşındı (yeni dispatcher motoru üzerinde çalışır ve kendi imzalarından bazıları değişti), bu yüzden aşağı inmeden önce **[Geçiş kılavuzu](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** sayfasını okuyun. -**[Client](client/index.md)** sayfası onu tanıtır, **[İstemci aktarımları](client/transports.md)** üç bağlantı biçimini anlatır, **[İstemci callback'leri](client/callbacks.md)** callback'lerin kendisini ele alır ve **[Test etme](get-started/testing.md)** v1'in `create_connected_server_and_client_session()` yardımcısının yerini alan bellek içi kalıbı gösterir. +**[Client](client/index.md)** sayfası onu tanıtır, **[İstemci aktarımları](client/transports.md)** dört bağlantı biçimini anlatır, **[İstemci callback'leri](client/callbacks.md)** callback'lerin kendisini ele alır ve **[Test etme](get-started/testing.md)** v1'in `create_connected_server_and_client_session()` yardımcısının yerini alan bellek içi kalıbı gösterir. ### Düşük düzey `Server` yeniden adlandırılmadı, yeniden inşa edildi {#the-low-level-server-was-rebuilt-not-renamed} @@ -134,7 +134,7 @@ Seçenekleri **[Sunucunuzu çalıştırma](run/index.md)**, bağlamayı **[Mevcu Yeniden adlandırmalar kendini belli eder. Bunlar etmez: * **Senkron fonksiyonlar bir işçi iş parçacığında çalışır.** Bir `def` aracı (ya da kaynağı, prompt'u veya çözümleyicisi) artık olay döngüsünü engellemez; bunun bedeli, gövdesinin artık olay döngüsü iş parçacığının *üzerinde* çalışmamasıdır ve bu, iş parçacığına bağlı kod için önemlidir. `async def` işleyicilere dokunulmadı. **[Geçiş kılavuzu](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**. -* **Bir aracın içinde fırlatılan `MCPError` (v1'deki `McpError`) artık bir protokol hatasıdır.** Model onu asla görmez. Diğer her istisna hâlâ modelin okuyup tepki verebileceği `is_error=True` bir sonuca dönüşür. Ayrım **[Hataları ele alma](servers/handling-errors.md)** sayfasında. +* **Bir aracın içinde fırlatılan `MCPError` (v1'deki `McpError`) artık bir protokol hatasıdır.** Model onu asla görmez. Diğer her istisna hâlâ `is_error=True` bir sonuca dönüşür, ancak modele yalnızca bir `ToolError`'ın mesajı ulaşır: başka herhangi bir istisna artık `Error executing tool ` olarak okunur, traceback ise sunucu log'unuzda kalır. Ayrım **[Hataları ele alma](servers/handling-errors.md)** sayfasında. * **Sonuçlar çıkmadan önce doğrulanır.** `input_schema`'sı `{}` olan elle kurulmuş bir `Tool` artık `tools/list` çağrısında başarısız olur (spesifikasyon `"type": "object"` gerektirir). `@mcp.tool()` üzerine kurulu sunucular bunu asla görmez; şemalarını SDK yazar. * **İstemciniz aldığını doğrular.** `list_tools()` ve `call_tool()` sunucunun yanıtını üzerinde anlaşılan protokol sürümüne göre denetler; bu yüzden v1'in hoşgörülü ayrıştırmasının idare ettiği tam geçerli olmayan bir sunucu artık `pydantic.ValidationError` fırlatır. Kontrol etmediğiniz sunuculara bağlanıyorsanız onları bulan kişi olmayı bekleyin; ayrıntılar **[Geçiş kılavuzu](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)** sayfasında. * **URI şablonları artık gerçek RFC 6570.** `{+path}`, `{?query}` ve benzerleri çalışır, eşleştirme regex gevşekliğinde değil birebirdir ve çıkarılan değerlerdeki yol geçişi (path traversal) varsayılan olarak reddedilir. Daha sıkı şablonlar ilk istekte değil, dekoratör uygulanırken başarısız olur. **[URI şablonları](servers/uri-templates.md)**. diff --git a/i18n/uk/pages/advanced/low-level-server.md b/i18n/uk/pages/advanced/low-level-server.md index 92fa509c24..98926a312b 100644 --- a/i18n/uk/pages/advanced/low-level-server.md +++ b/i18n/uk/pages/advanced/low-level-server.md @@ -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 --- # Низькорівневий Server {#the-low-level-server} @@ -116,6 +116,17 @@ asyncio.run(main()) Сервер ніколи не порівнює ці два поля. `Client` цього SDK — порівнює: поверніть `structured_content`, що не відповідає оголошеній вами `output_schema`, і `call_tool` викине `RuntimeError`, який починається з `Invalid structured content returned by tool search_books` і далі цитує помилку `jsonschema`. Пообіцяти схему легко; дотримати її — ваша справа. Уся драбина типів повернення та схем — на сторінці **[Структурований вивід](../servers/structured-output.md)**. +## Діалект — JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12} + +`input_schema` і `output_schema` — це JSON Schema, а [специфікація MCP](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) фіксує діалект: схема без ключа `$schema` — це **JSON Schema 2020-12**. Схеми, які генерує `MCPServer`, покладаються на це типове значення (Pydantic пише 2020-12 і пропускає ключ), і написаний вручну словник теж має його дотримуватися, тож доступний увесь набір ключових слів 2020-12: + +```python title="server.py" hl_lines="8 14-15" +--8<-- "docs_src/lowlevel/tutorial007.py" +``` + +* Корінь `input_schema` мусить бути `"type": "object"`. Поруч із ним `oneOf`, `additionalProperties`, `anyOf`, `if`/`then`/`else`, `prefixItems`, `$defs` з локальними `$ref` та решта ключових слів 2020-12 доходять до клієнта точно так, як написано. +* Ключ `$schema` не потрібен. Додавайте його лише щоб перейти на давнішу чернетку стандарту: `Client` цього SDK, який перевіряє `structured_content` за `output_schema` інструмента, обирає валідатор за `$schema` і використовує 2020-12, коли ключа немає. + ## `_meta`: для застосунку, а не для моделі {#\_meta-for-the-application-not-the-model} `content` — це частина відповіді, яку читає модель. `structured_content` — та сама відповідь у вигляді типізованих даних. `_meta` — третій канал: дані, що їдуть разом із результатом для **клієнтського застосунку** і взагалі не є частиною відповіді. @@ -167,7 +178,7 @@ asyncio.run(main()) --8<-- "docs_src/lowlevel/tutorial006.py" ``` -* Перший аргумент — рядок методу. Для сповіщень є близнюк — `add_notification_handler`. +* Перший аргумент — рядок методу. Для сповіщень є близнюк — `add_notification_handler`. Його обробники спрацьовують на stdio та на HTTP-з'єднаннях покоління рукостискання; на шляху Streamable HTTP версії `2026-07-28` POST-запит клієнта зі сповіщенням підтверджується кодом `202` і не диспетчеризується, бо ця редакція не визначає сповіщень від клієнта до сервера через HTTP. * `params_type` — це модель, за якою вхідні `params` перевіряються **до** запуску вашого обробника, тож власні методи *отримують* перевірку, якої інструменти не мають. Успадковуйтеся від `RequestParams`, щоб поле `_meta` розбиралося так само, як у кожного іншого методу. * Обробник повертає `BaseModel`, `dict` або `None`. SDK серіалізує це в результат JSON-RPC. diff --git a/i18n/uk/pages/advanced/middleware.md b/i18n/uk/pages/advanced/middleware.md index a51773410b..b53eeb6f73 100644 --- a/i18n/uk/pages/advanced/middleware.md +++ b/i18n/uk/pages/advanced/middleware.md @@ -1,6 +1,6 @@ --- translation: - sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c] + sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43] tool: 1 --- # Middleware {#middleware} @@ -33,8 +33,8 @@ translation: рядок методу; `ctx.params` — сирі параметри, **до** будь-якої валідації. * `call_next(ctx)` запускає решту ланцюжка: валідацію, пошук обробника, сам обробник. Поверніть те, що він повернув, — і відповідь залишиться недоторканою. -* `try`/`finally` тут навмисно: обробник, що викидає виняток, однаково буде заміряно, бо збій - доходить до middleware як виняток із `call_next`. +* `try`/`finally` тут навмисно: для обробника, що викидає виняток, час однаково буде заміряно, + бо збій доходить до middleware як виняток із `call_next`. * `server.middleware.append(...)` реєструє її. Список виконується від зовнішнього до внутрішнього, тож `middleware[0]` — найближча до мережі. @@ -55,8 +55,11 @@ tools/call took 0.1 ms * Установлення з'єднання: `server/discover`, або `initialize` і `notifications/initialized` у сесії старого покоління. -* Кожен запит і кожне сповіщення. Для сповіщення `ctx.request_id is None`, - `call_next(ctx)` повертає `None`, а все, що повернете ви, відкидається. +* Кожен запит і кожне сповіщення, що доходить до сервера. Для сповіщення + `ctx.request_id is None`, `call_next(ctx)` повертає `None`, а все, що повернете ви, + відкидається. (На шляху Streamable HTTP версії `2026-07-28` POST зі сповіщенням від клієнта + транспорт підтверджує кодом `202` і ніколи не передає далі, тож до middleware воно теж не + доходить; ця ревізія не визначає жодних сповіщень від клієнта до сервера через HTTP.) * Навіть метод, для якого сервер не має обробника: `call_next` викидає `MCPError(-32601, "Method not found")` *крізь* ваш middleware на шляху до клієнта. @@ -115,8 +118,9 @@ SDK постачає рівно один шар middleware, і він уже є * Middleware — це `async (ctx, call_next) -> result`; його передають як `MCPServer(middleware=[...])` (або додають до `mcp.middleware`), а в низькорівневому `Server` додають до `server.middleware`. -* Він огортає **кожне** вхідне повідомлення (`server/discover`, `initialize`, запити, - сповіщення, невідомі методи) і виконується від зовнішнього до внутрішнього. +* Він огортає **кожне** вхідне повідомлення, що доходить до сервера (`server/discover`, + `initialize`, запити, сповіщення, невідомі методи), і виконується від зовнішнього до + внутрішнього. * `ctx.request_id is None` — так відрізняють сповіщення від запиту. * Викиньте виняток замість виклику `call_next`, щоб відхилити одне повідомлення; з'єднання вціліє. diff --git a/i18n/uk/pages/client/index.md b/i18n/uk/pages/client/index.md index d967bc571f..91a410624b 100644 --- a/i18n/uk/pages/client/index.md +++ b/i18n/uk/pages/client/index.md @@ -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 --- # Клієнт {#the-client} @@ -27,9 +27,10 @@ translation: * Екземпляр `MCPServer` (або низькорівневого `Server`): під'єднання **в межах процесу**. * Рядок з URL (`Client("http://localhost:8000/mcp")`): Streamable HTTP, шлях для робочого розгортання. -* **Транспорт**: будь-що, що можна використати як `async with ... as (read, write)`, наприклад `stdio_client(...)`, що обгортає підпроцес. +* `StdioServerParameters`: команда, яку буде запущено як **підпроцес**; спілкування з ним іде через його stdin і stdout. +* **Транспорт**: будь-що, що можна використати як `async with ... as (read, write)`, наприклад `streamable_http_client(url, http_client=...)` навколо вашого власного HTTP-клієнта. -Усе інше на цій сторінці однакове для всіх трьох. Заголовки, підпроцеси, тайм-аути та протокол `Transport` мають власну сторінку: **[Транспорти клієнта](transports.md)**. +Усе інше на цій сторінці однакове для всіх чотирьох. Заголовки, підпроцеси, тайм-аути та протокол `Transport` мають власну сторінку: **[Транспорти клієнта](transports.md)**. ### Що є в під'єднаного клієнта {#whats-on-a-connected-client} @@ -85,7 +86,7 @@ tool.description # 'Search the catalog by title or author.' `call_tool(name, arguments)` запускає інструмент і повертає `CallToolResult`. -```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 @@ result.is_error # False !!! check Попросіть у `lookup_book` `"Solaris"` (назву, якої немає в каталозі) — і функція викине - `ValueError`. Виклик усе одно повернеться нормально: + `ToolError`. Виклик усе одно повернеться нормально: ```python result.is_error # True @@ -125,9 +126,10 @@ result.is_error # False result.structured_content # None ``` - Повідомлення винятку потрапило в `content`, де його може прочитати **модель** і спробувати ще раз. Це - навмисно: помилка інструмента — частина розмови, а не аварія. Завжди дивіться на `is_error`, - перш ніж довіряти `structured_content`. + Повідомлення `ToolError` потрапило в `content`, де його може прочитати **модель** і спробувати ще раз. Це + навмисно: помилка інструмента — частина розмови, а не аварія. (Якби інструмент упав + з якимось іншим винятком, у `content` було б лише `Error executing tool lookup_book`.) Завжди дивіться на + `is_error`, перш ніж довіряти `structured_content`. !!! warning `is_error=True` охоплює більше, ніж ваш власний `raise`. Попросіть інструмент, якого в сервера diff --git a/i18n/uk/pages/client/transports.md b/i18n/uk/pages/client/transports.md index d3b8f3edb6..97ed054445 100644 --- a/i18n/uk/pages/client/transports.md +++ b/i18n/uk/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # Транспорти клієнта {#client-transports} @@ -87,15 +87,15 @@ translation: Сервер **stdio** — це підпроцес. Клієнт запускає його, пише JSON-RPC в його stdin і читає JSON-RPC з його stdout. Саме так десктопний хост запускає сервер на вашій машині: хост і *є* цим кодом плюс UI, а сторінка **[Під'єднання до справжнього хоста](../get-started/real-host.md)** показує ті самі стосунки з боку хоста — як конфігураційний файл. -Опишіть процес за допомогою `StdioServerParameters`, перетворіть його на транспорт через `stdio_client` і передайте *це* в `Client`: +Опишіть процес за допомогою `StdioServerParameters` і передайте його в `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` не приймає сам об'єкт параметрів. `StdioServerParameters` — це конфігурація; `stdio_client(server)` — транспорт, який уміє запустити з неї процес. Завжди загортайте. +Вхід у блок запускає процес. Вихід із нього завершує підпроцес: закриває stdin, чекає, вбиває, якщо той затримується. Прибирати за ним самостійно ніколи не доведеться. -Вихід із блоку `async with` також завершує підпроцес: закриває stdin, чекає, вбиває, якщо той затримується. Прибирати за ним самостійно ніколи не доведеться. +stderr дочірнього процесу йде у ваш. Щоб спрямувати його деінде, зберіть транспорт самостійно через `stdio_client` (з `mcp`) і передайте натомість його: `Client(stdio_client(server, errlog=log_file))`. !!! warning Дочірній процес **не** успадковує ваше середовище. Він отримує мінімальний список дозволених змінних (`HOME`, `LOGNAME`, @@ -113,16 +113,16 @@ translation: Для `Client` усе перелічене вище — одне й те саме. -**Транспорт** — це будь-який асинхронний контекстний менеджер, що повертає пару потоків повідомлень `(read, write)`: формально — протокол `Transport` у `mcp.client`. `Client` розв'язує свій аргумент за типом: об'єкт сервера під'єднується в межах процесу, `str` стає `streamable_http_client(url)`, а в будь-що інше він входить безпосередньо як у транспорт. Саме завдяки останньому правилу `stdio_client(...)`, `streamable_http_client(...)` і `sse_client(...)` стають на одне й те саме місце — і саме тому можна написати власний. +**Транспорт** — це будь-який асинхронний контекстний менеджер, що повертає пару потоків повідомлень `(read, write)`: формально — протокол `Transport` у `mcp.client`. `Client` розв'язує свій аргумент за типом: об'єкт сервера під'єднується в межах процесу, `str` стає `streamable_http_client(url)`, `StdioServerParameters` стає `stdio_client(params)`, а в будь-що інше він входить безпосередньо як у транспорт. Саме завдяки останньому правилу `stdio_client(...)`, `streamable_http_client(...)` і `sse_client(...)` стають на одне й те саме місце — і саме тому можна написати власний. ## Підсумки {#recap} * `Client(mcp)` (об'єкт сервера) під'єднується в пам'яті. Використовуйте для тестів і для вбудовування. * `Client("http://.../mcp")` (URL) під'єднується через Streamable HTTP, продакшен-транспорт. * Заголовки, автентифікація, проксі й тайм-аути належать `httpx2.AsyncClient`, який ви передаєте в `streamable_http_client(url, http_client=...)`. Іменованого аргументу `headers=` немає. -* stdio — це `Client(stdio_client(StdioServerParameters(...)))`, ніколи не сам об'єкт параметрів. +* stdio — це `Client(StdioServerParameters(...))`. Загортайте його в `stdio_client(...)` самостійно лише для того, щоб перенаправити stderr дочірнього процесу. * Підпроцес отримує середовище зі списку дозволених, а не ваше; `env=` його доповнює. -* Транспорт — це будь-що, з чим можна зробити `async with x as (read, write)`. Усе, що не є об'єктом сервера чи URL, `Client` передає прямо цьому протоколу. +* Транспорт — це будь-що, з чим можна зробити `async with x as (read, write)`. Усе, що не є об'єктом сервера, URL чи `StdioServerParameters`, `Client` передає прямо цьому протоколу. * Створення `Client` обирає транспорт. `async with` його відкриває. -Щойно транспорт відкрито, обидві сторони мають домовитися про версію протоколу. Зазвичай про це не думаєш; а коли доводиться — є сторінка **[Версії протоколу](../protocol-versions.md)**. +Щойно транспорт відкрито, обидві сторони мають домовитися про версію протоколу. Зазвичай про це й не згадуєте; а коли все ж доведеться — є сторінка **[Версії протоколу](../protocol-versions.md)**. diff --git a/i18n/uk/pages/deprecated.md b/i18n/uk/pages/deprecated.md index 298e2a4237..c079c51691 100644 --- a/i18n/uk/pages/deprecated.md +++ b/i18n/uk/pages/deprecated.md @@ -1,11 +1,11 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # Застарілі можливості {#deprecated-features} -Специфікація 2026-07-28 виводить з ужитку п'ять речей. SDK і далі реалізує кожну з них, і кожна тепер супроводжується **попередженням про застарілість**. +Специфікація 2026-07-28 виводить з ужитку п'ять речей. SDK і далі реалізує кожну з них, і кожна тепер супроводжується **попередженням про застарілість**. Один допоміжний метод SDK оголошено застарілим окремо, і його наведено [наприкінці сторінки](#deprecated-sdk-helpers). Таблиця нижче називає кожну застарілу можливість, пояснює, чому вона зникає, і вказує заміну, на яку варто спиратися. @@ -56,6 +56,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE потім намагається надіслати запит. Ці два методи працюють від початку до кінця лише на з'єднанні з `mode="legacy"`, клієнт якого зареєстрував відповідний колбек. +## `ping` у сесії старого покоління {#ping-on-a-legacy-session} + +**Ping** — це порожній запит, який може надіслати будь-яка сторона, щоб перевірити, чи інша ще відповідає. Специфікація 2026-07-28 його вилучає ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)): кожен запит, який надсилає сучасний клієнт, уже доводить, що сервер на місці, а сучасний сервер не має каналу, яким міг би надіслати свій. Обидва методи SDK і далі працюють у сесії покоління з рукостисканням. З боку клієнта: + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +А з боку сервера, усередині будь-якого обробника: + +```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()` попереджає через `MCPDeprecationWarning` під час кожного виклику. На з'єднанні за замовчуванням (`2026-07-28`) сервер натомість відповідає `MCPError: Method not found`. +* `ctx.session.send_ping()` попередження не має. На сучасному з'єднанні він викидає ту саму помилку про відсутність зворотного каналу (back-channel), що й будь-який інший запит з ініціативи сервера. +* Жодна зі сторін нічого не реєструє, щоб відповідати на ping. + +## Сповіщення про зміну кореневих каталогів {#roots-change-notifications} + +Клієнт покоління 2025, який оголосив можливість кореневих каталогів, може повідомити серверу, що теки його робочого простору змінилися, надіславши `notifications/roots/list_changed`; сервер у відповідь знову запитує `roots/list`. Специфікація 2026-07-28 вилучає це сповіщення разом із рештою push-варіанту роботи з кореневими каталогами. На клієнті саме передавання `list_roots_callback=` (**[Колбеки клієнта](client/callbacks.md)**) оголошує `"roots": {"listChanged": true}`, а один виклик дотримує цієї обіцянки: + +```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() +``` + +На боці сервера обробник, що приймає це сповіщення, передають у низькорівневий `Server`: + +```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` — це список, який повертає ваш `list_roots_callback`. `client.send_roots_list_changed()` попереджає і потребує клієнта з `mode="legacy"`: на сучасному з'єднанні сповіщення мовчки відкидається. Після цього не закривайте сесію, бо наступний запит сервера `roots/list` надходить саме нею. +* `MCPServer` не має гачка для цього сповіщення. У низькорівневому `Server` обробник реєструє параметр `on_roots_list_changed=` (теж застарілий, він попереджає під час створення екземпляра). Сповіщення не несе корисного навантаження, тож обробник викликає `ctx.session.list_roots()`, щоб отримати новий список. + ## Приглушення попередження {#silencing-the-warning} У новому коді — не робіть цього. @@ -76,23 +125,33 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning) Розверніть фільтр у зворотний бік — і отримаєте безкоштовний регресійний тест. Додайте `"error::mcp.MCPDeprecationWarning"` до налаштування `filterwarnings` у конфігурації pytest — і застарілий виклик **викидатиме виняток** замість попередження. Інструмент - з назвою `old_log`, який досі викликає `ctx.info()`, перестає проходити тест і починає - повідомляти: + з назвою `old_log`, який досі викликає `ctx.info()`, перестає проходити тест: виклик + повертається з `is_error=True` і текстом `Error executing tool old_log`, а захоплений + лог сервера називає винуватця: ```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). ``` Один рядок конфігурації pytest — і застарілий виклик більше ніколи не прокрадеться назад у вашу кодову базу, не проваливши тест. +## Застарілі допоміжні методи SDK {#deprecated-sdk-helpers} + +Це не зміни специфікації, а лише внутрішні частини SDK, для яких є краща заміна. Вони попереджають тим самим `MCPDeprecationWarning` і будуть вилучені у версії 3.0. + +| Застаріле | Що робити натомість | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | `FuncMetadata.validate_arguments()`, а потім `FuncMetadata.call_fn()`. Його викликав лише код, що працює з `FuncMetadata` безпосередньо (скажімо, власний підклас `Tool`). | + ## Підсумки {#recap} * Специфікація 2026-07-28 оголошує застарілими **кореневі каталоги**, **семплювання** з ініціативи сервера та протокольне **логування** (усе — [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)), обмежує **перебіг виконання** напрямком від сервера до клієнта й вилучає **`ping`**. * Стовпець із замінами вказує, куди рухатися далі: **[Багатораундові запити](handlers/multi-round-trip.md)** для семплювання й кореневих каталогів, **[Логування](handlers/logging.md)** для логування, **[Перебіг виконання](handlers/progress.md)** для перебігу виконання. `ping` не потребує взагалі нічого. * Застарілість має рекомендаційний характер: жодних змін у переданих даних, усе й далі працює із сесіями до 2026, а ви отримуєте помітне попередження `MCPDeprecationWarning` (це `UserWarning`, тож воно ввімкнене за замовчуванням). -* Семплювання й кореневі каталоги додатково потребують зворотного каналу (back-channel), якого сесія 2026-07-28 не має. На сучасному з'єднанні вони попереджають, а потім викидають виняток. +* Семплювання й кореневі каталоги додатково потребують зворотного каналу, якого сесія 2026-07-28 не має. На сучасному з'єднанні вони попереджають, а потім викидають виняток. * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` приглушує всю категорію; `"error::mcp.MCPDeprecationWarning"` у pytest перетворює її на провал тесту. +* Один допоміжний метод SDK, `FuncMetadata.call_fn_with_arg_validation()`, оголошено застарілим окремо — його вилучать у версії 3.0. * Новий код не варто будувати на жодній із цих можливостей. Усі інші сторінки цієї документації навчають чинного API. diff --git a/i18n/uk/pages/get-started/real-host.md b/i18n/uk/pages/get-started/real-host.md index f9ad946903..bc2d92c40d 100644 --- a/i18n/uk/pages/get-started/real-host.md +++ b/i18n/uk/pages/get-started/real-host.md @@ -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 --- # Підключення до справжнього хоста {#connect-to-a-real-host} @@ -11,7 +11,7 @@ translation: ## Один сервер, усі хости {#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" ``` @@ -50,7 +50,7 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py А хост — це не більше ніж застосунок з MCP-клієнтом усередині, тож роль хоста може зіграти й ваш власний Python: сторінка **[Транспорти клієнта](../client/transports.md)** запускає - цей самий файл як підпроцес через `stdio_client(...)`, а **[Тестування](testing.md)** + цей самий файл як підпроцес через `Client(StdioServerParameters(...))`, а **[Тестування](testing.md)** підключається до нього в пам'яті взагалі без процесу. ## Claude Desktop {#claude-desktop} diff --git a/i18n/uk/pages/get-started/testing.md b/i18n/uk/pages/get-started/testing.md index 6ec0797e4c..f384dcde9f 100644 --- a/i18n/uk/pages/get-started/testing.md +++ b/i18n/uk/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # Тестування {#testing} @@ -84,13 +84,13 @@ async def test_call_add_tool(client: Client): Піти не так можуть дві різні речі, і цей прапорець стосується лише однієї з них. Виняток усередині одного з **ваших інструментів** — це не збій протоколу. Він стає звичайним результатом -з `is_error=True`, і модель читає повідомлення. `raise_exceptions` цього не змінює: з ним чи -без нього `call_tool` повертає той самий результат з `is_error=True`. Про це є ціла сторінка: +з `is_error=True` (а якщо це був `ToolError`, модель прочитає ваше повідомлення). `raise_exceptions` цього не +змінює: з ним чи без нього `call_tool` повертає той самий результат з `is_error=True`. Про це є ціла сторінка: **[Обробка помилок](../servers/handling-errors.md)**. Збій **поза** тілом інструмента — інша річ. На з'єднанні, яке дає `Client(mcp)`, сервер -замінює його загальним `"Internal server error"`, перш ніж його побачить клієнт. Ніколи не слід -розкривати подробиці неочікуваного падіння віддаленій стороні, що викликає. У тесті це саме те, +замінює його загальним `"Internal server error"`, перш ніж його побачить клієнт. Ніколи не +розкривайте подробиці неочікуваного падіння віддаленій стороні, що викликає. У тесті це саме те, чого ви *не* хочете, і саме це змінює `raise_exceptions=True`: тест бачить справжнє повідомлення замість узагальненого. diff --git a/i18n/uk/pages/handlers/elicitation.md b/i18n/uk/pages/handlers/elicitation.md index 0152e48b7d..d7df17cf74 100644 --- a/i18n/uk/pages/handlers/elicitation.md +++ b/i18n/uk/pages/handlers/elicitation.md @@ -1,6 +1,6 @@ --- translation: - sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] + sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] tool: 1 --- # Еліцитація {#elicitation} @@ -89,7 +89,8 @@ translation: !!! warning Схема еліцитації не така виразна, як вхідна схема інструмента. Лише пласкі примітивні поля: `str`, `int`, `float`, `bool` або `Literal` з рядків (він стає `enum`). - Покладіть модель усередину моделі — і `ctx.elicit` викине виняток ще до того, як щось буде надіслано клієнтові: + Покладіть модель усередину моделі — і `ctx.elicit` викине виняток ще до того, як щось буде надіслано клієнтові. + Виклик інструмента завершується помилкою `Error executing tool `, а причина — у лозі сервера: ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -112,8 +113,8 @@ translation: !!! tip Відповідь валідується за вашою моделлю, перш ніж її побачить ваш код. Клієнт, що надсилає - `"maybe"` для `bool`, не зіпсує бронювання: виклик завершується помилкою - невідповідності схемі, а ваш `if` так і не виконується. + `"maybe"` для `bool`, не зіпсує бронювання: `ctx.elicit` викидає `ValueError`, виклик + завершується помилкою, а ваш `if` так і не виконується. ## Перенаправлення користувача на URL {#send-the-user-to-a-url} diff --git a/i18n/uk/pages/handlers/logging.md b/i18n/uk/pages/handlers/logging.md index 7bca678198..9a473cd627 100644 --- a/i18n/uk/pages/handlers/logging.md +++ b/i18n/uk/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # Логування {#logging} @@ -55,6 +55,8 @@ translation: `logging.basicConfig()` ніколи не замінює обробники, що вже існують. Якщо налаштувати логування самостійно до створення сервера, ваше налаштування має перевагу. +Так само не потрібен `try`/`except` у кожному обробнику лише для того, щоб фіксувати збої. Коли функція інструмента чи ресурсу викидає виняток, SDK записує його в лог за вас. Що саме логується і на якому рівні, пояснено на сторінці **[Обробка помилок](../servers/handling-errors.md#any-other-exception)**. + ## Спробуйте самі {#try-it} Запустіть сервер з MCP Inspector: diff --git a/i18n/uk/pages/run/index.md b/i18n/uk/pages/run/index.md index bfe000839a..d5f388f36c 100644 --- a/i18n/uk/pages/run/index.md +++ b/i18n/uk/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # Запуск сервера {#running-your-server} @@ -70,9 +70,9 @@ Inspector робить рівно те саме, що й справжній хо * `host` / `port`: де слухати. За замовчуванням `127.0.0.1` і `8000`. * `streamable_http_path`: де розташована кінцева точка MCP. За замовчуванням `/mcp`. -* `json_response=True`: відповідати на кожен POST одним JSON-тілом замість SSE-потоку. У такому тілі є місце для відповіді й ні для чого іншого, тож інструмент, який посеред запиту звертається назад до клієнта (`ctx.elicit()`, семплювання (sampling)), на цьому відрізку викидає `NoBackChannelError`, а сповіщення, прив'язані до поточного виклику (перебіг виконання від `ctx.report_progress()`, повідомлення журналу окремого виклику), відкидаються; окремий потік `GET` і далі несе не пов'язані з ним. +* `json_response=True`: відповідати на кожен POST одним JSON-тілом замість SSE-потоку. У такому тілі є місце для відповіді й ні для чого іншого, тож інструмент, який посеред запиту звертається назад до клієнта (`ctx.elicit()`, семплювання (sampling)), на цьому відрізку викидає `NoBackChannelError`, а сповіщення, прив'язані до поточного виклику (перебіг виконання від `ctx.report_progress()`, лог-повідомлення окремого виклику), відкидаються; окремий потік `GET` і далі несе не пов'язані з ним. * `stateless_http=True`: новий транспорт на кожен запит, без відстеження сесій. -* `max_request_body_size`: найбільший прийнятний розмір тіла POST у байтах. За замовчуванням 4 МіБ; більші запити +* `max_request_body_size`: найбільший прийнятний розмір тіла запиту в байтах. За замовчуванням 4 МіБ; більші запити отримують HTTP 413 ще до розбору чи створення сесії. Збільшуйте його лише тоді, коли легітимні MCP-повідомлення перевищують цей розмір. * `event_store`, `retry_interval`, `transport_security`: відновлюваність і захист від DNS-rebinding. Вони можуть зачекати, доки ви не розгорнете сервер деінде, крім localhost; `transport_security` описано на сторінці **[Розгортання та масштабування](deploy.md)**. diff --git a/i18n/uk/pages/servers/handling-errors.md b/i18n/uk/pages/servers/handling-errors.md index af68db9283..059b9432c3 100644 --- a/i18n/uk/pages/servers/handling-errors.md +++ b/i18n/uk/pages/servers/handling-errors.md @@ -1,13 +1,13 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # Обробка помилок {#handling-errors} -Інструмент може завершитися невдачею двома способами, і SDK обробляє їх зовсім по-різному. +Інструмент може завершитися невдачею трьома способами, і SDK обробляє кожен по-різному. -Викиньте звичайний виняток — і його побачить **модель**. Викиньте `MCPError` — і його побачить **протокол**. +Викиньте `ToolError` — і ваше повідомлення побачить **модель**. Викиньте `MCPError` — і його побачить **протокол**. Викиньте будь-що інше — і це збій: модель дізнається лише, що виклик не вдався, а трасування потрапить у ваш лог. Ця сторінка — про те, як вибрати. @@ -15,11 +15,11 @@ translation: Візьмімо інструмент, який щось шукає, і нехай пошук нічого не знайде: -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -У цих двох рядках немає нічого специфічного для MCP. `get_author` викидає звичайний `ValueError`, як це зробила б будь-яка функція Python. +`ToolError` з `mcp.server.mcpserver.exceptions` — це спосіб, яким інструмент повідомляє моделі, що щось пішло не так. Викличте його з назвою, якої немає в каталозі, і подивіться на результат: @@ -30,13 +30,15 @@ result.structured_content # None ``` * Запит **виконався успішно**. Результат є; на боці того, хто викликав, нічого не викинуто. -* `is_error` дорівнює `True`, а повідомлення вашого винятку (з назвою інструмента на початку) лежить у `content` — саме там, де читає модель. +* `is_error` дорівнює `True`, а ваше повідомлення (з назвою інструмента на початку) лежить у `content` — саме там, де читає модель. * `structured_content` дорівнює `None`. У невдалого виклику немає значення, яке можна було б структурувати. -Це **помилка інструмента**, і так за замовчуванням обробляється *будь-який* виняток, який викидає ваш інструмент. І майже завжди це саме те, що потрібно. +Це **помилка інструмента**, і майже завжди це саме те, що потрібно. Ваш інструмент викликає саме модель. Це вона обрала аргументи. Тож помилка інструмента — це репліка в розмові: модель читає *«No book titled 'Nothing' in the catalog.»*, розуміє, що не вгадала назву, і викликає знову з кращою. Один `raise` — і маєте агента, що сам виправляє свої помилки. +На сервері `ToolError` — це один рядок рівня `INFO` у лозі, без трасування. Ви цього очікували, тож розслідувати нічого. + !!! tip Ніколи не повертайте повідомлення про помилку з інструмента через `return`. Повернутий рядок має `is_error=False`, тож для моделі (і для кожного клієнтського інтерфейсу) це виглядає так, ніби інструмент спрацював і цей рядок і є відповіддю. @@ -44,7 +46,7 @@ result.structured_content # None ## Помилка, яку модель не може виправити {#an-error-the-model-cannot-fix} -Тепер замініть `ValueError` на `MCPError`. +Тепер замініть `ToolError` на `MCPError`. ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -77,10 +79,10 @@ result.structured_content # None Ці два шляхи відповідають на два різні запитання. -* **Викидайте будь-який виняток** у разі збою *виконання*: те, що інструмент намагався зробити, не вдалося. Виклик обрала модель, тож саме модель має побачити наслідок і отримати шанс виправитися. Назва з помилкою, зовнішній API, що не відповів вчасно, рядок, якого не існує, — усе це помилки інструмента. +* **Викидайте `ToolError`** у разі збою *виконання*: те, що інструмент намагався зробити, не вдалося. Виклик обрала модель, тож саме модель має побачити наслідок і отримати шанс виправитися. Назва з помилкою, зовнішній API, що не відповів вчасно, рядок, якого не існує, — усе це помилки інструмента. * **Викидайте `MCPError`**, коли слід відхилити *сам запит*: клієнтові бракує можливості, від якої залежить інструмент, сервер не в тому стані, щоб обслуговувати будь-кого, той, хто викликає, пропустив обов'язковий крок. Жодна повторна спроба моделі нічого з цього не виправить, тож передавати їй повідомлення немає сенсу. -Вирішує одне запитання: **чи могла б розумніша модель цього уникнути?** Так -> звичайний виняток. Ні -> `MCPError`. +Вирішує одне запитання: **чи могла б розумніша модель цього уникнути?** Так -> `ToolError`. Ні -> `MCPError`. За цим критерієм друга версія `get_author` зробила хибний вибір: краща назва все виправляє, тож модель заслуговувала побачити повідомлення. Ця версія тут, щоб показати механізм, а не щоб його рекомендувати. @@ -89,6 +91,25 @@ result.structured_content # None корисне навантаження `data`. Усе, що ви в них покладете, клієнт і отримає: SDK пересилає викинутий `MCPError` дослівно, не очищуючи його. +## Будь-який інший виняток {#any-other-exception} + +Тепер приберіть перевірку й дайте пошуку в словнику завершитися невдачею самому по собі: + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]` викидає `KeyError`. Ви його не передбачали, тож SDK вважає це збоєм: + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +Виклик усе одно повертає `is_error=True`, тож модель знає, що він не вдався, і може рухатися далі. Чого вона не отримує — то це тексту винятку: `KeyError` з вашого коду чи купа SQL від драйвера на три бібліотеки глибше можуть описувати нутрощі вашого сервера, тому за його межі вони ніколи не виходять. + +Натомість їх отримуєте ви. Сервер записує збій у лог на рівні `ERROR` з повним трасуванням як `Tool 'get_author' raised an unexpected exception`. Тож лог робочого середовища на рівні `WARNING` мовчить під час кожної `ToolError` і озивається, щойно щось справді зламалося. + ## Ресурс, якого не існує {#a-resource-that-doesnt-exist} Ресурси проводять ту саму межу й мають один іменований виняток для типового випадку. @@ -109,7 +130,7 @@ result.structured_content # None } ``` -Зверніть увагу: тут немає напіврезультату з `is_error=True`. Читання ресурсу або повертає вміст, або завершується помилкою: у ресурсів є лише протокольний шлях. Про шаблони й усе інше, що стосується ресурсів, — на сторінці **[Ресурси](resources.md)**. +Зверніть увагу: тут немає напіврезультату з `is_error=True`. Читання ресурсу або повертає вміст, або завершується помилкою: у ресурсів є лише протокольний шлях. `ResourceError` — те саме для збою, який не є «не знайдено» (`-32603`, ваше повідомлення), і обидва — це один рядок рівня `INFO` у вашому лозі. Будь-який інший виняток, окрім `MCPError`, — це збій: клієнт отримує `-32603`, де вказано лише URI, а трасування йде у ваш лог на рівні `ERROR`. Про шаблони й усе інше, що стосується ресурсів, — на сторінці **[Ресурси](resources.md)**. ## Помилки, які ви ніколи не викидаєте {#errors-you-never-raise} @@ -120,19 +141,21 @@ result.structured_content # None Це означає цілий клас інструкцій `raise`, які писати не треба: не перевіряйте повторно власні анотації типів. !!! info - Усе на цій сторінці — те, що бачить **клієнт**, і `Client` у пам'яті, з яким ви писатимете - тести, бачить рівно те саме. Навіть `raise_exceptions=True` не перетворює помилку інструмента - назад на трасування: до моменту, коли цей прапорець міг би спрацювати, ваш виняток уже став - результатом з `is_error=True`. Перевіряйте результат через assert. Цей підхід описано на сторінці **[Тестування](../get-started/testing.md)**. + Усе, що на цій сторінці бачить **клієнт**, бачить і `Client` у пам'яті, з яким ви писатимете + тести. Навіть `raise_exceptions=True` не повертає виняток інструмента, що впав, + тому, хто викликав: до моменту, коли цей прапорець міг би спрацювати, ваш виняток уже став + результатом з `is_error=True`. Перевіряйте результат через assert. Якщо потрібне трасування збою, воно + в лозі сервера, і `caplog` з pytest його перехоплює. Цей підхід описано на сторінці **[Тестування](../get-started/testing.md)**. ## Підсумки {#recap} -* Викиньте **будь-який виняток** в інструменті -> виклик повертає `is_error=True` з вашим повідомленням у `content`. Модель читає його й може повторити спробу. Це поведінка за замовчуванням. +* Викиньте **`ToolError`** в інструменті -> виклик повертає `is_error=True` з вашим повідомленням у `content`. Модель читає його й може повторити спробу. * Викиньте **`MCPError`** -> сам виклик завершується помилкою JSON-RPC. Модель нічого не бачить; розбирається хост. `code`, `message` і `data` доходять без змін. -* Вирішальне запитання: *чи могла б розумніша модель цього уникнути?* Так -> виняток. Ні -> `MCPError`. +* Вирішальне запитання: *чи могла б розумніша модель цього уникнути?* Так -> `ToolError`. Ні -> `MCPError`. +* Будь-який **інший виняток** — це збій -> `is_error=True` лише з `Error executing tool ` для моделі та запис рівня `ERROR` із трасуванням для вас. * `ResourceNotFoundError` з обробника ресурсу -> протокольний `-32602` з URI в `data`. * Некоректні аргументи відхиляються за схемою ще до запуску вашої функції; для них `raise` не потрібен. -* `from mcp import MCPError`; константи кодів помилок — з `mcp.types`. +* Імпорти: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError` і константи кодів помилок з `mcp.types`. З помилками розібралися. Це все, що сервер *надає назовні*. Про те, що кожен обробник може читати і що робити у відповідь клієнтові під час роботи, — наступний розділ: **[Усередині обробника](../handlers/index.md)**. diff --git a/i18n/uk/pages/servers/media.md b/i18n/uk/pages/servers/media.md index b8581446b5..088d8967af 100644 --- a/i18n/uk/pages/servers/media.md +++ b/i18n/uk/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # Медіа {#media} @@ -86,6 +86,24 @@ result.structured_content # None так `Audio` з байтів MP3 — і клієнту повідомлять `mime_type="audio/wav"`, після чого він сумлінно не зможе це декодувати. Передаєте `data=` — передавайте й `format=`. +## Вбудовування ресурсу {#embedding-a-resource} + +Інструмент може повернути й документ: текст або байти разом з URI, за яким він доступний, і MIME-типом. Це **`EmbeddedResource`**, ще один різновид блока вмісту. На відміну від звичайного `str`, він повідомляє клієнту, що саме це за вміст, тож клієнт може показати його як вкладення або впізнати ресурс, який уже знає. + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines` — звичайний ресурс (про них — на сторінці **[Ресурси](resources.md)**). Інструмент на запит передає моделі той самий документ, а прямий виклик `guidelines()` зберігає єдине джерело істини. +* `EmbeddedResource` і `TextResourceContents` беруться з `mcp.types`. Допоміжного класу, як для зображень, немає: побудований блок потрапляє в результат без змін, і `structured_content` теж немає. +* Використовуйте URI, під яким ресурс зареєстровано, щоб клієнт міг зрозуміти, що вкладення й `brand://guidelines` — той самий документ. Дозволений будь-який URI, зареєстрований чи ні. + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +Для двійкового вмісту замість `TextResourceContents` використовуйте `BlobResourceContents(uri=..., mime_type=..., blob=...)` з байтами в кодуванні base64 у полі `blob`. Щоб надіслати лише вказівник, за яким клієнт зможе пізніше виконати `resources/read`, поверніть натомість `ResourceLink(name=..., uri=...)` — це теж блок вмісту. + ## Іконки {#icons} `Icon` — це метадані, а не вміст. Він не містить зображення, а вказує на нього через URI, і клієнт може завантажити його й показати поруч із назвою сервера, інструментом, ресурсом чи промптом. @@ -115,6 +133,7 @@ client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_ * Поверніть з інструмента `Image` або `Audio` — і клієнт отримає блок `ImageContent` / `AudioContent`: ваші байти в кодуванні base64 з MIME-типом. * Створюйте їх із `path=`, і тоді MIME-тип визначить розширення, або з `data=` у пам'яті плюс явний `format=`. +* Поверніть `EmbeddedResource`, щоб покласти в результат документ (текст або blob у base64 з його URI та MIME-типом), або `ResourceLink`, щоб надіслати лише вказівник. * Медіарезультати не мають ні `structured_content`, ні схеми виводу. * `Icon` — це вказівник: URI `src` плюс необов'язкові `mime_type`, `sizes` і `theme`. * `icons=[...]` працює на сервері, інструментах, ресурсах і промптах, а клієнти знаходять їх у відповідних об'єктах. diff --git a/i18n/uk/pages/servers/prompts.md b/i18n/uk/pages/servers/prompts.md index 8fedcf3147..cb5d8592ea 100644 --- a/i18n/uk/pages/servers/prompts.md +++ b/i18n/uk/pages/servers/prompts.md @@ -1,6 +1,6 @@ --- translation: - sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094] + sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd] tool: 1 --- # Промпти {#prompts} @@ -139,10 +139,55 @@ uv run mcp dev server.py ``` !!! info - Якщо ви читали сторінку **[Інструменти](tools.md)**, то вже знаєте все, що є на цій. Той самий декоратор, той самий + Якщо ви читали сторінку **[Інструменти](tools.md)**, то вже знаєте все, про що йшлося досі. Той самий декоратор, той самий docstring як опис, ті самі `Annotated`/`Field`. Змінюється лише те, хто його запускає (користувач) і куди йде результат (у розмову). +## Більше ніж текст {#more-than-text} + +`UserMessage` і `AssistantMessage` також приймають блок вмісту або допоміжний об'єкт `Image` / `Audio` всюди, де приймають `str`. У промптах трапляються два випадки: прикріпити документ і прикріпити зображення. + +### Вбудовування файлу {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* Посібник зі стилю — це ресурс за адресою `style://python` (про них — на сторінці **[Ресурси](resources.md)**), який читається з файлу `style-guide.md` поруч із `server.py`. Покладіть туди будь-який Markdown-файл. +* `EmbeddedResource(resource=TextResourceContents(...))`, обидва з `mcp.types`, несе файл разом із його URI та MIME-типом як перше повідомлення; запит, що на нього посилається, іде слідом як звичайний текст. +* Вбудовування замість вставлення посібника в f-рядок дає клієнту змогу показати його як вкладення й пізніше знову відкрити `style://python`, а модель отримує файл дослівно. Для двійкового файлу використовуйте `BlobResourceContents` із `blob` у base64. + +Після генерування `content` першого повідомлення — це блок `resource`: + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### Прикріплення зображення {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image` — допоміжний клас зі сторінки **[Зображення, аудіо та піктограми](media.md)**. `UserMessage` перетворює його на блок `ImageContent` (файл закодовано в base64, MIME-тип вгадано з `.png`), коли промпт генерується; `Audio` так само стає `AudioContent`. +* Покладіть будь-який PNG з іменем `architecture.png` поруч із `server.py`. Аргументи промпту — рядки, тому зображення завжди надходить із сервера; `component` лише дає слова. + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## Зміна списку під час роботи {#changing-the-list-at-runtime} + +Промпти можна додавати, поки клієнти під'єднані, наприклад щоб користувач міг зберегти інструкцію як власний пункт меню. Зареєструйте промпт, а тоді надішліть сповіщення: + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` реєструє функцію точно так, як це зробив би `@mcp.prompt()`, а `mcp.remove_prompt(name)` — зворотна дія. `add_prompt` залишає наявний запис із тим самим іменем, а не перезаписує його, тому інструмент спершу видаляє старий, щоб збереження працювало як заміна. `prompts/list` відображає зміну одразу. +* `await ctx.notify_prompts_changed()` надсилає `notifications/prompts/list_changed` кожному клієнту `2026-07-28`, що слухає потік `subscriptions/listen` (**[Підписки](../handlers/subscriptions.md)**). `await ctx.session.send_prompt_list_changed()` надсилає його клієнту, який зробив виклик, якщо той старший за 2026 (**[Обслуговування клієнтів старого покоління](../run/legacy-clients.md)**). Викликайте обидва; кожен нічого не робить, коли сповіщати нікого. +* Клієнт, що отримав сповіщення, знову викликає `prompts/list`. У Python-класі `Client` це `async with client.listen(prompts_list_changed=True) as sub:`, що видає подію `PromptsListChanged`. + ## Підсумки {#recap} * `@mcp.prompt()` над функцією робить її промптом. Ім'я — з функції, опис — з docstring. @@ -151,5 +196,7 @@ uv run mcp dev server.py * Поверніть `str` — і він стане одним повідомленням користувача. Поверніть список `UserMessage` / `AssistantMessage`, щоб закласти багатоходову розмову. * `title=` і `Field(description=...)` — це те, що клієнт показує у своєму інтерфейсі. * Відсутній обов'язковий аргумент провалює весь запит. Окремого результату з помилкою для промпту немає. +* Загорніть `EmbeddedResource` або `Image` у `UserMessage`, щоб прикріпити документ чи зображення. +* Додавайте або видаляйте промпти під час роботи через `mcp.add_prompt(...)` / `mcp.remove_prompt(...)`, а тоді викликайте `await ctx.notify_prompts_changed()` і `await ctx.session.send_prompt_list_changed()`. Серверне автодоповнення аргументів промпту (або шаблону ресурсу) — це **[Автодоповнення](completions.md)**. diff --git a/i18n/uk/pages/servers/structured-output.md b/i18n/uk/pages/servers/structured-output.md index 585204430d..3f897661e7 100644 --- a/i18n/uk/pages/servers/structured-output.md +++ b/i18n/uk/pages/servers/structured-output.md @@ -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 --- # Структурований вивід {#structured-output} @@ -105,7 +105,7 @@ result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions --8<-- "docs_src/structured_output/tutorial003.py" ``` -Під час виконання `TypedDict` — це звичайний `dict`, тож саме його ви будуєте й повертаєте. Схема, валідація і `structured_content` ідентичні версії з `BaseModel` (за винятком описів, для яких у `TypedDict` немає місця). +Під час виконання `TypedDict` — це звичайний `dict`, тож саме його ви будуєте й повертаєте. Схема, валідація і `structured_content` підпорядковуються тим самим правилам, що й у версії з `BaseModel`: додайте docstring класу або `Annotated[..., Field(description=...)]` — і вони стануть описами, а ключ `NotRequired`, якого немає в словнику, не потрапить і до `structured_content`. ## Dataclass {#a-dataclass} @@ -187,18 +187,19 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} Анотація обіцяє `WeatherData`. Відповідь зовнішнього сервісу перестала надсилати `humidity`. !!! check - Викличте `get_weather` — і він не передасть клієнту тихцем напівпорожній об'єкт. Виклик завершується помилкою, - і перші рядки помилки називають поле: + Викличте `get_weather` — і він не передасть клієнту тихцем напівпорожній об'єкт. Виклик завершується помилкою: + клієнт отримує `is_error=True` з `Error executing tool get_weather`, тож модель знає, що виклик + не вдався, замість того щоб упевнено читати погоду, якої немає. Назва поля — для вас, + у лозі сервера на рівні `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] ``` - Цей текст повертається як результат інструмента з `is_error=True`, тож модель знає, що виклик не вдався, - замість того щоб упевнено читати погоду, якої немає. - До речі, повертати звичайний `dict` з інструмента з `-> WeatherData` цілком можна. Саме це й видав `json.loads`. Перевіряється значення, а не тип Python. ## Відмова від структурованого виводу {#opting-out} @@ -213,6 +214,10 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} Протилежне, `structured_output=True`, перетворює автоматичне визначення на вимогу: інструмент, чий тип повернення не може дати схему, викидає виняток під час імпорту замість відкоту до тексту. +## Блоки вмісту й медіа {#content-blocks-and-media} + +Блоки вмісту й медіа (`TextContent`, `EmbeddedResource`, `Image`, `Audio` та подібні — самі по собі, як елементи `list`, `tuple` чи `Sequence` або як варіанти об'єднання типів) вимкнено за вас: вони призначені для читання моделлю, тож автоматичне визначення не виводить із них схеми (`Image` та `Audio` описано на сторінці **[Зображення, аудіо та іконки](media.md)**). `structured_output=True` усе одно примусово створює схему для класів блоків вмісту. + ## Клас без анотацій типів {#a-class-without-type-hints} Є один спосіб опинитися без структурованого виводу, не просивши про це: повернути клас, у **тілі якого немає анотацій**. @@ -245,6 +250,6 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} * Скаляри, списки, кортежі й об'єднання типів загортаються в `{"result": ...}`. Моделі, `TypedDict`, dataclass, анотовані класи й `dict[str, ...]` — уже об'єкти й лишаються як є. * Кожен результат несе `content` (текст, для моделі) **і** `structured_content` (дані, для застосунку). * Те, що ви повертаєте, перевіряється на відповідність схемі. Невідповідність — це помилка інструмента, а не зіпсований результат. -* `structured_output=False` вимикає це для інструмента. Клас без анотацій типів вимикає це мовчки; пильнуйте. +* `structured_output=False` вимикає це для інструмента. Блоки вмісту, `Image` та `Audio` вимкнено за замовчуванням; клас без анотацій типів вимикає це мовчки, тож пильнуйте. Тепер ви володієте всім, що інструмент може сказати у відповідь. Далі — другий примітив: **[Ресурси](resources.md)**. diff --git a/i18n/uk/pages/servers/tools.md b/i18n/uk/pages/servers/tools.md index d41dfd6386..dba6d743fd 100644 --- a/i18n/uk/pages/servers/tools.md +++ b/i18n/uk/pages/servers/tools.md @@ -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} @@ -39,6 +39,8 @@ translation: Обидва аргументи потрапили в `required`, бо жоден не має типового значення. За мить ви це виправите. (Ключі `title` — артефакти Pydantic; контракт складають властивості, їхні типи та `required`.) +Ключа `$schema` теж немає: схему без нього MCP трактує як **JSON Schema 2020-12** — саме її й генерує Pydantic, тож вибирати нічого, доки не візьметеся писати схеми вручну для **[низькорівневого Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)**. + !!! tip Анотації типів тут — не документація. Вони і є **контракт**. Якщо клієнт надішле `"limit": "ten"`, SDK відхилить запит ще до того, як ваша функція запуститься. diff --git a/i18n/uk/pages/servers/uri-templates.md b/i18n/uk/pages/servers/uri-templates.md index 258d230635..38f294445d 100644 --- a/i18n/uk/pages/servers/uri-templates.md +++ b/i18n/uk/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # URI-шаблони та безпека шляхів {#uri-templates-and-path-safety} @@ -170,7 +170,7 @@ SDK підтримує підмножину, відібрану для зіст `safe_join`, щоб розв'язати шлях і впевнитися, що він лишається всередині базового каталогу: -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` @@ -211,9 +211,10 @@ SDK підтримує підмножину, відібрану для зіст !!! tip Якщо обробник не може виконати запит (файл не існує, ідентифікатор - невідомий), викиньте виняток. SDK перетворить його на відповідь з - помилкою. Про різницю між помилкою протоколу та помилкою інструмента — - на сторінці **[Обробка помилок](handling-errors.md)**. + невідомий), викиньте `ResourceNotFoundError`, як це робить `read_manual` + вище. Клієнт отримає `-32602` з вашим повідомленням і URI. Неочікуваний + виняток натомість стає загальною помилкою `-32603`. Див. + **[Обробка помилок](handling-errors.md#a-resource-that-doesnt-exist)**. ## Ресурси на низькорівневому Server {#resources-on-the-low-level-server} diff --git a/i18n/uk/pages/troubleshooting.md b/i18n/uk/pages/troubleshooting.md index ce0584c1b0..2f5df7a21d 100644 --- a/i18n/uk/pages/troubleshooting.md +++ b/i18n/uk/pages/troubleshooting.md @@ -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 --- # Усунення несправностей {#troubleshooting} @@ -81,11 +81,11 @@ async def main() -> None: `__aexit__` — це від'єднання, тому й немає `client.close()`, про який можна забути. Сторінка **[Тестування](get-started/testing.md)** побудована саме на цьому шаблоні. -## `Error executing tool : ` і `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `, `Error executing tool ` і `Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} Перед вами **результат**, а не виняток. `call_tool` нічого не викинув і ніколи не викине для інструмента, що завершився збоєм. -Викличте `forecast` для міста, якого сервер не знає, — і виняток, який він викидає, повертається із запитом, позначеним як *успішний*: +Викличте `forecast` для міста, якого сервер не знає, — і `ToolError`, який він викидає, повертається із запитом, позначеним як *успішний*: ```python result.is_error # True @@ -97,6 +97,8 @@ result.structured_content # None Виправлення — на боці клієнта: **перевіряйте `result.is_error`**. `try/except` навколо `call_tool` не перехопить жодного з цих випадків, бо перехоплювати нічого. Це зроблено навмисно, і це найкорисніше, що варто засвоїти з цієї сторінки: виклик обрала *модель*, тож саме модель отримує повідомлення й шанс спробувати знову. Докладніше — на сторінці **[Обробка помилок](servers/handling-errors.md)**, зокрема про шлях через `MCPError`, який *таки* викидає виняток. +Коротка форма, `Error executing tool ` без повідомлення, означає, що інструмент **упав**: з нього вийшов виняток, якого він не передбачав (або його повернене значення не пройшло вихідну схему), і текст цього винятку в передані дані не потрапляє. Трасування — у **лозі сервера** на рівні `ERROR`, як `Tool '' 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} Ви написали `@mcp.tool` замість `@mcp.tool()`. `tool()` — це *фабрика* декораторів: без дужок Python передає вашу функцію в її параметр `name=`. @@ -410,7 +412,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## Підсумки {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup` ніколи не є самою помилкою. Читайте **останній рядок**; перехоплення `MCPError` *всередині* блоку `async with Client(...)` повністю оминає обгортання. -* `call_tool` не викидає виняток для інструмента, що завершився збоєм. `Error executing tool ...` і `Unknown tool: ...` — це результати: перевіряйте `result.is_error`. +* `call_tool` не викидає виняток для інструмента, що завершився збоєм. `Error executing tool ...` і `Unknown tool: ...` — це результати: перевіряйте `result.is_error`. Якщо після імені інструмента немає повідомлення, він упав, а трасування — у лозі сервера. * `Client must be used within an async context manager` -> використовуйте `async with`. `Use @tool() instead of @tool` -> додайте дужки. * `Tool already exists:` у лозі сервера — єдина ознака того, що два однойменні інструменти злилися в один. * Один 421, три написання: `Server returned an error response` (python `Client`), `421 Misdirected Request` / `Invalid Host header` (усе інше), `Invalid Host header: ` (лог сервера). Виправлення: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`. diff --git a/i18n/uk/pages/whats-new.md b/i18n/uk/pages/whats-new.md index d501c066ee..02d1a39e71 100644 --- a/i18n/uk/pages/whats-new.md +++ b/i18n/uk/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # Що нового у v2 {#whats-new-in-v2} @@ -46,9 +46,9 @@ v1 давав три вкладені шари: контекстний мене --8<-- "docs_src/client/tutorial001.py" ``` -`Client` приймає об'єкт сервера (у пам'яті, без транспорту: це сценарій тестування), URL (Streamable HTTP) або будь-який контекстний менеджер транспорту, як-от `stdio_client(...)`. Вхід в `async with` під'єднує та узгоджує версію протоколу, хай яким поколінням говорить сервер; після цього `client.server_capabilities` і `client.protocol_version` просто є, як і `client.server_info`, коли сервер себе ідентифікує (тепер це `Implementation | None`, бо ідентичність у поколінні 2026 необов'язкова). Колбеки семплювання й еліцитації, зареєстровані у v1, і далі працюють (їхні тіла зазнають того самого перейменування атрибутів у snake_case, що й усе інше на цій сторінці), тепер вони також відповідають на запити всередині результатів у стилі 2026 (нижче) і виконуються паралельно, а не по одному. `ClientSession` досі лежить під сподом для тих, кому потрібна низькорівнева поверхня, і `client.session` її віддає; вона теж змінилася (працює на новому рушії диспетчера, і деякі її власні сигнатури змінилися), тож прочитайте **[Посібник з міграції](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**, перш ніж спускатися нижче. +`Client` приймає об'єкт сервера (у пам'яті, без транспорту: це сценарій тестування), URL (Streamable HTTP), `StdioServerParameters` (підпроцес stdio) або будь-який інший контекстний менеджер транспорту, як-от `sse_client(...)`. Вхід в `async with` під'єднує та узгоджує версію протоколу, хай яким поколінням говорить сервер; після цього `client.server_capabilities` і `client.protocol_version` просто є, як і `client.server_info`, коли сервер себе ідентифікує (тепер це `Implementation | None`, бо ідентичність у поколінні 2026 необов'язкова). Колбеки семплювання й еліцитації, зареєстровані у v1, і далі працюють (їхні тіла зазнають того самого перейменування атрибутів у snake_case, що й усе інше на цій сторінці), тепер вони також відповідають на запити всередині результатів у стилі 2026 (нижче) і виконуються паралельно, а не по одному. `ClientSession` досі лежить під сподом для тих, кому потрібна низькорівнева поверхня, і `client.session` її віддає; вона теж змінилася (працює на новому рушії диспетчера, і деякі її власні сигнатури змінилися), тож прочитайте **[Посібник з міграції](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**, перш ніж спускатися нижче. -**[Клієнт](client/index.md)** знайомить із ним, **[Транспорти клієнта](client/transports.md)** описує три форми під'єднання, **[Колбеки клієнта](client/callbacks.md)** — самі колбеки, а **[Тестування](get-started/testing.md)** показує шаблон роботи в пам'яті, що замінює допоміжну функцію `create_connected_server_and_client_session()` з v1. +**[Клієнт](client/index.md)** знайомить із ним, **[Транспорти клієнта](client/transports.md)** описує чотири форми під'єднання, **[Колбеки клієнта](client/callbacks.md)** — самі колбеки, а **[Тестування](get-started/testing.md)** показує шаблон роботи в пам'яті, що замінює допоміжну функцію `create_connected_server_and_client_session()` з v1. ### Низькорівневий `Server` перебудовано, а не перейменовано {#the-low-level-server-was-rebuilt-not-renamed} @@ -134,7 +134,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB Перейменування заявляють про себе самі. А оце — ні: * **Синхронні функції виконуються в робочому потоці.** Інструмент, оголошений через `def` (або ресурс, промпт чи резолвер), більше не блокує цикл подій; плата за це — його тіло більше не виконується *в* потоці циклу подій, що важливо для коду, прив'язаного до потоку. Обробники `async def` не зачеплено. **[Посібник з міграції](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**. -* **`MCPError` (`McpError` у v1), викинутий усередині інструмента, тепер є помилкою протоколу.** Модель його ніколи не бачить. Будь-який інший виняток, як і раніше, стає результатом з `is_error=True`, який модель може прочитати й на який може відреагувати. Розмежування — на сторінці **[Обробка помилок](servers/handling-errors.md)**. +* **`MCPError` (`McpError` у v1), викинутий усередині інструмента, тепер є помилкою протоколу.** Модель його ніколи не бачить. Будь-який інший виняток, як і раніше, стає результатом з `is_error=True`, але до моделі доходить лише повідомлення `ToolError`: будь-який інший виняток тепер читається як `Error executing tool `, а трасування стека лишається в лозі вашого сервера. Розмежування — на сторінці **[Обробка помилок](servers/handling-errors.md)**. * **Результати перевіряються перед відправленням.** Зібраний вручну `Tool`, у якого `input_schema` дорівнює `{}`, тепер провалює `tools/list` (специфікація вимагає `"type": "object"`). Сервери, побудовані на `@mcp.tool()`, цього ніколи не бачать: їхні схеми пише SDK. * **Ваш клієнт перевіряє те, що отримує.** `list_tools()` і `call_tool()` звіряють відповідь сервера з узгодженою версією протоколу, тож не зовсім валідний сервер, який поблажливий розбір v1 терпів, тепер викидає `pydantic.ValidationError`. Якщо ви під'єднуєтеся до серверів, яких не контролюєте, готуйтеся бути тим, хто їх знайде; подробиці — у **[Посібнику з міграції](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)**. * **URI-шаблони тепер — справжній RFC 6570.** `{+path}`, `{?query}` та подібні працюють, зіставлення точне, а не приблизне за регулярним виразом, а обхід шляху у видобутих значеннях за замовчуванням відхиляється. Суворіші шаблони падають під час декорування, а не на першому запиті. **[URI-шаблони](servers/uri-templates.md)**. diff --git a/i18n/zh-hant/pages/advanced/low-level-server.md b/i18n/zh-hant/pages/advanced/low-level-server.md index a619753879..9e05b31442 100644 --- a/i18n/zh-hant/pages/advanced/low-level-server.md +++ b/i18n/zh-hant/pages/advanced/low-level-server.md @@ -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 --- # 低階 Server {#the-low-level-server} @@ -116,6 +116,17 @@ asyncio.run(main()) 伺服器從不比對這兩個欄位。這個 SDK 的 `Client` 會:回傳的 `structured_content` 如果不符合你宣告的 `output_schema`,`call_tool` 就會引發 `RuntimeError`,訊息以 `Invalid structured content returned by tool search_books` 開頭,接著引用 `jsonschema` 的失敗內容。承諾一個 schema 很便宜;守住承諾是你的事。回傳型別與 schema 的完整階梯請見 **[結構化輸出](../servers/structured-output.md)**。 +## 方言是 JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12} + +`input_schema` 和 `output_schema` 是 JSON Schema,而 [MCP 規範](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage)把方言定了下來:沒有 `$schema` 鍵的 schema 就是 **JSON Schema 2020-12**。`MCPServer` 產生的 schema 依賴這個預設值(Pydantic 寫的是 2020-12,並省略這個鍵),手寫的 dict 也同樣受它約束,所以整套 2020-12 詞彙都可以用: + +```python title="server.py" hl_lines="8 14-15" +--8<-- "docs_src/lowlevel/tutorial007.py" +``` + +* `input_schema` 的根必須是 `"type": "object"`。在它旁邊,`oneOf`、`additionalProperties`、`anyOf`、`if`/`then`/`else`、`prefixItems`、帶本地 `$ref` 的 `$defs`,以及其餘的 2020-12 關鍵字,都會照你寫的一字不差地送到用戶端。 +* 不需要 `$schema` 鍵。只有想改用較舊的草案時才加:這個 SDK 的 `Client` 會依工具的 `output_schema` 驗證 `structured_content`,它從 `$schema` 挑選驗證器,沒有的話就用 2020-12。 + ## `_meta`:給應用程式,不是給模型 {#\_meta-for-the-application-not-the-model} `content` 是答案中模型會讀的部分。`structured_content` 是同一個答案的型別化資料。`_meta` 是第三個管道:跟著結果一起送給**用戶端應用程式**的資料,完全不屬於答案的一部分。 @@ -166,7 +177,7 @@ asyncio.run(main()) --8<-- "docs_src/lowlevel/tutorial006.py" ``` -* 第一個引數是方法字串。通知有個孿生的 `add_notification_handler`。 +* 第一個引數是方法字串。通知有個孿生的 `add_notification_handler`。它的處理函式在 stdio 和交握世代的 HTTP 連線上會觸發;在 `2026-07-28` 的 Streamable HTTP 路徑上,用戶端的通知 POST 會收到 `202` 確認而不會被分派,因為該修訂版沒有定義任何透過 HTTP 的用戶端到伺服器通知。 * `params_type` 是傳入的 `params` 在處理函式執行**之前**用來驗證的模型,所以自訂方法**確實**享有工具沒有的驗證。繼承 `RequestParams`,讓 `_meta` 欄位和其他方法一樣解析。 * 處理函式回傳 `BaseModel`、`dict` 或 `None`。SDK 會把它序列化成 JSON-RPC 結果。 @@ -179,7 +190,7 @@ ValueError: 'initialize' is handled by the server runner and cannot be overridde use Server.middleware to observe or wrap initialization ``` -交握屬於執行器。`server/discover`、`ping`,以及其他所有內建方法,都可以替換。 +交握屬於執行器。`server/discover`、`ping`,以及其他所有內建方法,都可以由你替換。 !!! tip 那則錯誤裡提到的 `Server.middleware` 會包住**每一則**傳入訊息,包括 `initialize`。如果想做的是觀察或改寫流量,而不是回應新方法,請從 **[中介軟體](middleware.md)** 開始。 diff --git a/i18n/zh-hant/pages/advanced/middleware.md b/i18n/zh-hant/pages/advanced/middleware.md index a590a79bc7..80b83d9363 100644 --- a/i18n/zh-hant/pages/advanced/middleware.md +++ b/i18n/zh-hant/pages/advanced/middleware.md @@ -1,6 +1,6 @@ --- translation: - sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c] + sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43] tool: 1 --- # 中介軟體 {#middleware} @@ -42,7 +42,7 @@ tools/call took 0.1 ms 重點就在這裡。中介軟體包住**每一則**傳入的訊息: * 連線建立階段:`server/discover`,或在舊版工作階段(session)上的 `initialize` 和 `notifications/initialized`。 -* 每一個請求和每一則通知。對通知而言,`ctx.request_id is None`,`call_next(ctx)` 回傳 `None`,而你回傳的任何東西都會被丟棄。 +* 每一個抵達伺服器的請求和每一則通知。對通知而言,`ctx.request_id is None`,`call_next(ctx)` 回傳 `None`,而你回傳的任何東西都會被丟棄。(在 `2026-07-28` 的 Streamable HTTP 路徑上,用戶端以 POST 送出的通知會在傳輸層直接以 `202` 確認收到、從不分派,所以也不會抵達中介軟體;該修訂版沒有定義任何透過 HTTP 由用戶端送往伺服器的通知。) * 連伺服器沒有處理函式的方法也一樣:`call_next` 會引發 `MCPError(-32601, "Method not found")`,**穿過**你的中介軟體一路送到用戶端。 ## 在裡面能做什麼 {#what-you-can-do-inside-one} @@ -75,7 +75,7 @@ SDK 只附帶一個中介軟體,而且它已經在伺服器的清單上了: ## 重點回顧 {#recap} * 中介軟體是 `async (ctx, call_next) -> result`,以 `MCPServer(middleware=[...])` 傳入(或附加到 `mcp.middleware`),在低階的 `Server` 上則附加到 `server.middleware`。 -* 它包住**每一則**傳入的訊息(`server/discover`、`initialize`、請求、通知、未知的方法),並由最外層開始執行。 +* 它包住**每一則**抵達伺服器的傳入訊息(`server/discover`、`initialize`、請求、通知、未知的方法),並由最外層開始執行。 * 用 `ctx.request_id is None` 區分通知和請求。 * 不呼叫 `call_next` 改為引發例外,就能拒絕一則訊息;連線會存活下來。 * SDK 自己的 OpenTelemetry 追蹤也是一個中介軟體,已經在清單上。請見 **[OpenTelemetry](../run/opentelemetry.md)**。 diff --git a/i18n/zh-hant/pages/client/index.md b/i18n/zh-hant/pages/client/index.md index f535d5ad0c..8bd6edeea1 100644 --- a/i18n/zh-hant/pages/client/index.md +++ b/i18n/zh-hant/pages/client/index.md @@ -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 --- # 用戶端 {#the-client} @@ -27,9 +27,10 @@ Python 程式要和 MCP 伺服器對話,靠的就是 **`Client`**。 * `MCPServer`(或低階的 `Server`)實例:在**同一個處理程序內**連線。 * URL 字串(`Client("http://localhost:8000/mcp")`):Streamable HTTP,也就是正式環境的路徑。 -* 一個**傳輸**:任何可以 `async with ... as (read, write)` 的東西,例如包住子處理程序的 `stdio_client(...)`。 +* `StdioServerParameters`:要當作**子處理程序**啟動的命令,透過它的 stdin 和 stdout 溝通。 +* 一個**傳輸**:任何可以 `async with ... as (read, write)` 的東西,例如用 `streamable_http_client(url, http_client=...)` 包住你自己的 HTTP 用戶端。 -這一頁其餘的內容在這三種情況下完全相同。標頭、子處理程序、逾時,以及 `Transport` 協定另外有專屬的頁面:**[用戶端傳輸方式](transports.md)**。 +這一頁其餘的內容在這四種情況下完全相同。標頭、子處理程序、逾時,以及 `Transport` 協定另外有專屬的頁面:**[用戶端傳輸方式](transports.md)**。 ### 連線後的用戶端上有什麼 {#whats-on-a-connected-client} @@ -82,7 +83,7 @@ tool.description # 'Search the catalog by title or author.' `call_tool(name, arguments)` 會執行工具,並回傳一個 `CallToolResult`。 -```python title="client.py" hl_lines="26-33" +```python title="client.py" hl_lines="27-34" --8<-- "docs_src/client/tutorial003.py" ``` @@ -113,7 +114,7 @@ result.is_error # False 會引發例外的工具,在用戶端這邊**不會**引發例外。它會以一個普通的結果回來,帶著 `is_error=True`。 !!! check - 向 `lookup_book` 要 `"Solaris"`(目錄裡沒有的書名),函式會引發 `ValueError`。呼叫仍然正常回傳: + 向 `lookup_book` 要 `"Solaris"`(目錄裡沒有的書名),函式會引發 `ToolError`。呼叫仍然正常回傳: ```python result.is_error # True @@ -121,7 +122,7 @@ result.is_error # False result.structured_content # None ``` - 例外的訊息落在 `content` 裡,**模型**可以讀到它並再試一次。這是刻意的設計:工具錯誤是對話的一部分,不是當機。在信任 `structured_content` 之前,一定要先看 `is_error`。 + `ToolError` 的訊息落在 `content` 裡,**模型**可以讀到它並再試一次。這是刻意的設計:工具錯誤是對話的一部分,不是當機。(假如工具是因為其他例外而當掉,`content` 就只會寫 `Error executing tool lookup_book`。)在信任 `structured_content` 之前,一定要先看 `is_error`。 !!! warning `is_error=True` 涵蓋的不只是你自己的 `raise`。要一個伺服器根本沒有的工具(`call_tool("does_not_exist", {})`),也不會引發任何例外。你會拿回同樣的形狀:`is_error=True`,`content` 裡是 `Unknown tool: does_not_exist`。只有在伺服器回的是 JSON-RPC **錯誤**而不是結果時,`Client` 的方法才會引發 `MCPError`;伺服器什麼時候產生哪一種,請見 **[處理錯誤](../servers/handling-errors.md)**。 diff --git a/i18n/zh-hant/pages/client/transports.md b/i18n/zh-hant/pages/client/transports.md index 7c262749bb..a7f839d428 100644 --- a/i18n/zh-hant/pages/client/transports.md +++ b/i18n/zh-hant/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # 用戶端傳輸方式 {#client-transports} @@ -76,15 +76,15 @@ translation: **stdio** 伺服器是一個子處理程序。用戶端啟動它,把 JSON-RPC 寫進它的 stdin,再從它的 stdout 讀取 JSON-RPC。桌面版 MCP 主機(host)就是這樣在你的機器上執行伺服器的:主機**就是**這段程式碼加上一個 UI,而 **[連接到真正的主機](../get-started/real-host.md)** 則是從主機那一側、以設定檔的形式看同一個關係。 -用 `StdioServerParameters` 描述這個處理程序,用 `stdio_client` 把它變成傳輸,再把**那個**交給 `Client`: +用 `StdioServerParameters` 描述這個處理程序,再把它交給 `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` 不接受單獨的參數物件。`StdioServerParameters` 是設定;`stdio_client(server)` 才是知道怎麼依據它啟動處理程序的傳輸。一定要包起來。 +進入區塊時會啟動處理程序。離開區塊時,子處理程序也會一併關閉:關掉 stdin、等待、拖太久就強制終止。你永遠不需要自己清理。 -離開 `async with` 區塊時,子處理程序也會一併關閉:關掉 stdin、等待、拖太久就強制終止。你永遠不需要自己清理。 +子處理程序的 stderr 會接到你的 stderr。想送到別的地方,就自己用 `stdio_client`(來自 `mcp`)建立傳輸,改傳入那個:`Client(stdio_client(server, errlog=log_file))`。 !!! warning 子處理程序**不會**繼承你的環境。它拿到的是一份精簡的允許清單(POSIX 上是 `HOME`、`LOGNAME`、`PATH`、`SHELL`、`TERM` 和 `USER`),這樣敏感的東西才不會洩漏到一個可能不是你寫的處理程序裡。 @@ -99,16 +99,16 @@ translation: 對 `Client` 來說,上面這些全都是同一種東西。 -**傳輸**是任何會產出一對 `(read, write)` 訊息串流的非同步 context manager:正式地說,就是 `mcp.client` 裡的 `Transport` 協定。`Client` 依型別解析它的引數:伺服器物件就在處理程序內連線,`str` 會變成 `streamable_http_client(url)`,其他任何東西則直接當成傳輸進入。最後這條規則就是為什麼 `stdio_client(...)`、`streamable_http_client(...)` 和 `sse_client(...)` 都能放進同一個位置,也是為什麼你可以自己寫一個。 +**傳輸**是任何會產出一對 `(read, write)` 訊息串流的非同步 context manager:正式地說,就是 `mcp.client` 裡的 `Transport` 協定。`Client` 依型別解析它的引數:伺服器物件就在處理程序內連線,`str` 會變成 `streamable_http_client(url)`,`StdioServerParameters` 會變成 `stdio_client(params)`,其他任何東西則直接當成傳輸進入。最後這條規則就是為什麼 `stdio_client(...)`、`streamable_http_client(...)` 和 `sse_client(...)` 都能放進同一個位置,也是為什麼你可以自己寫一個。 ## 重點回顧 {#recap} * `Client(mcp)`(伺服器物件)在記憶體內連線。用在測試和嵌入。 * `Client("http://.../mcp")`(URL)透過 Streamable HTTP 連線,也就是正式環境用的傳輸方式。 * 標頭、驗證、proxy 和逾時都放在你傳給 `streamable_http_client(url, http_client=...)` 的 `httpx2.AsyncClient` 上。沒有 `headers=` 這個關鍵字引數。 -* stdio 是 `Client(stdio_client(StdioServerParameters(...)))`,絕對不是單獨的參數物件。 +* stdio 是 `Client(StdioServerParameters(...))`。只有要把子處理程序的 stderr 導到別處時,才需要自己用 `stdio_client(...)` 包起來。 * 子處理程序拿到的是允許清單上的環境,不是你的環境;`env=` 會往上加。 -* 傳輸就是任何可以 `async with x as (read, write)` 的東西。只要不是伺服器物件或 URL,`Client` 就會直接交給那個協定處理。 +* 傳輸就是任何可以 `async with x as (read, write)` 的東西。只要不是伺服器物件、URL 或 `StdioServerParameters`,`Client` 就會直接交給那個協定處理。 * 建立 `Client` 是選定傳輸方式。`async with` 才是開啟它。 傳輸開啟之後,兩邊得對協定版本達成一致。平常根本不需要去想這件事;真的需要的時候,請看 **[協定版本](../protocol-versions.md)**。 diff --git a/i18n/zh-hant/pages/deprecated.md b/i18n/zh-hant/pages/deprecated.md index 473448d80c..ca8137da84 100644 --- a/i18n/zh-hant/pages/deprecated.md +++ b/i18n/zh-hant/pages/deprecated.md @@ -1,11 +1,11 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # 已棄用的功能 {#deprecated-features} -2026-07-28 規格讓五樣東西退場。SDK 仍然實作了其中每一項,而每一項現在都帶有**棄用警告**。 +2026-07-28 規格讓五樣東西退場。SDK 仍然實作了其中每一項,而每一項現在都帶有**棄用警告**。另外有一個 SDK 輔助函式是因為自身的原因棄用,列在[最後](#deprecated-sdk-helpers)。 下表列出每一項已棄用的功能、它為什麼要退場,以及應該改用的替代做法。 @@ -49,6 +49,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE 兩個訊號,依這個順序出現。`MCPDeprecationWarning` 在呼叫方法的那一刻就會發出,任何連線都一樣。錯誤則是 SDK 接著嘗試傳送時回傳來的東西。這兩者只有在用戶端註冊了對應回呼的 `mode="legacy"` 連線上,才能從頭到尾正常運作。 +## 舊版工作階段上的 `ping` {#ping-on-a-legacy-session} + +**ping** 是一個空的請求,任何一方都可以送出,用來確認對方還有回應。2026-07-28 規格移除了它([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)):現代用戶端送出的每個請求本身就已經證明伺服器還在,而現代伺服器也沒有通道可以送出 ping。兩個 SDK 方法在交握世代的工作階段上仍然有效。從用戶端: + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +從伺服器端,在任何處理函式內: + +```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()` 每次呼叫都會發出 `MCPDeprecationWarning`。在預設(`2026-07-28`)的連線上,伺服器則改為回應 `MCPError: Method not found`。 +* `ctx.session.send_ping()` 不帶警告。在現代連線上,它會和其他任何伺服器發起的請求一樣,引發沒有反向通道(back-channel)的錯誤。 +* 兩邊都不需要註冊任何東西來回應 ping。 + +## 根目錄變更通知 {#roots-change-notifications} + +宣告了根目錄能力的 2025 世代用戶端,可以送出 `notifications/roots/list_changed` 告訴伺服器它的工作區資料夾變了;伺服器的回應是再次請求 `roots/list`。2026-07-28 規格把這個通知連同其餘推送式的根目錄流程一起移除。在用戶端,傳入 `list_roots_callback=`(**[用戶端回呼](client/callbacks.md)**)就是宣告 `"roots": {"listChanged": true}` 的那一步,而兌現這個承諾只需要一次呼叫: + +```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() +``` + +在伺服器端,接收端的處理函式由低階 `Server` 接手: + +```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` 是 `list_roots_callback` 回傳的那個清單。`client.send_roots_list_changed()` 會發出警告,而且需要 `mode="legacy"` 的用戶端:在現代連線上,這個通知會被默默丟棄。之後要讓工作階段保持開啟,因為伺服器後續的 `roots/list` 會從這條工作階段送來。 +* `MCPServer` 沒有對應這個通知的掛鉤。在低階 `Server` 上,`on_roots_list_changed=` 用來註冊處理函式(同樣已棄用,建構時就會發出警告)。這個通知不帶任何酬載,所以處理函式要呼叫 `ctx.session.list_roots()` 取得新清單。 + ## 讓警告靜音 {#silencing-the-warning} 新程式碼裡,不要這麼做。 @@ -66,21 +115,30 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning) 整個 API 就這樣。沒有逐方法的開關,你也不會想要:只用一個類別的意義在於,一行就能關掉它,一行就能把它叫回來。 !!! check - 把過濾器反過來用,就免費得到一個回歸測試。在 pytest 設定的 `filterwarnings` 裡加上 `"error::mcp.MCPDeprecationWarning"`,已棄用的呼叫就會**引發例外**而不是發出警告。一個名為 `old_log`、還在呼叫 `ctx.info()` 的工具會不再通過,開始回報: + 把過濾器反過來用,就免費得到一個回歸測試。在 pytest 設定的 `filterwarnings` 裡加上 `"error::mcp.MCPDeprecationWarning"`,已棄用的呼叫就會**引發例外**而不是發出警告。一個名為 `old_log`、還在呼叫 `ctx.info()` 的工具會不再通過:呼叫回來時是 `is_error=True`,帶著 `Error executing tool old_log`,而擷取到的伺服器記錄會點名元凶: ```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). ``` 一行 pytest 設定,已棄用的呼叫就再也沒辦法在不讓測試失敗的情況下溜回程式碼庫。 +## 已棄用的 SDK 輔助函式 {#deprecated-sdk-helpers} + +這些不是規格變更,只是有了更好替代做法的 SDK 內部實作。它們用同樣的 `MCPDeprecationWarning` 發出警告,並會在 3.0 移除。 + +| 已棄用項目 | 替代做法 | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | 先 `FuncMetadata.validate_arguments()`,再 `FuncMetadata.call_fn()`。只有直接操作 `FuncMetadata` 的程式碼(例如自訂的 `Tool` 子類別)才會呼叫過它。 | + ## 重點回顧 {#recap} * 2026-07-28 規格棄用了**根目錄**、伺服器發起的**取樣**和協定**記錄**(全部來自 [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)),把**進度**限制為只能從伺服器到用戶端,並移除了 **`ping`**。 * 替代做法那一欄指引你接下來往哪走:取樣和根目錄看 **[多輪往返請求](handlers/multi-round-trip.md)**,記錄看 **[記錄](handlers/logging.md)**,進度看 **[進度](handlers/progress.md)**。`ping` 什麼都不需要。 * 棄用只是勸告性質:線路沒有變更,一切在 2026 之前的工作階段上都能繼續運作,而且你會看到明顯的 `MCPDeprecationWarning`(它是 `UserWarning`,所以預設就會顯示)。 -* 取樣和根目錄還額外需要一條反向通道(back-channel),而 2026-07-28 的工作階段沒有。在現代連線上,它們會先警告,再引發例外。 +* 取樣和根目錄還額外需要一條反向通道,而 2026-07-28 的工作階段沒有。在現代連線上,它們會先警告,再引發例外。 * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` 會讓整個類別靜音;pytest 裡的 `"error::mcp.MCPDeprecationWarning"` 則把它變成測試失敗。 +* 有一個 SDK 輔助函式 `FuncMetadata.call_fn_with_arg_validation()` 另外單獨棄用,預計在 3.0 移除。 * 新程式碼不應該建立在這些東西之上。 這份說明文件的其他每一頁教的都是目前的 API。 diff --git a/i18n/zh-hant/pages/get-started/real-host.md b/i18n/zh-hant/pages/get-started/real-host.md index 0b356c8876..2c12515430 100644 --- a/i18n/zh-hant/pages/get-started/real-host.md +++ b/i18n/zh-hant/pages/get-started/real-host.md @@ -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 --- # 連接到真正的主機 {#connect-to-a-real-host} @@ -11,7 +11,7 @@ translation: ## 一個伺服器,所有主機 {#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" ``` @@ -41,7 +41,7 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py !!! note "這一頁講的是本機情境" 這裡的一切都是在主機所在的那台機器上執行伺服器:主機透過 stdio 啟動你的檔案。對個人用或單機工具來說,這完全正確。要把伺服器交給**沒有**你這個檔案的人,給出去的是 **URL** 而不是指令:同一個 `mcp` 物件,改用 Streamable HTTP 提供服務。**[執行伺服器](../run/index.md)** 用一張表講清楚這個抉擇,**[部署與擴展](../run/deploy.md)** 則是從那裡走到真正主機名稱的路。 - 而主機不過就是內含 MCP 用戶端的應用程式,所以你自己的 Python 也能扮演主機的角色:**[用戶端傳輸方式](../client/transports.md)** 用 `stdio_client(...)` 把同一個檔案當成子處理程序啟動,**[測試](testing.md)** 則在記憶體內連接它,完全不需要處理程序。 + 而主機不過就是內含 MCP 用戶端的應用程式,所以你自己的 Python 也能扮演主機的角色:**[用戶端傳輸方式](../client/transports.md)** 用 `Client(StdioServerParameters(...))` 把同一個檔案當成子處理程序啟動,**[測試](testing.md)** 則在記憶體內連接它,完全不需要處理程序。 ## Claude Desktop {#claude-desktop} diff --git a/i18n/zh-hant/pages/get-started/testing.md b/i18n/zh-hant/pages/get-started/testing.md index a67a364dc9..c37bac9d9c 100644 --- a/i18n/zh-hant/pages/get-started/testing.md +++ b/i18n/zh-hant/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # 測試 {#testing} @@ -80,7 +80,7 @@ async def test_call_add_tool(client: Client): 可能出錯的地方有兩種,而這個旗標只影響其中一種。 -**你的工具**內部引發的例外不算協定失敗。它會變成一個帶有 `is_error=True` 的正常結果,模型會讀到那則訊息。`raise_exceptions` 不會改變這一點:不管有沒有設定,`call_tool` 都回傳同樣的 `is_error=True` 結果。這部分有一整頁的說明:**[處理錯誤](../servers/handling-errors.md)**。 +**你的工具**內部引發的例外不算協定失敗。它會變成一個帶有 `is_error=True` 的正常結果(如果是 `ToolError`,模型會讀到你寫的訊息)。`raise_exceptions` 不會改變這一點:不管有沒有設定,`call_tool` 都回傳同樣的 `is_error=True` 結果。這部分有一整頁的說明:**[處理錯誤](../servers/handling-errors.md)**。 發生在工具本體**之外**的失敗就不一樣了。在 `Client(mcp)` 給你的連線上,伺服器會先把它淨化成通用的 `"Internal server error"`,用戶端才看得到。意外當掉的細節本來就不該洩漏給遠端呼叫端。但在測試裡,這正是你**不**想要的,也正是 `raise_exceptions=True` 改變的地方:測試會看到真正的訊息,而不是淨化過的版本。 diff --git a/i18n/zh-hant/pages/handlers/elicitation.md b/i18n/zh-hant/pages/handlers/elicitation.md index fa42fe5f14..0af4ae64f5 100644 --- a/i18n/zh-hant/pages/handlers/elicitation.md +++ b/i18n/zh-hant/pages/handlers/elicitation.md @@ -1,6 +1,6 @@ --- translation: - sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] + sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] tool: 1 --- # 徵詢 {#elicitation} @@ -83,7 +83,7 @@ translation: 那份 schema 就是表單。`Field(description=...)` 是標籤;預設值會預先填入輸入框,並讓該欄位變成選填。這和**[工具](../servers/tools.md)**頁面描述工具引數時用的是同一套 Pydantic 轉 JSON Schema 機制。 !!! warning - 徵詢用的 schema 表達能力不如工具的輸入 schema。只能用扁平的基本型別欄位:`str`、`int`、`float`、`bool`,或是字串組成的 `Literal`(會變成 `enum`)。如果在模型裡再放一個模型,`ctx.elicit` 會在送出任何東西給用戶端之前就引發例外: + 徵詢用的 schema 表達能力不如工具的輸入 schema。只能用扁平的基本型別欄位:`str`、`int`、`float`、`bool`,或是字串組成的 `Literal`(會變成 `enum`)。如果在模型裡再放一個模型,`ctx.elicit` 會在送出任何東西給用戶端之前就引發例外。工具呼叫會以 `Error executing tool ` 失敗,原因則在伺服器記錄裡: ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -104,7 +104,7 @@ translation: 拒絕不是錯誤。拒絕代表什麼由工具決定(這裡是不訂位),然後照常回答模型。 !!! tip - 回答在你的程式碼看到之前,就會先依照模型驗證。用戶端若在 `bool` 欄位送來 `"maybe"`,也不會弄壞你的訂位:呼叫會以 schema 不符的錯誤失敗,你的 `if` 根本不會執行。 + 回答在你的程式碼看到之前,就會先依照模型驗證。用戶端若在 `bool` 欄位送來 `"maybe"`,也不會弄壞你的訂位:`ctx.elicit` 會引發 `ValueError`,呼叫失敗,你的 `if` 根本不會執行。 ## 把使用者送往一個 URL {#send-the-user-to-a-url} diff --git a/i18n/zh-hant/pages/handlers/logging.md b/i18n/zh-hant/pages/handlers/logging.md index 16ff218743..38513afefa 100644 --- a/i18n/zh-hant/pages/handlers/logging.md +++ b/i18n/zh-hant/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # 記錄 {#logging} @@ -49,6 +49,8 @@ MCP 有一個協定層級的 **logging 能力**:伺服器可以透過 `Context `logging.basicConfig()` 永遠不會取代已經存在的 handler。如果在建立伺服器之前就自己設定好記錄,以你的設定為準。 +也不需要只為了記下失敗,就在每個處理函式裡包一層 `try`/`except`。工具或資源函式引發例外時,SDK 會替你記錄下來。記錄了什麼、用哪個層級,請見 **[處理錯誤](../servers/handling-errors.md#any-other-exception)**。 + ## 試試看 {#try-it} 用 MCP Inspector 執行伺服器: diff --git a/i18n/zh-hant/pages/run/index.md b/i18n/zh-hant/pages/run/index.md index 3b1574dccf..a0a3960b38 100644 --- a/i18n/zh-hant/pages/run/index.md +++ b/i18n/zh-hant/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # 執行伺服器 {#running-your-server} @@ -70,7 +70,7 @@ Inspector 做的事和真正的主機一模一樣:把 `server.py` 當成子處 * `streamable_http_path`:MCP 端點的位置。預設為 `/mcp`。 * `json_response=True`:每個 POST 都用單一 JSON 本體回應,而不是 SSE 串流。那個本體只裝得下回應本身,別的都沒有,所以在請求中途回頭呼叫用戶端的工具(`ctx.elicit()`、取樣(sampling))在這一段會引發 `NoBackChannelError`,而綁在進行中呼叫上的通知(`ctx.report_progress()` 的進度、每次呼叫的記錄訊息)會被丟棄;獨立的 `GET` 串流仍會承載不相關的那些。 * `stateless_http=True`:每個請求一個全新的傳輸,不追蹤工作階段(session)。 -* `max_request_body_size`:可接受的最大 POST 本體,以位元組計。預設為 4 MiB;更大的請求在解析或建立工作階段之前就會收到 HTTP 413。只有在合法的 MCP 訊息超過這個大小時才調高它。 +* `max_request_body_size`:可接受的最大請求本體,以位元組計。預設為 4 MiB;更大的請求在解析或建立工作階段之前就會收到 HTTP 413。只有在合法的 MCP 訊息超過這個大小時才調高它。 * `event_store`、`retry_interval`、`transport_security`:可續傳性與 DNS 重新綁定防護。這些可以先放著,等到部署到 localhost 以外的地方再說;`transport_security` 在 **[部署與擴展](deploy.md)** 有說明。 !!! warning diff --git a/i18n/zh-hant/pages/servers/handling-errors.md b/i18n/zh-hant/pages/servers/handling-errors.md index 031cfa7557..f4561450b1 100644 --- a/i18n/zh-hant/pages/servers/handling-errors.md +++ b/i18n/zh-hant/pages/servers/handling-errors.md @@ -1,13 +1,13 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # 處理錯誤 {#handling-errors} -工具失敗的方式有兩種,而 SDK 對待它們的方式截然不同。 +工具失敗的方式有三種,而 SDK 對待每一種的方式都不同。 -引發一般的例外,看到的是**模型**。引發 `MCPError`,看到的是**協定**。 +引發 `ToolError`,**模型**會看到你的訊息。引發 `MCPError`,看到的是**協定**。引發其他任何東西就是崩潰:模型只知道呼叫失敗了,而 traceback 進了你的記錄。 這一頁談的是怎麼選。 @@ -15,11 +15,11 @@ translation: 拿一個查東西的工具來說,讓查詢落空: -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -這兩行跟 MCP 一點關係也沒有。`get_author` 引發的是普通的 `ValueError`,任何 Python 函式都會這麼做。 +`ToolError` 來自 `mcp.server.mcpserver.exceptions`,是工具告訴模型出了問題的方式。 用一個不在目錄裡的書名呼叫它,看看結果: @@ -30,19 +30,21 @@ result.structured_content # None ``` * 請求**成功**了。有結果;呼叫端沒有引發任何東西。 -* `is_error` 是 `True`,而例外的訊息(前面加上工具名稱)就在 `content` 裡,正是模型讀取的地方。 +* `is_error` 是 `True`,而你的訊息(前面加上工具名稱)就在 `content` 裡,正是模型讀取的地方。 * `structured_content` 是 `None`。失敗的呼叫沒有回傳值可以結構化。 -這是**工具錯誤**,也是工具引發**任何**例外時的預設行為。而且幾乎總是你想要的。 +這是**工具錯誤**,而且幾乎總是你想要的。 呼叫工具的是模型,引數也是它選的。所以工具錯誤就是對話中的一個回合:模型讀到「No book titled 'Nothing' in the catalog.」,發現自己猜錯了書名,就換個更好的再呼叫一次。只寫了一個 `raise`,就得到一個會自我修正的 agent。 +在伺服器上,一個 `ToolError` 就是記錄裡的一行 `INFO`,沒有 traceback。這是你預料中的事,所以沒什麼好追查的。 + !!! tip 永遠不要從工具 `return` 錯誤訊息。回傳的字串 `is_error=False`,所以在模型(以及每個用戶端 UI)看來,工具是成功的,那個字串就是答案。要用 `raise`。那個旗標才是訊號。 ## 模型無法修正的錯誤 {#an-error-the-model-cannot-fix} -現在把 `ValueError` 換成 `MCPError`。 +現在把 `ToolError` 換成 `MCPError`。 ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -74,16 +76,35 @@ result.structured_content # None 兩條路徑回答的是兩個不同的問題。 -* **引發任何例外**,用於**執行**上的失敗:工具想做的事沒做成。呼叫是模型選的,所以模型應該看到後果,並有機會補救。拼錯的書名、逾時的上游 API、不存在的資料列:都是工具錯誤。 +* **引發 `ToolError`**,用於**執行**上的失敗:工具想做的事沒做成。呼叫是模型選的,所以模型應該看到後果,並有機會補救。拼錯的書名、逾時的上游 API、不存在的資料列:都是工具錯誤。 * **引發 `MCPError`**,用於**請求本身**就該被拒絕的情況:用戶端缺少工具所依賴的能力、伺服器處於無法服務任何人的狀態、呼叫端跳過了必要的步驟。模型再怎麼重試也修不好這些,所以把訊息交給它毫無益處。 -一個問題就能決定:**更聰明的模型能避開這個錯誤嗎?**能 -> 一般的例外。不能 -> `MCPError`。 +一個問題就能決定:**更聰明的模型能避開這個錯誤嗎?**能 -> `ToolError`。不能 -> `MCPError`。 照這個標準,第二版的 `get_author` 選錯了:換個更好的書名就能解決,所以模型理應看到訊息。放在那裡是為了示範機制,不是建議這麼做。 !!! info `MCPError` 位於 `from mcp import MCPError`,接受 `code`、`message` 和選用的 `data` 承載。放進去什麼,用戶端就收到什麼:SDK 會把引發的 `MCPError` 原封不動地轉送,不會加以清理。 +## 其他任何例外 {#any-other-exception} + +現在把檢查拿掉,讓字典查詢自己失敗: + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]` 引發 `KeyError`。這不在你的計畫之內,所以 SDK 把它當成崩潰: + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +呼叫仍然回傳 `is_error=True`,所以模型知道它失敗了,可以繼續往下走。它拿不到的是例外的文字:你程式碼裡的 `KeyError`,或是隔了三層函式庫的驅動程式丟出的一堆 SQL,都可能描述了伺服器的內部細節,所以永遠不會離開伺服器。 + +拿到它的是你。伺服器以 `ERROR` 層級記錄這次崩潰,附上完整的 traceback,訊息是 `Tool 'get_author' raised an unexpected exception`。因此,設在 `WARNING` 的正式環境記錄在每個 `ToolError` 經過時都保持安靜,一旦真的有東西壞了才會出聲。 + ## 不存在的資源 {#a-resource-that-doesnt-exist} 資源也畫了同一條線,並為常見情況提供了一個具名的例外。 @@ -104,7 +125,7 @@ result.structured_content # None } ``` -注意這裡沒有 `is_error=True` 那種半成品結果。資源讀取要嘛回傳內容,要嘛失敗:資源只有協定這條路。範本以及資源的其他一切都在 **[資源](resources.md)**。 +注意這裡沒有 `is_error=True` 那種半成品結果。資源讀取要嘛回傳內容,要嘛失敗:資源只有協定這條路。`ResourceError` 是同樣的東西,用在不是「找不到」的失敗上(`-32603`,附上你的訊息),兩者在記錄裡都是一行 `INFO`。除了 `MCPError` 以外的任何其他例外都是崩潰:用戶端收到只寫出 URI 的 `-32603`,traceback 則以 `ERROR` 層級進你的記錄。範本以及資源的其他一切都在 **[資源](resources.md)**。 ## 永遠不用引發的錯誤 {#errors-you-never-raise} @@ -115,16 +136,17 @@ result.structured_content # None 這表示有一整類 `raise` 陳述式不用寫:不要重新驗證自己的型別提示。 !!! info - 這一頁的一切都是**用戶端**看到的東西,而寫測試用的記憶體內 `Client` 看到的完全一樣。就算是 `raise_exceptions=True` 也不會把工具錯誤變回 traceback:等到那個旗標能起作用時,你的例外早已是 `is_error=True` 的結果。對結果做斷言。**[測試](../get-started/testing.md)** 說明了這個模式。 + 這一頁**用戶端**看到的一切,寫測試用的記憶體內 `Client` 也都看得到。就算是 `raise_exceptions=True` 也不會把失敗工具的例外交回給呼叫端:等到那個旗標能起作用時,你的例外早已是 `is_error=True` 的結果。對結果做斷言。如果需要崩潰的 traceback,它在伺服器的記錄裡,pytest 的 `caplog` 能捕捉到。**[測試](../get-started/testing.md)** 說明了這個模式。 ## 重點回顧 {#recap} -* 在工具裡引發**任何例外** -> 呼叫回傳 `is_error=True`,訊息在 `content` 裡。模型讀到後可以重試。這是預設行為。 +* 在工具裡引發 **`ToolError`** -> 呼叫回傳 `is_error=True`,你的訊息在 `content` 裡。模型讀到後可以重試。 * 引發 **`MCPError`** -> 呼叫本身以 JSON-RPC 錯誤失敗。模型什麼都看不到;由主機處理。`code`、`message` 和 `data` 完整保留。 -* 決定性的問題:「更聰明的模型能避開這個錯誤嗎?」能 -> 例外。不能 -> `MCPError`。 +* 決定性的問題:「更聰明的模型能避開這個錯誤嗎?」能 -> `ToolError`。不能 -> `MCPError`。 +* 任何**其他例外**都是崩潰 -> `is_error=True`,模型只看到 `Error executing tool `,而你得到一筆附上 traceback 的 `ERROR` 記錄。 * 資源處理函式引發的 `ResourceNotFoundError` -> 協定的 `-32602`,URI 在 `data` 裡。 * 錯誤的引數會在函式執行前依 schema 被拒絕;這些不用 `raise`。 -* `from mcp import MCPError`;錯誤碼常數來自 `mcp.types`。 +* 匯入:`from mcp import MCPError`、`from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`,以及來自 `mcp.types` 的錯誤碼常數。 錯誤處理完畢。這就是伺服器**公開**的全部內容。每個處理函式在執行時能讀到什麼、又能反過來對用戶端做什麼,是下一節的主題:**[在處理函式內部](../handlers/index.md)**。 diff --git a/i18n/zh-hant/pages/servers/media.md b/i18n/zh-hant/pages/servers/media.md index 0117075f8d..7e45fd2cc8 100644 --- a/i18n/zh-hant/pages/servers/media.md +++ b/i18n/zh-hant/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # 媒體 {#media} @@ -81,6 +81,24 @@ result.structured_content # None !!! check 用 `data=` 時沒有檔名,也就沒有東西可以猜。忘了 `format=`,SDK 就會退回預設值:圖片是 `image/png`,音訊是 `audio/wav`。這樣用 MP3 位元組建立 `Audio`,用戶端會被告知 `mime_type="audio/wav"`,然後老老實實地解碼失敗。傳 `data=` 的時候,就一起傳 `format=`。 +## 內嵌資源 {#embedding-a-resource} + +工具也可以回傳一份文件:一些文字或位元組,連同它所在的 URI 和 MIME 型別。這就是 **`EmbeddedResource`**,另一種內容區塊。它跟普通的 `str` 不同,會告訴用戶端這段內容是什麼,讓用戶端可以把它顯示成附件,或認出這是它已經知道的資源。 + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines` 是一個普通的資源(**[資源](resources.md)** 有完整介紹)。模型要求時,工具會交出同一份文件;直接呼叫 `guidelines()`,真實來源就只有一個。 +* `EmbeddedResource` 和 `TextResourceContents` 來自 `mcp.types`。這裡沒有像圖片那樣的輔助工具:建立的區塊會原封不動放進結果,也沒有 `structured_content`。 +* 使用資源註冊時的 URI,用戶端才能知道附件和 `brand://guidelines` 是同一份文件。任何 URI 都合法,不論有沒有註冊。 + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +二進位內容就用 `BlobResourceContents(uri=..., mime_type=..., blob=...)` 取代 `TextResourceContents`,把位元組經 base64 編碼後放進 `blob`。如果只想送出一個指標,讓用戶端之後再用 `resources/read` 讀取,就改回傳 `ResourceLink(name=..., uri=...)`;它也是一種內容區塊。 + ## 圖示 {#icons} `Icon` 是中繼資料,不是內容。它不帶圖片本身,而是用一個 URI 指向圖片;用戶端可以去抓取並顯示在伺服器名稱、工具、資源或提示詞旁邊。 @@ -110,6 +128,7 @@ client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_ * 從工具回傳 `Image` 或 `Audio`,用戶端就會收到一個 `ImageContent`/`AudioContent` 區塊:位元組經 base64 編碼,附上 MIME 型別。 * 可以用 `path=` 建立並讓副檔名決定 MIME 型別,或用記憶體內的 `data=` 加上明確的 `format=`。 +* 回傳 `EmbeddedResource` 可以把一份文件(文字或 base64 blob,附上 URI 和 MIME 型別)放進結果;只想送出指標的話,回傳 `ResourceLink`。 * 媒體結果沒有 `structured_content`,也沒有輸出 schema。 * `Icon` 是個指標:一個 `src` URI,加上選用的 `mime_type`、`sizes` 和 `theme`。 * `icons=[...]` 在伺服器、工具、資源和提示詞上都能用,用戶端會在對應的物件上找到它們。 diff --git a/i18n/zh-hant/pages/servers/prompts.md b/i18n/zh-hant/pages/servers/prompts.md index d59fe14ab0..bc83eba122 100644 --- a/i18n/zh-hant/pages/servers/prompts.md +++ b/i18n/zh-hant/pages/servers/prompts.md @@ -1,6 +1,6 @@ --- translation: - sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094] + sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd] tool: 1 --- # 提示詞 {#prompts} @@ -137,7 +137,52 @@ uv run mcp dev server.py ``` !!! info - 如果讀過 **[工具](tools.md)**,這一頁的內容你都已經會了。同樣的裝飾器、同樣以 docstring 當描述、同樣的 `Annotated`/`Field`。唯一不同的是由誰觸發(使用者),以及結果去哪裡(進入對話)。 + 如果讀過 **[工具](tools.md)**,到這裡為止的內容你都已經會了。同樣的裝飾器、同樣以 docstring 當描述、同樣的 `Annotated`/`Field`。唯一不同的是由誰觸發(使用者),以及結果去哪裡(進入對話)。 + +## 不只是文字 {#more-than-text} + +`UserMessage` 和 `AssistantMessage` 凡是接受 `str` 的地方,也都接受內容區塊,或 `Image`/`Audio` 輔助類別。提示詞裡常見兩種情況:附上一份文件,以及附上一張圖片。 + +### 嵌入檔案 {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* 風格指南是位於 `style://python` 的資源(**[資源](resources.md)** 會介紹),從 `server.py` 旁邊的 `style-guide.md` 讀取。放任何一個 Markdown 檔案在那裡都可以。 +* `EmbeddedResource(resource=TextResourceContents(...))`(兩者都來自 `mcp.types`)把檔案連同 URI 和 MIME 類型當成第一則訊息帶上;引用它的請求以純文字接在後面。 +* 用嵌入而不是把指南貼進 f-string,用戶端就能把它顯示成附件,之後還能重新打開 `style://python`,而模型收到的是原封不動的檔案。二進位檔案則改用 `BlobResourceContents` 搭配 base64 的 `blob`。 + +算繪之後,第一則訊息的 `content` 是一個 `resource` 區塊: + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### 附上圖片 {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image` 是 **[圖片、音訊與圖示](media.md)** 裡的輔助類別。提示詞算繪時,`UserMessage` 會把它轉成 `ImageContent` 區塊(檔案以 base64 編碼,MIME 類型從 `.png` 推測);`Audio` 也以同樣方式變成 `AudioContent`。 +* 在 `server.py` 旁邊放任何一張名為 `architecture.png` 的 PNG。提示詞引數是字串,所以圖片一定來自伺服器;`component` 只提供文字。 + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## 執行時變更清單 {#changing-the-list-at-runtime} + +用戶端連著的時候也可以新增提示詞,例如讓使用者把一段指示存成自己的選單項目。先註冊提示詞,再發通知: + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` 註冊函式的效果和 `@mcp.prompt()` 完全一樣,`mcp.remove_prompt(name)` 則是反過來。`add_prompt` 遇到同名的既有項目會保留而不覆寫,所以這個工具會先移除舊的,讓儲存變成取代。`prompts/list` 會立即反映變更。 +* `await ctx.notify_prompts_changed()` 把 `notifications/prompts/list_changed` 送給每個在 `subscriptions/listen` 串流上監聽的 `2026-07-28` 用戶端(**[訂閱](../handlers/subscriptions.md)**)。呼叫端是 2026 之前的用戶端時,`await ctx.session.send_prompt_list_changed()` 會把通知送給它(**[服務舊版用戶端](../run/legacy-clients.md)**)。兩個都呼叫;沒有人可通知時,各自什麼都不做。 +* 收到通知的用戶端會再呼叫一次 `prompts/list`。在 Python 的 `Client` 裡寫成 `async with client.listen(prompts_list_changed=True) as sub:`,會產出 `PromptsListChanged` 事件。 ## 重點回顧 {#recap} @@ -147,5 +192,7 @@ uv run mcp dev server.py * 回傳 `str` 會變成一則使用者訊息。回傳 `UserMessage`/`AssistantMessage` 的清單,可以替多輪對話起頭。 * `title=` 和 `Field(description=...)` 是用戶端放在 UI 上的內容。 * 缺少必填引數會讓整個請求失敗,沒有個別提示詞的錯誤結果。 +* 把 `EmbeddedResource` 或 `Image` 包進 `UserMessage`,就能附上文件或圖片。 +* 執行時用 `mcp.add_prompt(...)`/`mcp.remove_prompt(...)` 新增或移除提示詞,接著 `await ctx.notify_prompts_changed()` 和 `await ctx.session.send_prompt_list_changed()`。 伺服器端替提示詞(或資源範本)引數做自動完成,請見 **[自動完成](completions.md)**。 diff --git a/i18n/zh-hant/pages/servers/structured-output.md b/i18n/zh-hant/pages/servers/structured-output.md index f46e303001..d9359726a7 100644 --- a/i18n/zh-hant/pages/servers/structured-output.md +++ b/i18n/zh-hant/pages/servers/structured-output.md @@ -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 --- # 結構化輸出 {#structured-output} @@ -103,7 +103,7 @@ result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions --8<-- "docs_src/structured_output/tutorial003.py" ``` -`TypedDict` 在執行時就是普通的 `dict`,所以建立並回傳的就是它。schema、驗證和 `structured_content` 都跟 `BaseModel` 版本一模一樣(少了描述,因為 `TypedDict` 沒有地方放)。 +`TypedDict` 在執行時就是普通的 `dict`,所以建立並回傳的就是它。schema、驗證和 `structured_content` 都遵循跟 `BaseModel` 版本一樣的規則:加上類別 docstring 或 `Annotated[..., Field(description=...)]`,它們就成為描述;dict 裡沒放進去的 `NotRequired` 鍵,也不會出現在 `structured_content` 裡。 ## dataclass {#a-dataclass} @@ -185,16 +185,16 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} 註記承諾的是 `WeatherData`,但上游回應不再送 `humidity` 了。 !!! check - 呼叫 `get_weather`,它不會默默把一個半空的物件交給用戶端。呼叫會失敗,錯誤的頭幾行就點名了那個欄位: + 呼叫 `get_weather`,它不會默默把一個半空的物件交給用戶端。呼叫會失敗:用戶端收到 `is_error=True` 和 `Error executing tool get_weather`,所以模型知道呼叫失敗了,而不是信心滿滿地讀一份根本不存在的天氣。欄位名稱是留給你看的,在伺服器記錄裡以 `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] ``` - 這段文字會以 `is_error=True` 的工具結果回傳,所以模型知道呼叫失敗了,而不是信心滿滿地讀一份根本不存在的天氣。 - 順帶一提,從 `-> WeatherData` 的工具回傳普通的 `dict` 沒問題,`json.loads` 產生的正是這個。驗證看的是值,不是 Python 型別。 ## 選擇退出 {#opting-out} @@ -209,6 +209,10 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} 反過來,`structured_output=True` 會把自動偵測變成硬性要求:回傳型別產生不出 schema 的工具,會在匯入時引發例外,而不是退回文字。 +## 內容區塊與媒體 {#content-blocks-and-media} + +內容區塊和媒體(`TextContent`、`EmbeddedResource`、`Image`、`Audio` 這一類,不論是單獨出現、作為 `list`、`tuple` 或 `Sequence` 的元素,還是作為 union 的分支)會自動幫你退出:它們是給模型讀的,所以自動偵測不會從中推導出 schema(`Image` 和 `Audio` 在 **[圖片、音訊與圖示](media.md)** 說明)。對內容區塊類別,`structured_output=True` 仍然會強制產生一個 schema。 + ## 沒有型別提示的類別 {#a-class-without-type-hints} 有一種情況會在沒有要求的前提下變成非結構化:回傳一個**本體上沒有任何註記**的類別。 @@ -237,6 +241,6 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} * 純量、串列、tuple 和 union 會包進 `{"result": ...}`。模型、`TypedDict`、dataclass、帶註記的類別和 `dict[str, ...]` 本來就是物件,維持原樣。 * 每個結果都帶有 `content`(文字,給模型)**和** `structured_content`(資料,給應用程式)。 * 回傳的東西會拿 schema 驗證。不符合就是工具錯誤,不會是一份壞掉的結果。 -* `structured_output=False` 讓工具退出。沒有型別提示的類別會默默退出,要留意。 +* `structured_output=False` 讓工具退出。內容區塊、`Image` 和 `Audio` 預設就退出;沒有型別提示的類別會默默退出,要留意。 工具能回覆的一切,現在都掌握在你手上了。接下來是第二個基本元件:**[資源](resources.md)**。 diff --git a/i18n/zh-hant/pages/servers/tools.md b/i18n/zh-hant/pages/servers/tools.md index 7aea2c3fde..1e3b48e4e7 100644 --- a/i18n/zh-hant/pages/servers/tools.md +++ b/i18n/zh-hant/pages/servers/tools.md @@ -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} @@ -39,6 +39,8 @@ SDK 從這些型別提示產生一份 JSON Schema,並在 `tools/list` 時送 兩個引數都在 `required` 裡,因為都沒有預設值。等一下就會修正這點。(`title` 鍵是 Pydantic 產生的附帶產物;屬性、它們的型別和 `required` 才是契約。) +也沒有 `$schema` 鍵:MCP 把沒有這個鍵的 schema 當作 **JSON Schema 2020-12**,而這正是 Pydantic 產生的格式,所以在你到 **[低階 Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)** 上親手寫 schema 之前,沒有什麼需要選的。 + !!! tip 這裡的型別提示不是說明文件,而是**契約**。如果用戶端送來 `"limit": "ten"`,SDK 會在函式執行之前就拒絕它。 diff --git a/i18n/zh-hant/pages/servers/uri-templates.md b/i18n/zh-hant/pages/servers/uri-templates.md index 8a36f06c56..f5dec6f5d3 100644 --- a/i18n/zh-hant/pages/servers/uri-templates.md +++ b/i18n/zh-hant/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # URI 範本與路徑安全 {#uri-templates-and-path-safety} @@ -98,7 +98,7 @@ translation: 內建檢查擋得住常見情況,但無從得知你的沙箱邊界。存取檔案系統時,用 `safe_join` 解析路徑,並確認它仍在基底目錄之內: -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` @@ -127,7 +127,7 @@ translation: 這些檢查只是啟發式的前置過濾;存取檔案系統時,`safe_join` 仍然是真正的隔離邊界。 !!! tip - 如果處理函式無法完成請求(檔案不存在、id 不認識),就引發例外。SDK 會把它轉成錯誤回應。協定錯誤和工具錯誤的差別請見 **[處理錯誤](handling-errors.md)**。 + 如果處理函式無法完成請求(檔案不存在、id 不認識),就像上面的 `read_manual` 那樣引發 `ResourceNotFoundError`。用戶端會收到 `-32602`,附上你的訊息和 URI。非預期的例外則會變成通用的 `-32603`。請見 **[處理錯誤](handling-errors.md#a-resource-that-doesnt-exist)**。 ## 低階 Server 上的資源 {#resources-on-the-low-level-server} diff --git a/i18n/zh-hant/pages/troubleshooting.md b/i18n/zh-hant/pages/troubleshooting.md index 855672d8b8..fb91a04667 100644 --- a/i18n/zh-hant/pages/troubleshooting.md +++ b/i18n/zh-hant/pages/troubleshooting.md @@ -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 --- # 疑難排解 {#troubleshooting} @@ -79,11 +79,11 @@ async def main() -> None: `__aexit__` 就是斷線,這也是為什麼沒有 `client.close()` 可以忘記。**[測試](get-started/testing.md)** 正是建立在這個模式上。 -## `Error executing tool : ` 與 `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `、`Error executing tool ` 與 `Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} 你讀到的是**結果**,不是例外。`call_tool` 沒有引發例外,而且遇到失敗的工具它永遠不會引發。 -用伺服器不認識的城市呼叫 `forecast`,它引發的例外會跟著一個標記為**成功**的請求一起回來: +用伺服器不認識的城市呼叫 `forecast`,它引發的 `ToolError` 會跟著一個標記為**成功**的請求一起回來: ```python result.is_error # True @@ -95,6 +95,8 @@ result.structured_content # None 修正在用戶端:**檢查 `result.is_error`**。包在 `call_tool` 外面的 `try/except` 一個都攔不到,因為根本沒有東西可以攔。這是刻意的設計,也是這一頁最值得內化的一件事:是**模型**選擇了這個呼叫,所以訊息交給模型,讓它有機會再試一次。完整說明請見 **[處理錯誤](servers/handling-errors.md)**,包括**確實會**引發例外的 `MCPError` 路徑。 +不帶訊息的簡略形式 `Error executing tool ` 表示工具**當掉了**:一個它沒預料到的例外逃了出來(或是它的回傳值沒通過輸出 schema),而那個例外的文字不會送上線路。traceback 在**伺服器的記錄**裡,以 `ERROR` 層級記錄為 `Tool '' 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} 你寫了 `@mcp.tool` 而不是 `@mcp.tool()`。`tool()` 是裝飾器**工廠**:少了括號,Python 會把你的函式交給它的 `name=` 參數。 @@ -393,7 +395,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## 重點回顧 {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup` 永遠不是錯誤本身。讀**最後一行**;在 `async with Client(...)` 區塊**裡面**攔截 `MCPError` 就完全跳過包裝。 -* `call_tool` 不會因為工具失敗而引發例外。`Error executing tool ...` 和 `Unknown tool: ...` 是結果:檢查 `result.is_error`。 +* `call_tool` 不會因為工具失敗而引發例外。`Error executing tool ...` 和 `Unknown tool: ...` 是結果:檢查 `result.is_error`。工具名稱後面沒有訊息表示它當掉了,traceback 在伺服器記錄裡。 * `Client must be used within an async context manager` -> 用 `async with`。`Use @tool() instead of @tool` -> 加上括號。 * 伺服器記錄裡的 `Tool already exists:` 是兩個同名工具合併成一個的唯一跡象。 * 一個 421,三種寫法:`Server returned an error response`(python `Client`)、`421 Misdirected Request` / `Invalid Host header`(其他所有東西)、`Invalid Host header: `(伺服器記錄)。修正:`transport_security=TransportSecuritySettings(allowed_hosts=[...])`。 diff --git a/i18n/zh-hant/pages/whats-new.md b/i18n/zh-hant/pages/whats-new.md index 96690f8567..50a0766532 100644 --- a/i18n/zh-hant/pages/whats-new.md +++ b/i18n/zh-hant/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # v2 的新功能 {#whats-new-in-v2} @@ -41,9 +41,9 @@ v1 交給你的是三層巢狀結構:一個產出原始串流的傳輸 context --8<-- "docs_src/client/tutorial001.py" ``` -`Client` 接受一個伺服器物件(記憶體內、沒有傳輸,也就是測試的做法)、一個 URL(Streamable HTTP),或任何傳輸 context manager,例如 `stdio_client(...)`。進入 `async with` 就會連線並協商協定版本,不管伺服器講的是哪個世代;之後 `client.server_capabilities` 和 `client.protocol_version` 就直接在那裡,伺服器有表明身分時 `client.server_info` 也在(它現在是 `Implementation | None`,因為 2026 世代的身分是選用的)。在 v1 註冊的取樣和徵詢回呼仍然有效(回呼本體會遇到跟本頁其他地方一樣的 snake_case 屬性改名),現在也會回應 2026 風格的「結果中夾帶請求」(見下文),而且是並行執行,不再一次一個。想要低階介面的人,`ClientSession` 仍在底下,`client.session` 會把它交給你;它也有變動(跑在新的分派器引擎上,自己的部分簽章也改了),所以往下鑽之前先讀 **[遷移指南](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**。 +`Client` 接受一個伺服器物件(記憶體內、沒有傳輸,也就是測試的做法)、一個 URL(Streamable HTTP)、一個 `StdioServerParameters`(stdio 子處理程序),或任何其他傳輸 context manager,例如 `sse_client(...)`。進入 `async with` 就會連線並協商協定版本,不管伺服器講的是哪個世代;之後 `client.server_capabilities` 和 `client.protocol_version` 就直接在那裡,伺服器有表明身分時 `client.server_info` 也在(它現在是 `Implementation | None`,因為 2026 世代的身分是選用的)。在 v1 註冊的取樣和徵詢回呼仍然有效(回呼本體會遇到跟本頁其他地方一樣的 snake_case 屬性改名),現在也會回應 2026 風格的「結果中夾帶請求」(見下文),而且是並行執行,不再一次一個。想要低階介面的人,`ClientSession` 仍在底下,`client.session` 會把它交給你;它也有變動(跑在新的分派器引擎上,自己的部分簽章也改了),所以往下鑽之前先讀 **[遷移指南](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**。 -**[用戶端](client/index.md)** 介紹它,**[用戶端傳輸方式](client/transports.md)** 說明三種連線形式,**[用戶端回呼](client/callbacks.md)** 說明回呼本身,**[測試](get-started/testing.md)** 示範取代 v1 `create_connected_server_and_client_session()` 輔助函式的記憶體內模式。 +**[用戶端](client/index.md)** 介紹它,**[用戶端傳輸方式](client/transports.md)** 說明四種連線形式,**[用戶端回呼](client/callbacks.md)** 說明回呼本身,**[測試](get-started/testing.md)** 示範取代 v1 `create_connected_server_and_client_session()` 輔助函式的記憶體內模式。 ### 低階 `Server` 是重寫,不是改名 {#the-low-level-server-was-rebuilt-not-renamed} @@ -129,7 +129,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB 改名會自己跳出來提醒你。下面這些不會: * **同步函式在工作執行緒上執行。** `def` 的工具(或資源、提示詞、解析器)不再阻塞事件迴圈;代價是它的本體不再**在**事件迴圈執行緒上執行,這對綁定執行緒的程式碼有影響。`async def` 處理函式不受影響。**[遷移指南](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**。 -* **在工具裡引發的 `MCPError`(v1 的 `McpError`)現在是協定錯誤。** 模型永遠看不到它。其他所有例外仍然會變成模型讀得到、能做出反應的 `is_error=True` 結果。兩者的分界請見 **[處理錯誤](servers/handling-errors.md)**。 +* **在工具裡引發的 `MCPError`(v1 的 `McpError`)現在是協定錯誤。** 模型永遠看不到它。其他所有例外仍然會變成 `is_error=True` 的結果,但只有 `ToolError` 的訊息會送到模型面前:其他例外現在一律顯示為 `Error executing tool `,traceback 則留在伺服器記錄裡。兩者的分界請見 **[處理錯誤](servers/handling-errors.md)**。 * **結果送出前會先驗證。** 手動建立、`input_schema` 為 `{}` 的 `Tool` 現在會讓 `tools/list` 失敗(規格要求 `"type": "object"`)。用 `@mcp.tool()` 建的伺服器不會遇到;它們的 schema 是 SDK 寫的。 * **用戶端會驗證收到的東西。** `list_tools()` 和 `call_tool()` 會用協商好的協定版本檢查伺服器的回答,所以 v1 寬鬆解析還能容忍的不太合規伺服器,現在會引發 `pydantic.ValidationError`。如果連到的是自己無法控制的伺服器,要有心理準備,發現問題的人會是你;細節請見 **[遷移指南](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)**。 * **URI 範本現在是真正的 RFC 6570。** `{+path}`、`{?query}` 這些都能用,比對是精確的而不是正規表示式那種寬鬆,擷取出的值若含路徑穿越,預設會被拒絕。更嚴格的範本會在裝飾時就失敗,而不是等到第一個請求。**[URI 範本](servers/uri-templates.md)**。 diff --git a/i18n/zh/pages/advanced/low-level-server.md b/i18n/zh/pages/advanced/low-level-server.md index 51096d1022..c4200b0f6b 100644 --- a/i18n/zh/pages/advanced/low-level-server.md +++ b/i18n/zh/pages/advanced/low-level-server.md @@ -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 --- # 底层 Server {#the-low-level-server} @@ -116,6 +116,17 @@ asyncio.run(main()) 服务器从不比较这两个字段。本 SDK 的 `Client` 会:返回的 `structured_content` 不满足你声明的 `output_schema` 时,`call_tool` 会抛出一个 `RuntimeError`,开头是 `Invalid structured content returned by tool search_books`,后面引用 `jsonschema` 的失败信息。承诺一个模式很便宜;信守它是你的事。返回类型和模式的完整阶梯详见 **[结构化输出](../servers/structured-output.md)**。 +## 方言是 JSON Schema 2020-12 {#the-dialect-is-json-schema-2020-12} + +`input_schema` 和 `output_schema` 是 JSON Schema,而 [MCP 规范](https://modelcontextprotocol.io/specification/latest/basic#json-schema-usage) 固定了方言:没有 `$schema` 键的模式就是 **JSON Schema 2020-12**。`MCPServer` 生成的模式依赖这个默认值(Pydantic 写的是 2020-12 并省略该键),手写的 dict 也按同样的标准对待,所以完整的 2020-12 词汇表都可以用: + +```python title="server.py" hl_lines="8 14-15" +--8<-- "docs_src/lowlevel/tutorial007.py" +``` + +* `input_schema` 的根必须是 `"type": "object"`。在它旁边,`oneOf`、`additionalProperties`、`anyOf`、`if`/`then`/`else`、`prefixItems`、带本地 `$ref` 的 `$defs`,以及其余 2020-12 关键字,都会一字不差地到达客户端。 +* 不需要 `$schema` 键。只有想选用更早的草案时才加:本 SDK 的 `Client` 会按工具的 `output_schema` 校验 `structured_content`,它根据 `$schema` 选择校验器,没有时就用 2020-12。 + ## `_meta`:给应用程序,不是给模型 {#\_meta-for-the-application-not-the-model} `content` 是答案里模型读取的部分。`structured_content` 是同一个答案的类型化数据形式。`_meta` 是第三条通道:随结果一起传递、面向**客户端应用程序**的数据,根本不属于答案的一部分。 @@ -166,7 +177,7 @@ asyncio.run(main()) --8<-- "docs_src/lowlevel/tutorial006.py" ``` -* 第一个参数是方法字符串。通知有一个对应的 `add_notification_handler`。 +* 第一个参数是方法字符串。通知有一个对应的 `add_notification_handler`。它的处理函数在 stdio 和握手时代的 HTTP 连接上触发;在 `2026-07-28` 的 Streamable HTTP 路径上,客户端发来的通知 POST 会得到 `202` 确认但不会分发,因为那个修订版没有定义通过 HTTP 的客户端到服务器通知。 * `params_type` 是传入的 `params` 在处理函数运行**之前**校验所依据的模型,所以自定义方法**确实**得到了工具没有的校验。继承 `RequestParams`,这样 `_meta` 字段的解析方式和其他方法一样。 * 处理函数返回 `BaseModel`、`dict` 或 `None`。SDK 把它序列化进 JSON-RPC 结果。 diff --git a/i18n/zh/pages/advanced/middleware.md b/i18n/zh/pages/advanced/middleware.md index 319820f342..e23d55a3e2 100644 --- a/i18n/zh/pages/advanced/middleware.md +++ b/i18n/zh/pages/advanced/middleware.md @@ -1,6 +1,6 @@ --- translation: - sections: [6048b4f308edbb8c, 068bda0f21ee9c1b, c3e565b61acd75c5, c62422b159c6ed09, 47204fab253cc45c] + sections: [6048b4f308edbb8c, 46056f318ef205e4, c3e565b61acd75c5, c62422b159c6ed09, 420968f514138f43] tool: 1 --- # 中间件 {#middleware} @@ -42,7 +42,7 @@ tools/call took 0.1 ms 这正是关键所在。中间件包裹**每一条**入站消息: * 连接建立:`server/discover`,或者旧版会话上的 `initialize` 和 `notifications/initialized`。 -* 每一个请求和每一个通知。对于通知,`ctx.request_id is None`,`call_next(ctx)` 返回 `None`,而你返回的任何东西都会被丢弃。 +* 每一个到达服务器的请求和通知。对于通知,`ctx.request_id is None`,`call_next(ctx)` 返回 `None`,而你返回的任何东西都会被丢弃。(在 `2026-07-28` 的 Streamable HTTP 路径上,客户端的通知 POST 在传输层就以 `202` 确认,从不分发,所以也到不了中间件;该修订版本没有定义任何经由 HTTP 的客户端到服务器通知。) * 甚至包括服务器没有处理函数的方法:`call_next` 会抛出 `MCPError(-32601, "Method not found")`,**穿过**你的中间件送往客户端。 ## 在中间件里能做什么 {#what-you-can-do-inside-one} @@ -75,7 +75,7 @@ SDK 自带的中间件恰好只有一个,而且已经在你服务器的列表 ## 回顾 {#recap} * 中间件是 `async (ctx, call_next) -> result`,以 `MCPServer(middleware=[...])` 传入(或追加到 `mcp.middleware`),在低层 `Server` 上则追加到 `server.middleware`。 -* 它包裹**每一条**入站消息(`server/discover`、`initialize`、请求、通知、未知方法),按从外到内的顺序执行。 +* 它包裹**每一条**到达服务器的入站消息(`server/discover`、`initialize`、请求、通知、未知方法),按从外到内的顺序执行。 * 用 `ctx.request_id is None` 区分通知和请求。 * 抛出异常而不调用 `call_next` 即可拒绝一条消息;连接不受影响。 * SDK 自己的 OpenTelemetry 追踪也是一个中间件,已经在列表上了。见 **[OpenTelemetry](../run/opentelemetry.md)**。 diff --git a/i18n/zh/pages/client/index.md b/i18n/zh/pages/client/index.md index 5abdc0c5a8..f12c633430 100644 --- a/i18n/zh/pages/client/index.md +++ b/i18n/zh/pages/client/index.md @@ -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 --- # Client {#the-client} @@ -27,9 +27,10 @@ translation: * `MCPServer`(或低层 `Server`)实例:**进程内**连接。 * URL 字符串(`Client("http://localhost:8000/mcp")`):Streamable HTTP,生产环境的路径。 -* **传输**:任何可以 `async with ... as (read, write)` 的对象,比如包装子进程的 `stdio_client(...)`。 +* `StdioServerParameters`:要作为**子进程**启动的命令,通过它的 stdin 和 stdout 通信。 +* **传输**:任何可以 `async with ... as (read, write)` 的对象,比如用 `streamable_http_client(url, http_client=...)` 包装你自己的 HTTP 客户端。 -本页其余内容在这三种方式下完全相同。请求头、子进程、超时以及 `Transport` 协议另有专页:**[客户端传输](transports.md)**。 +本页其余内容在这四种方式下完全相同。请求头、子进程、超时以及 `Transport` 协议另有专页:**[客户端传输](transports.md)**。 ### 已连接的客户端上有什么 {#whats-on-a-connected-client} @@ -82,7 +83,7 @@ UI 渲染参数表单所需的一切,以及模型生成合法参数所需的 `call_tool(name, arguments)` 运行工具,返回 `CallToolResult`。 -```python title="client.py" hl_lines="26-33" +```python title="client.py" hl_lines="27-34" --8<-- "docs_src/client/tutorial003.py" ``` @@ -113,7 +114,7 @@ result.is_error # False 抛出异常的工具**不会**在客户端里抛出异常。它作为一个普通结果返回,带 `is_error=True`。 !!! check - 向 `lookup_book` 查询 `"Solaris"`(目录里没有的书名),函数会抛出 `ValueError`。调用仍然正常返回: + 向 `lookup_book` 查询 `"Solaris"`(目录里没有的书名),函数会抛出 `ToolError`。调用仍然正常返回: ```python result.is_error # True @@ -121,7 +122,7 @@ result.is_error # False result.structured_content # None ``` - 异常消息落在了 `content` 里,**模型**可以读到它并重试。这是有意为之:工具错误是对话的一部分,不是崩溃。在相信 `structured_content` 之前,务必先看 `is_error`。 + `ToolError` 的消息落在了 `content` 里,**模型**可以读到它并重试。这是有意为之:工具错误是对话的一部分,不是崩溃。(如果工具是因为别的异常崩溃的,`content` 里只会写 `Error executing tool lookup_book`。)在相信 `structured_content` 之前,务必先看 `is_error`。 !!! warning `is_error=True` 涵盖的不只是你自己的 `raise`。请求一个服务器根本没有的工具(`call_tool("does_not_exist", {})`),什么异常都不会抛出。返回的形状相同:`is_error=True`,`content` 里是 `Unknown tool: does_not_exist`。只有当服务器回复的是 JSON-RPC **错误**而不是结果时,`Client` 方法才会抛出 `MCPError`;服务器在什么情况下产生哪一种,见 **[处理错误](../servers/handling-errors.md)**。 diff --git a/i18n/zh/pages/client/transports.md b/i18n/zh/pages/client/transports.md index 7b0c610ef7..ecfd8ac867 100644 --- a/i18n/zh/pages/client/transports.md +++ b/i18n/zh/pages/client/transports.md @@ -1,6 +1,6 @@ --- translation: - sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, ff7401df479af877, 3d0832f39b0d7059, d4bf7e4479637768, 05e20c0a798860e7] + sections: [9cac816674181eb0, 0700f337babcd4dd, 2bde0dd58cdf00f5, 40b4916d82eaf1d4, 3d0832f39b0d7059, dfa4446556badef0, 5bd93be2ab2ecb9c] tool: 1 --- # 客户端传输 {#client-transports} @@ -79,15 +79,15 @@ translation: **stdio** 服务器是一个子进程。客户端启动它,向它的 stdin 写 JSON-RPC,从它的 stdout 读 JSON-RPC。桌面宿主就是这样在你的机器上运行服务器的:宿主**就是**这段代码加上一个 UI,而 **[连接到真实宿主](../get-started/real-host.md)** 是从宿主一侧、以配置文件的形式看到的同一种关系。 -用 `StdioServerParameters` 描述进程,用 `stdio_client` 把它变成传输,再把**它**交给 `Client`: +用 `StdioServerParameters` 描述进程,再把它交给 `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` 不接受单独的参数对象。`StdioServerParameters` 是配置;`stdio_client(server)` 才是知道如何据此启动进程的传输。一定要包一层。 +进入块时启动进程。离开块时关停子进程:关闭 stdin,等待,如果它迟迟不退出就杀掉。你从来不需要自己清理。 -离开 `async with` 块也会关停子进程:关闭 stdin,等待,如果它迟迟不退出就杀掉。你从来不需要自己清理。 +子进程的 stderr 会输出到你的 stderr。要把它送到别处,就用 `stdio_client`(来自 `mcp`)自己构建传输,改为传入它:`Client(stdio_client(server, errlog=log_file))`。 !!! warning 子进程**不会**继承你的环境。它只拿到一个最小的允许列表(POSIX 上是 `HOME`、`LOGNAME`、`PATH`、`SHELL`、`TERM` 和 `USER`),这样敏感信息就不会泄漏进一个可能不是你写的进程。 @@ -102,16 +102,16 @@ translation: 对 `Client` 来说,上面这些都是同一种东西。 -**传输**是任何能产出一对 `(read, write)` 消息流的异步上下文管理器:正式地说,就是 `mcp.client` 中的 `Transport` 协议。`Client` 按类型解析它的参数:服务器对象在进程内连接,`str` 变成 `streamable_http_client(url)`,其他任何东西都直接作为传输进入。正是最后这条规则让 `stdio_client(...)`、`streamable_http_client(...)` 和 `sse_client(...)` 都能放进同一个位置,也让你可以自己写一个。 +**传输**是任何能产出一对 `(read, write)` 消息流的异步上下文管理器:正式地说,就是 `mcp.client` 中的 `Transport` 协议。`Client` 按类型解析它的参数:服务器对象在进程内连接,`str` 变成 `streamable_http_client(url)`,`StdioServerParameters` 变成 `stdio_client(params)`,其他任何东西都直接作为传输进入。正是最后这条规则让 `stdio_client(...)`、`streamable_http_client(...)` 和 `sse_client(...)` 都能放进同一个位置,也让你可以自己写一个。 ## 回顾 {#recap} * `Client(mcp)`(服务器对象)在内存中连接。用于测试和嵌入。 * `Client("http://.../mcp")`(URL)通过 Streamable HTTP 连接,即生产环境的传输方式。 * 请求头、认证、代理和超时应放在 `httpx2.AsyncClient` 上,再传给 `streamable_http_client(url, http_client=...)`。没有 `headers=` 关键字参数。 -* stdio 是 `Client(stdio_client(StdioServerParameters(...)))`,绝不是单独的参数对象。 +* stdio 是 `Client(StdioServerParameters(...))`。只有在需要重定向子进程的 stderr 时,才自己用 `stdio_client(...)` 包一层。 * 子进程拿到的是允许列表里的环境,不是你的环境;`env=` 往里添加。 -* 传输就是任何可以 `async with x as (read, write)` 的东西。凡不是服务器对象或 URL 的参数,`Client` 都直接交给这个协议。 +* 传输就是任何可以 `async with x as (read, write)` 的东西。凡不是服务器对象、URL 或 `StdioServerParameters` 的参数,`Client` 都直接交给这个协议。 * 构造 `Client` 选定传输方式。`async with` 打开它。 传输打开之后,两边必须就协议版本达成一致。通常根本不用考虑它;需要考虑的时候,去看 **[协议版本](../protocol-versions.md)**。 diff --git a/i18n/zh/pages/deprecated.md b/i18n/zh/pages/deprecated.md index eab92f5d65..9785fdf205 100644 --- a/i18n/zh/pages/deprecated.md +++ b/i18n/zh/pages/deprecated.md @@ -1,11 +1,11 @@ --- translation: - sections: [20541a40dbdd5980, 01262a123ad9501d, 429db5b574a2ac08, 56b2d49da412cb28, 6a1717123fe4513c] + sections: [490237e61c3a7a44, 01262a123ad9501d, 429db5b574a2ac08, e2d0d273fbd2d74b, 64ab0331e868f3d4, 6c8878ce2d1f6d56, 4068f23e371bf0b3, eaef75b8725bc931] tool: 1 --- # 已弃用的功能 {#deprecated-features} -2026-07-28 规范让五项内容退役。SDK 仍然实现了其中每一项,而且每一项现在都带有**弃用警告**。 +2026-07-28 规范让五项内容退役。SDK 仍然实现了其中每一项,而且每一项现在都带有**弃用警告**。另有一个 SDK 辅助函数因自身原因被弃用,列在[本页末尾](#deprecated-sdk-helpers)。 下表列出了每一项已弃用的功能、它为什么要退场,以及应该改用的替代方案。 @@ -49,6 +49,55 @@ MCPDeprecationWarning: The logging capability is deprecated as of 2026-07-28 (SE 两个信号,按这个顺序。`MCPDeprecationWarning` 在你调用方法的那一刻触发,任何连接上都是如此。错误是 SDK 随后尝试发送时返回的结果。这两个功能只有在 `mode="legacy"` 连接上、且客户端注册了对应回调时,才能端到端地工作。 +## 旧版会话上的 `ping` {#ping-on-a-legacy-session} + +**ping** 是一个空请求,任何一方都可以发送,用来确认对方仍在应答。2026-07-28 规范移除了它([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)):现代客户端发出的每个请求本身就证明服务器在那里,而现代服务器没有通道可以发出 ping。两个 SDK 方法在握手时代的会话上仍然有效。从客户端: + +```python +async def main() -> None: + async with Client("http://localhost:8000/mcp", mode="legacy") as client: + await client.send_ping() # warns; returns an EmptyResult +``` + +从服务器,在任意处理函数内部: + +```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()` 每次调用都会发出 `MCPDeprecationWarning`。在默认(`2026-07-28`)连接上,服务器改为回应 `MCPError: Method not found`。 +* `ctx.session.send_ping()` 不带警告。在现代连接上,它和其他任何服务器发起的请求一样,抛出同样的无反向通道(back-channel)错误。 +* 双方都不需要注册任何东西来应答 ping。 + +## 根目录变更通知 {#roots-change-notifications} + +声明了根目录能力的 2025 时代客户端,可以发送 `notifications/roots/list_changed` 告诉服务器它的工作区文件夹变了;服务器的回应是再次请求 `roots/list`。2026-07-28 规范把这个通知连同其余推送式的根目录流程一起移除了。在客户端,传入 `list_roots_callback=`(**[客户端回调](client/callbacks.md)**)就等于声明了 `"roots": {"listChanged": true}`,而一次调用就能兑现这个承诺: + +```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() +``` + +在服务器端,接收方的处理函数交给低层 `Server`: + +```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` 是你的 `list_roots_callback` 返回的列表。`client.send_roots_list_changed()` 会发出警告,并且需要 `mode="legacy"` 客户端:在现代连接上,这个通知会被静默丢弃。之后保持会话打开,因为服务器后续的 `roots/list` 请求会从这个会话上到达。 +* `MCPServer` 没有针对这个通知的钩子。在低层 `Server` 上,`on_roots_list_changed=` 注册处理函数(它也已弃用,并在构造时发出警告)。通知不带任何载荷,所以处理函数调用 `ctx.session.list_roots()` 获取新列表。 + ## 屏蔽警告 {#silencing-the-warning} 新代码里不要这样做。 @@ -66,21 +115,30 @@ warnings.filterwarnings("ignore", category=MCPDeprecationWarning) 整个 API 就这些。没有按方法的开关,你也不需要:只用一个类别的意义就在于,一行代码让它静音,一行代码把它恢复。 !!! check - 反过来用这个过滤器,就白得一个回归测试。在 pytest 配置的 `filterwarnings` 设置里加上 `"error::mcp.MCPDeprecationWarning"`,已弃用的调用就会**抛出异常**而不是发出警告。一个名为 `old_log`、仍在调用 `ctx.info()` 的工具不再通过,转而报告: + 反过来用这个过滤器,就白得一个回归测试。在 pytest 配置的 `filterwarnings` 设置里加上 `"error::mcp.MCPDeprecationWarning"`,已弃用的调用就会**抛出异常**而不是发出警告。一个名为 `old_log`、仍在调用 `ctx.info()` 的工具不再通过:调用返回 `is_error=True`,附带 `Error executing tool old_log`,而捕获到的服务器日志点出了元凶: ```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). ``` 一行 pytest 配置,已弃用的调用就再也不可能悄悄溜回你的代码库而不让测试失败。 +## 已弃用的 SDK 辅助函数 {#deprecated-sdk-helpers} + +这些不是规范变更,只是有了更好替代的 SDK 内部实现。它们用同一个 `MCPDeprecationWarning` 发出警告,并将在 3.0 中移除。 + +| 已弃用 | 替代做法 | +|---|---| +| `FuncMetadata.call_fn_with_arg_validation()` | 先调用 `FuncMetadata.validate_arguments()`,再调用 `FuncMetadata.call_fn()`。只有直接驱动 `FuncMetadata` 的代码(比如自定义的 `Tool` 子类)才调用过它。 | + ## 回顾 {#recap} * 2026-07-28 规范弃用了**根目录**、服务器发起的**采样**和协议**日志**(都出自 [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)),把**进度**限制为只能由服务器发往客户端,并移除了 **`ping`**。 * 替代做法那一列为你指明了去处:采样和根目录看 **[多轮往返请求](handlers/multi-round-trip.md)**,日志看 **[日志](handlers/logging.md)**,进度看 **[进度](handlers/progress.md)**。`ping` 什么都不需要。 * 弃用只是建议性的:线路上没有变化,在 2026 之前的会话上一切照常工作,你会看到一条醒目的 `MCPDeprecationWarning`(它是 `UserWarning`,所以默认开启)。 -* 采样和根目录还需要一条反向通道(back-channel),而 2026-07-28 会话没有。在现代连接上,它们先警告,然后抛出异常。 +* 采样和根目录还需要一条反向通道,而 2026-07-28 会话没有。在现代连接上,它们先警告,然后抛出异常。 * `warnings.filterwarnings("ignore", category=MCPDeprecationWarning)` 让整个类别静音;pytest 中的 `"error::mcp.MCPDeprecationWarning"` 把它变成测试失败。 +* 一个 SDK 辅助函数 `FuncMetadata.call_fn_with_arg_validation()` 单独被弃用,将在 3.0 中移除。 * 新代码不应建立在其中任何一项之上。 本文档的其他每一页讲的都是当前的 API。 diff --git a/i18n/zh/pages/get-started/real-host.md b/i18n/zh/pages/get-started/real-host.md index 69c84bf335..0f290ec1a9 100644 --- a/i18n/zh/pages/get-started/real-host.md +++ b/i18n/zh/pages/get-started/real-host.md @@ -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 --- # 连接到真实的宿主 {#connect-to-a-real-host} @@ -11,7 +11,7 @@ translation: ## 一个服务器,所有宿主 {#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" ``` @@ -41,7 +41,7 @@ uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py !!! note "本页讲的是本地场景" 这里的一切都是在宿主所在的那台机器上运行你的服务器:宿主通过 stdio 启动你的文件。对个人工具或单机工具来说,这样做完全合适。要把服务器交给 **没有** 你这个文件的人,给出去的是 **URL** 而不是命令:同一个 `mcp` 对象,通过 Streamable HTTP 提供服务。**[运行服务器](../run/index.md)** 用一张表讲清这个决策,**[部署与扩展](../run/deploy.md)** 则是从那里走到真实主机名的路线。 - 而且宿主不过是内置了 MCP 客户端的应用程序,所以你自己的 Python 也能扮演宿主的角色:**[客户端传输方式](../client/transports.md)** 用 `stdio_client(...)` 把同一个文件作为子进程启动,**[测试](testing.md)** 则一个进程都不起,直接在内存中连接它。 + 而且宿主不过是内置了 MCP 客户端的应用程序,所以你自己的 Python 也能扮演宿主的角色:**[客户端传输方式](../client/transports.md)** 用 `Client(StdioServerParameters(...))` 把同一个文件作为子进程启动,**[测试](testing.md)** 则一个进程都不起,直接在内存中连接它。 ## Claude Desktop {#claude-desktop} diff --git a/i18n/zh/pages/get-started/testing.md b/i18n/zh/pages/get-started/testing.md index cca6253f83..234d378b81 100644 --- a/i18n/zh/pages/get-started/testing.md +++ b/i18n/zh/pages/get-started/testing.md @@ -1,6 +1,6 @@ --- translation: - sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef] + sections: ['4926721070127497', c52a1de2b6b32f40, 8e792bf8c7489ec6, 627195f7159e24ef] tool: 1 --- # 测试 {#testing} @@ -80,7 +80,7 @@ async def test_call_add_tool(client: Client): 可能出错的情况有两种,而这个标志只管其中一种。 -**你的工具**内部抛出的异常不算协议失败。它会变成一个带 `is_error=True` 的普通结果,模型会读到其中的消息。`raise_exceptions` 不会改变这一点:不管有没有它,`call_tool` 返回的都是同一个 `is_error=True` 结果。有一整页专门讲这个:**[处理错误](../servers/handling-errors.md)**。 +**你的工具**内部抛出的异常不算协议失败。它会变成一个带 `is_error=True` 的普通结果(如果抛出的是 `ToolError`,模型会读到你写的消息)。`raise_exceptions` 不会改变这一点:不管有没有它,`call_tool` 返回的都是同一个 `is_error=True` 结果。有一整页专门讲这个:**[处理错误](../servers/handling-errors.md)**。 工具函数体**之外**的失败则不同。在 `Client(mcp)` 提供的这条连接上,服务器会先把它脱敏成一条笼统的 `"Internal server error"`,客户端才会看到。意外崩溃的细节绝不应该泄露给远程调用方。但在测试里,这恰恰是你**不**想要的,也正是 `raise_exceptions=True` 所改变的:测试看到的是真实的消息,而不是脱敏后的那条。 diff --git a/i18n/zh/pages/handlers/elicitation.md b/i18n/zh/pages/handlers/elicitation.md index c9d234a4e0..432d0b60a5 100644 --- a/i18n/zh/pages/handlers/elicitation.md +++ b/i18n/zh/pages/handlers/elicitation.md @@ -1,6 +1,6 @@ --- translation: - sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 0bb1396c86daeba4, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] + sections: [335ca2a0b266f003, d1ad562d3fe87bc0, 25b49f89c9e6f9d0, d1cb1235bb9ee267, 833179c09d239c83, e5d6dec2d2e655e8] tool: 1 --- # 征询 {#elicitation} @@ -83,7 +83,7 @@ translation: 这个模式就是表单。`Field(description=...)` 是标签;默认值会预填输入框,并让该字段变成可选。这和 **[工具](../servers/tools.md)** 里描述的工具参数用的是同一套 Pydantic 转 JSON Schema 的机制。 !!! warning - 征询的模式不如工具的输入模式表达力强。只能是扁平的原始类型字段:`str`、`int`、`float`、`bool`,或字符串的 `Literal`(会变成 `enum`)。在模型里再放一个模型,`ctx.elicit` 会在任何东西发给客户端之前抛出异常: + 征询的模式不如工具的输入模式表达力强。只能是扁平的原始类型字段:`str`、`int`、`float`、`bool`,或字符串的 `Literal`(会变成 `enum`)。在模型里再放一个模型,`ctx.elicit` 会在任何东西发给客户端之前抛出异常。工具调用以 `Error executing tool ` 失败,原因记在你的服务器日志里: ```text TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition @@ -104,7 +104,7 @@ translation: 拒绝不是错误。由工具决定拒绝意味着什么(这里是不订位),然后正常回答模型。 !!! tip - 答案在你的代码看到之前就已按你的模型验证过。一个给 `bool` 字段发来 `"maybe"` 的客户端不会弄坏你的订位:调用以模式不匹配的错误失败,你的 `if` 根本不会执行。 + 答案在你的代码看到之前就已按你的模型验证过。一个给 `bool` 字段发来 `"maybe"` 的客户端不会弄坏你的订位:`ctx.elicit` 抛出 `ValueError`,调用失败,你的 `if` 根本不会执行。 ## 把用户引到一个 URL {#send-the-user-to-a-url} diff --git a/i18n/zh/pages/handlers/logging.md b/i18n/zh/pages/handlers/logging.md index b01d8342e6..49f9dc9392 100644 --- a/i18n/zh/pages/handlers/logging.md +++ b/i18n/zh/pages/handlers/logging.md @@ -1,6 +1,6 @@ --- translation: - sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, c03764bd9dfeef7b, 4a0391691a674ae4, 2df5cd279eabf9f5] + sections: [c93a3e1aefd77955, 7851abd5ec54393b, f49d1ca2f330f9cd, 4cc0a00347c3f534, 4a0391691a674ae4, 2df5cd279eabf9f5] tool: 1 --- # 日志 {#logging} @@ -49,6 +49,8 @@ MCP 在协议层面有一个**日志能力**(logging capability):服务器 `logging.basicConfig()` 永远不会替换已经存在的 handler。如果你在创建服务器之前自己配置了日志,以你的配置为准。 +也不需要只为了记录失败而在每个处理函数里都写 `try`/`except`。工具或资源函数抛出异常时,SDK 会替你记录下来。记录了什么、用哪个级别,详见 **[处理错误](../servers/handling-errors.md#any-other-exception)**。 + ## 试一试 {#try-it} 用 MCP Inspector 运行服务器: diff --git a/i18n/zh/pages/run/index.md b/i18n/zh/pages/run/index.md index 5ccfe0791f..fb8425f581 100644 --- a/i18n/zh/pages/run/index.md +++ b/i18n/zh/pages/run/index.md @@ -1,6 +1,6 @@ --- translation: - sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 7a53ead3e704a7f0, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] + sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0] tool: 1 --- # 运行服务器 {#running-your-server} @@ -70,7 +70,7 @@ Inspector 做的事和真实宿主完全一样:它把 `server.py` 作为子进 * `streamable_http_path`:MCP 端点所在的路径。默认 `/mcp`。 * `json_response=True`:用单个 JSON 正文回应每个 POST,而不是 SSE 流。这个正文只容得下响应本身,别的什么都放不下,所以在请求中途回调客户端的工具(`ctx.elicit()`、采样(sampling))会在这一段抛出 `NoBackChannelError`;与进行中的调用绑定的通知(`ctx.report_progress()` 的进度、每次调用的日志消息)会被丢弃;独立的 `GET` 流仍然承载与之无关的通知。 * `stateless_http=True`:每个请求一个全新的传输,不跟踪会话。 -* `max_request_body_size`:接受的最大 POST 正文大小,单位为字节。默认 4 MiB;更大的请求在解析或创建会话之前就会收到 HTTP 413。只有当合法的 MCP 消息确实超过这个大小时才调高它。 +* `max_request_body_size`:接受的最大请求正文大小,单位为字节。默认 4 MiB;更大的请求在解析或创建会话之前就会收到 HTTP 413。只有当合法的 MCP 消息确实超过这个大小时才调高它。 * `event_store`、`retry_interval`、`transport_security`:可恢复性和 DNS 重绑定防护。它们可以先放一放,等部署到 localhost 以外的地方再说;**[部署与扩展](deploy.md)** 介绍了 `transport_security`。 !!! warning diff --git a/i18n/zh/pages/servers/handling-errors.md b/i18n/zh/pages/servers/handling-errors.md index 1cb707522f..28f3017173 100644 --- a/i18n/zh/pages/servers/handling-errors.md +++ b/i18n/zh/pages/servers/handling-errors.md @@ -1,13 +1,13 @@ --- translation: - sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601] + sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5] tool: 1 --- # 错误处理 {#handling-errors} -工具失败有两种方式,SDK 对它们的处理截然不同。 +工具失败有三种方式,SDK 对每一种的处理都不一样。 -抛出普通异常,看到它的是**模型**。抛出 `MCPError`,看到它的是**协议**。 +抛出 `ToolError`,看到你消息的是**模型**。抛出 `MCPError`,看到它的是**协议**。抛出其他任何东西就是崩溃:模型只知道调用失败了,traceback 进你的日志。 这一页讲的就是怎么选。 @@ -15,11 +15,11 @@ translation: 拿一个查东西的工具来说,让它查不到: -```python title="server.py" hl_lines="11-12" +```python title="server.py" hl_lines="2 12-13" --8<-- "docs_src/handling_errors/tutorial001.py" ``` -这两行没有任何 MCP 特有的东西。`get_author` 抛出一个普通的 `ValueError`,和任何 Python 函数一样。 +`ToolError` 来自 `mcp.server.mcpserver.exceptions`,是工具告诉模型出了问题的方式。 用一个书目里没有的书名去调用它,看看结果: @@ -30,19 +30,21 @@ result.structured_content # None ``` * 请求**成功了**。有一个结果;调用方这边什么也没抛出。 -* `is_error` 为 `True`,你的异常消息(前面加了工具名)就在 `content` 里,正是模型读取的位置。 +* `is_error` 为 `True`,你的消息(前面加了工具名)就在 `content` 里,正是模型读取的位置。 * `structured_content` 为 `None`。失败的调用没有返回值可供结构化。 -这就是**工具错误**,也是工具抛出的**任何**异常的默认归宿。而且它几乎总是你想要的效果。 +这就是**工具错误**,而且它几乎总是你想要的效果。 调用工具的是模型,参数也是它挑的。所以工具错误就是对话里的一个回合:模型读到“No book titled 'Nothing' in the catalog.”,发现自己猜错了书名,就换个更好的再调一次。你只写了一个 `raise`,就得到了一个会自我纠正的智能体。 +在服务器上,一个 `ToolError` 就是日志里的一行 `INFO`,没有 traceback。这是你预料之中的,所以没什么可查的。 + !!! tip 永远不要从工具里 `return` 错误消息。返回的字符串带的是 `is_error=False`,所以在模型(以及每个客户端 UI)看来,工具运行正常,那个字符串就是答案。要 `raise`。这个标志才是信号。 ## 模型纠正不了的错误 {#an-error-the-model-cannot-fix} -现在把 `ValueError` 换成 `MCPError`。 +现在把 `ToolError` 换成 `MCPError`。 ```python title="server.py" hl_lines="1 3 14" --8<-- "docs_src/handling_errors/tutorial002.py" @@ -74,16 +76,35 @@ result.structured_content # None 两条路径回答的是两个不同的问题。 -* **抛出任意异常**,对应**执行**层面的失败:工具想做的事没做成。调用是模型选的,所以后果也该让模型看到,给它补救的机会。拼错的书名、超时的上游 API、不存在的数据行:全是工具错误。 +* **抛出 `ToolError`**,对应**执行**层面的失败:工具想做的事没做成。调用是模型选的,所以后果也该让模型看到,给它补救的机会。拼错的书名、超时的上游 API、不存在的数据行:全是工具错误。 * **抛出 `MCPError`**,对应**请求本身**就该被拒绝的情况:客户端缺少工具所依赖的某项能力,服务器当前的状态没法为任何人服务,调用方跳过了某个必需步骤。这些问题模型怎么重试都修不好,所以把消息交给它没有任何好处。 -一个问题就能定夺:**换个更聪明的模型,能避免这个问题吗?** 能 -> 普通异常。不能 -> `MCPError`。 +一个问题就能定夺:**换个更聪明的模型,能避免这个问题吗?** 能 -> `ToolError`。不能 -> `MCPError`。 按这个标准,第二版 `get_author` 选错了:换个更好的书名就能解决,所以模型理应看到那条消息。它放在这里是为了让你看清机制,而不是推荐这种写法。 !!! info `MCPError` 通过 `from mcp import MCPError` 导入,接受 `code`、`message` 和可选的 `data` 载荷。你往里放什么,客户端就收到什么:SDK 会把抛出的 `MCPError` 原样转发,不做任何清理。 +## 任何其他异常 {#any-other-exception} + +现在把检查去掉,让字典查找自己失败: + +```python title="server.py" hl_lines="11" +--8<-- "docs_src/handling_errors/tutorial004.py" +``` + +`CATALOG[title]` 抛出 `KeyError`。这不在你的计划之内,所以 SDK 把它当作崩溃: + +```python +result.is_error # True +result.content # [TextContent(text="Error executing tool get_author")] +``` + +调用仍然返回 `is_error=True`,所以模型知道它失败了,可以继续往下走。它拿不到的是异常的文本:你代码里的一个 `KeyError`,或者隔着三层库的某个驱动吐出的一堆 SQL,都可能暴露服务器的内部细节,所以它永远不会离开服务器。 + +拿到它的是你。服务器以 `ERROR` 级别记录这次崩溃,附带完整的 traceback,消息是 `Tool 'get_author' raised an unexpected exception`。所以一个设在 `WARNING` 级别的生产日志,遇到每个 `ToolError` 都保持安静,一旦真有东西坏了就会出声。 + ## 不存在的资源 {#a-resource-that-doesnt-exist} 资源也划出同样的界线,并为常见情况自带了一个具名异常。 @@ -104,7 +125,7 @@ result.structured_content # None } ``` -注意这里没有 `is_error=True` 式的“半个结果”。资源读取要么返回内容,要么失败:资源只有协议这一条路径。模板以及资源的其他方方面面,详见 **[资源](resources.md)**。 +注意这里没有 `is_error=True` 式的“半个结果”。资源读取要么返回内容,要么失败:资源只有协议这一条路径。`ResourceError` 是同样的东西,用于不属于“未找到”的失败(`-32603`,带你的消息),两者在日志里都是一行 `INFO`。除 `MCPError` 之外的任何其他异常都是崩溃:客户端收到只写明 URI 的 `-32603`,traceback 以 `ERROR` 级别进你的日志。模板以及资源的其他方方面面,详见 **[资源](resources.md)**。 ## 你永远不用抛的错误 {#errors-you-never-raise} @@ -115,16 +136,17 @@ result.structured_content # None 这意味着有一整类 `raise` 语句不用你写:不要重复校验自己的类型注解。 !!! info - 这一页上的一切都是**客户端**看到的样子,而你写测试时用的内存中的 `Client` 看到的也一模一样。就连 `raise_exceptions=True` 也不会把工具错误变回 traceback:等那个标志能起作用的时候,你的异常早已是 `is_error=True` 的结果了。对结果做断言。这个模式详见 **[测试](../get-started/testing.md)**。 + 这一页上**客户端**看到的一切,你写测试时用的内存中的 `Client` 看到的也一模一样。就连 `raise_exceptions=True` 也不会把失败工具的异常交还给调用方:等那个标志能起作用的时候,你的异常早已是 `is_error=True` 的结果了。对结果做断言。如果需要崩溃的 traceback,它在服务器的日志里,pytest 的 `caplog` 能捕获到。这个模式详见 **[测试](../get-started/testing.md)**。 ## 回顾 {#recap} -* 在工具里抛出**任意异常** -> 调用返回 `is_error=True`,你的消息在 `content` 里。模型读到后可以重试。这是默认行为。 +* 在工具里抛出 **`ToolError`** -> 调用返回 `is_error=True`,你的消息在 `content` 里。模型读到后可以重试。 * 抛出 **`MCPError`** -> 调用本身以 JSON-RPC 错误失败。模型什么也看不到;由宿主处理。`code`、`message` 和 `data` 原封不动地保留。 -* 决定性的问题:“换个更聪明的模型,能避免这个问题吗?”能 -> 异常。不能 -> `MCPError`。 +* 决定性的问题:“换个更聪明的模型,能避免这个问题吗?”能 -> `ToolError`。不能 -> `MCPError`。 +* 任何**其他异常**都是崩溃 -> `is_error=True`,模型只看到 `Error executing tool `,你拿到一条带 traceback 的 `ERROR` 记录。 * 资源处理函数抛出 `ResourceNotFoundError` -> 协议的 `-32602`,URI 在 `data` 里。 * 不合法的参数在你的函数运行之前就会对照模式被拒掉;这些不用你 `raise`。 -* `from mcp import MCPError`;错误码常量来自 `mcp.types`。 +* 导入:`from mcp import MCPError`、`from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`,以及来自 `mcp.types` 的错误码常量。 错误处理完毕。服务器**对外暴露**的内容就是这些。每个处理函数在运行期间能读到什么、又能反过来对客户端做什么,是下一部分的内容:**[在处理函数内部](../handlers/index.md)**。 diff --git a/i18n/zh/pages/servers/media.md b/i18n/zh/pages/servers/media.md index 9291710b07..af5077876f 100644 --- a/i18n/zh/pages/servers/media.md +++ b/i18n/zh/pages/servers/media.md @@ -1,6 +1,6 @@ --- translation: - sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 043f526230dd243d, 6ee3e9bcfd24047a] + sections: [496394d24d221bf1, 4ceb4591180dc6c3, 0fd63e4682d02e0c, 969ede0bd3686a16, 864137b5e9c61e91, 043f526230dd243d, db1ef91db7d6b3f3] tool: 1 --- # 媒体 {#media} @@ -81,6 +81,24 @@ result.structured_content # None !!! check 用 `data=` 时没有文件名,也就无从推断。漏掉 `format=`,SDK 就会回退到默认值:图片是 `image/png`,音频是 `audio/wav`。照这样用 MP3 字节构建一个 `Audio`,客户端会被告知 `mime_type="audio/wav"`,然后老老实实地解码失败。传 `data=` 时,就要一并传 `format=`。 +## 嵌入资源 {#embedding-a-resource} + +工具还可以返回一份文档:一段文本或字节,连同它所在的 URI 和一个 MIME 类型。这就是 **`EmbeddedResource`**,另一种内容块。和普通的 `str` 不同,它会告诉客户端内容是什么,客户端因此可以把它作为附件显示,或者认出这是一个它已经知道的资源。 + +```python title="server.py" hl_lines="7 14 16-18" +--8<-- "docs_src/media/tutorial005.py" +``` + +* `brand://guidelines` 是一个普通的资源(**[资源](resources.md)** 讲的就是这些)。工具按请求把同一份文档交给模型,直接调用 `guidelines()` 能保持唯一的事实来源。 +* `EmbeddedResource` 和 `TextResourceContents` 来自 `mcp.types`。这里没有像图片那样的辅助类型:你构建的块原封不动地放进结果,也没有 `structured_content`。 +* 使用资源注册时所用的 URI,这样客户端才能分辨出附件和 `brand://guidelines` 是同一份文档。任何 URI 都合法,不管注册过没有。 + +```python +result.content # [EmbeddedResource(type="resource", resource=TextResourceContents(uri="brand://guidelines", mime_type="text/markdown", text="# Brand guidelines\n\n..."))] +``` + +二进制内容用 `BlobResourceContents(uri=..., mime_type=..., blob=...)` 代替 `TextResourceContents`,把字节 base64 编码后放进 `blob`。如果只想发送一个指针,让客户端之后再 `resources/read`,就改为返回 `ResourceLink(name=..., uri=...)`;它同样是一个内容块。 + ## 图标 {#icons} `Icon` 是元数据,不是内容。它不携带图片本身,而是用一个 URI 指向图片;客户端可以获取它,并显示在服务器名称、某个工具、资源或提示词旁边。 @@ -110,6 +128,7 @@ client.server_info.icons # [Icon(src="https://example.com/brand-kit.png", mime_ * 从工具返回 `Image` 或 `Audio`,客户端就会收到一个 `ImageContent` / `AudioContent` 块:字节经 base64 编码,附带 MIME 类型。 * 可以用 `path=` 构建,让后缀决定 MIME 类型;也可以用内存中的 `data=` 加上显式的 `format=` 构建。 +* 返回 `EmbeddedResource` 可以把一份文档(文本或 base64 blob,附带 URI 和 MIME 类型)放进结果;返回 `ResourceLink` 则只发送指针。 * 媒体结果不带 `structured_content`,也没有输出模式。 * `Icon` 是一个指针:一个 `src` URI,加上可选的 `mime_type`、`sizes` 和 `theme`。 * `icons=[...]` 可用于服务器、工具、资源和提示词,客户端在对应的对象上就能找到它们。 diff --git a/i18n/zh/pages/servers/prompts.md b/i18n/zh/pages/servers/prompts.md index f1cd70677e..757b125e3c 100644 --- a/i18n/zh/pages/servers/prompts.md +++ b/i18n/zh/pages/servers/prompts.md @@ -1,6 +1,6 @@ --- translation: - sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, ffe823cb0fedd488, f33651add1b59094] + sections: [d65c098f37f5b6c3, dd0c2724d6f2877e, 6835bb3570c6714c, d30d3c20168b88b2, f5ef38dad59d6f76, 6e38a699ba57fbdf, 2b984a3bf37a0ddd] tool: 1 --- # 提示词 {#prompts} @@ -137,7 +137,52 @@ uv run mcp dev server.py ``` !!! info - 如果读过 **[工具](tools.md)**,这一页的内容你其实都已经会了。装饰器一样,用 docstring 作描述一样,`Annotated`/`Field` 也一样。变的只有两点:由谁触发(用户),以及结果去哪儿(进入对话)。 + 如果读过 **[工具](tools.md)**,到这里为止的内容你其实都已经会了。装饰器一样,用 docstring 作描述一样,`Annotated`/`Field` 也一样。变的只有两点:由谁触发(用户),以及结果去哪儿(进入对话)。 + +## 不止文本 {#more-than-text} + +`UserMessage` 和 `AssistantMessage` 在接受 `str` 的地方,也接受内容块,或者 `Image` / `Audio` 辅助类。提示词里常见两种情况:附上一份文档,和附上一张图片。 + +### 嵌入文件 {#embedding-a-file} + +```python title="server.py" hl_lines="5 12 21 23" +--8<-- "docs_src/prompts/tutorial004.py" +``` + +* 风格指南是位于 `style://python` 的资源(**[资源](resources.md)** 会介绍这类东西),从 `server.py` 旁边的 `style-guide.md` 读取。在那里放任意一个 Markdown 文件即可。 +* `EmbeddedResource(resource=TextResourceContents(...))` 两者都来自 `mcp.types`,它把文件连同 URI 和 MIME 类型一起作为第一条消息携带;引用它的请求以纯文本形式跟在后面。 +* 用嵌入,而不是把指南直接贴进 f-string,客户端就能把它显示为附件,之后还能重新打开 `style://python`,模型收到的也是原封不动的文件。二进制文件用 `BlobResourceContents`,带一个 base64 的 `blob`。 + +渲染后,第一条消息的 `content` 是一个 `resource` 块: + +```json +{"type": "resource", "resource": {"uri": "style://python", "mimeType": "text/markdown", "text": "* Prefer early returns.\n..."}} +``` + +### 附上图片 {#attaching-an-image} + +```python title="server.py" hl_lines="4 15" +--8<-- "docs_src/prompts/tutorial005.py" +``` + +* `Image` 是 **[图片、音频和图标](media.md)** 里的辅助类。提示词渲染时,`UserMessage` 把它转换成一个 `ImageContent` 块(文件经 base64 编码,MIME 类型从 `.png` 推断);`Audio` 同样会变成 `AudioContent`。 +* 在 `server.py` 旁边放任意一个名为 `architecture.png` 的 PNG。提示词参数都是字符串,所以图片总是来自服务器;`component` 只提供文字。 + +```json +{"type": "image", "data": "iVBORw0KGgoAAAANSUhEUg...", "mimeType": "image/png"} +``` + +## 在运行时修改列表 {#changing-the-list-at-runtime} + +客户端连接期间也可以添加提示词,比如让用户把一条指令保存成自己的菜单项。先注册提示词,再发通知: + +```python title="server.py" hl_lines="5 23-27" +--8<-- "docs_src/prompts/tutorial006.py" +``` + +* `mcp.add_prompt(Prompt.from_function(fn, name=..., description=...))` 注册函数的方式和 `@mcp.prompt()` 完全一样,`mcp.remove_prompt(name)` 则相反。`add_prompt` 遇到同名的现有条目会保留它而不是覆盖,所以这个工具先删掉旧条目,让保存变成替换。`prompts/list` 立即反映这一变化。 +* `await ctx.notify_prompts_changed()` 向每个在 `subscriptions/listen` 流上监听的 `2026-07-28` 客户端发送 `notifications/prompts/list_changed`(**[订阅](../handlers/subscriptions.md)**)。当发起调用的客户端是 2026 之前的版本时,`await ctx.session.send_prompt_list_changed()` 把它发给这个客户端(**[服务旧版客户端](../run/legacy-clients.md)**)。两个都调用;没有人可通知时,它们各自什么也不做。 +* 收到通知的客户端会再次调用 `prompts/list`。在 Python `Client` 里就是 `async with client.listen(prompts_list_changed=True) as sub:`,它会产出一个 `PromptsListChanged` 事件。 ## 回顾 {#recap} @@ -147,5 +192,7 @@ uv run mcp dev server.py * 返回 `str`,它就变成一条用户消息。返回 `UserMessage` / `AssistantMessage` 的列表,可以为多轮对话铺好开头。 * `title=` 和 `Field(description=...)` 是客户端放进 UI 里的内容。 * 缺少必填参数会让整个请求失败。没有针对单个提示词的错误结果。 +* 把 `EmbeddedResource` 或 `Image` 包进 `UserMessage`,就能附上文档或图片。 +* 运行时用 `mcp.add_prompt(...)` / `mcp.remove_prompt(...)` 添加或移除提示词,然后 `await ctx.notify_prompts_changed()` 和 `await ctx.session.send_prompt_list_changed()`。 要在服务器端为提示词(或资源模板)的参数提供自动补全,见 **[补全](completions.md)**。 diff --git a/i18n/zh/pages/servers/structured-output.md b/i18n/zh/pages/servers/structured-output.md index ea2e3840ec..ededb26119 100644 --- a/i18n/zh/pages/servers/structured-output.md +++ b/i18n/zh/pages/servers/structured-output.md @@ -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 --- # 结构化输出 {#structured-output} @@ -103,7 +103,7 @@ result.structured_content # {"temperature": 16.2, "humidity": 0.83, "conditions --8<-- "docs_src/structured_output/tutorial003.py" ``` -`TypedDict` 在运行时就是普通的 `dict`,所以构建并返回的也就是它。模式、校验和 `structured_content` 都与 `BaseModel` 版本完全相同(只是少了描述,`TypedDict` 里没有地方写)。 +`TypedDict` 在运行时就是普通的 `dict`,所以构建并返回的也就是它。模式、校验和 `structured_content` 遵循与 `BaseModel` 版本相同的规则:加上类的 docstring 或 `Annotated[..., Field(description=...)]`,它们就成为描述;dict 里省略不写的 `NotRequired` 键也不会出现在 `structured_content` 中。 ## dataclass {#a-dataclass} @@ -185,16 +185,16 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} 注解承诺的是 `WeatherData`,上游响应却不再发送 `humidity` 了。 !!! check - 调用 `get_weather`,它不会悄悄把一个缺了一半的对象递给客户端。调用会失败,错误的头几行直接点名那个字段: + 调用 `get_weather`,它不会悄悄把一个缺了一半的对象递给客户端。调用会失败:客户端收到 `is_error=True` 和 `Error executing tool get_weather`,于是模型知道调用失败了,而不会信心十足地去读根本不存在的天气数据。字段名是留给你看的,记录在服务器日志的 `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] ``` - 这段文本作为工具结果返回,并带着 `is_error=True`,于是模型知道调用失败了,而不会信心十足地去读根本不存在的天气数据。 - 顺带一提,从 `-> WeatherData` 的工具里返回普通 `dict` 完全没问题。`json.loads` 产出的正是它。校验针对的是值,而不是 Python 类型。 ## 选择退出 {#opting-out} @@ -209,6 +209,10 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} 反过来,`structured_output=True` 会把自动检测变成硬性要求:返回类型产不出模式的工具会在导入时直接抛错,而不是退回到纯文本。 +## 内容块与媒体 {#content-blocks-and-media} + +内容块与媒体(`TextContent`、`EmbeddedResource`、`Image`、`Audio` 等等,无论是单独返回、作为 `list`、`tuple` 或 `Sequence` 的元素,还是作为联合类型的分支)已经替你退出了:它们是给模型读的,所以自动检测不会从中推导出模式(`Image` 和 `Audio` 详见 **[图像、音频与图标](media.md)**)。对内容块类,`structured_output=True` 仍然会强制生成一个模式。 + ## 没有类型提示的类 {#a-class-without-type-hints} 有一种情况,你没有要求也会落得非结构化:返回一个**类体上没有任何注解**的类。 @@ -237,6 +241,6 @@ result.structured_content # {"London": 16.2, "Reykjavik": 4.4} * 标量、列表、元组和联合类型会被包装进 `{"result": ...}`。模型、`TypedDict`、dataclass、带注解的类以及 `dict[str, ...]` 本身已是对象,保持原样。 * 每个结果都同时带有 `content`(文本,给模型)**和** `structured_content`(数据,给应用程序)。 * 返回的内容会对照模式校验。不匹配就是工具错误,而不是一个损坏的结果。 -* `structured_output=False` 让工具退出结构化输出。没有类型提示的类会悄无声息地退出;要当心。 +* `structured_output=False` 让工具退出结构化输出。内容块、`Image` 和 `Audio` 默认退出;没有类型提示的类会悄无声息地退出,要当心。 至此,工具能回传的一切都由你掌控。接下来是第二种原语:**[资源](resources.md)**。 diff --git a/i18n/zh/pages/servers/tools.md b/i18n/zh/pages/servers/tools.md index 2947fe4fbe..f21f8cdc4c 100644 --- a/i18n/zh/pages/servers/tools.md +++ b/i18n/zh/pages/servers/tools.md @@ -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} @@ -39,6 +39,8 @@ SDK 根据这些类型提示生成一份 JSON Schema,并在 `tools/list` 时 两个参数都在 `required` 里,因为都没有默认值。这一点马上就会改。(`title` 键是 Pydantic 附带生成的;属性、属性的类型和 `required` 才是契约。) +也没有 `$schema` 键:不带这个键的模式,MCP 一律按 **JSON Schema 2020-12** 处理,而 Pydantic 生成的正是它,所以在你到 **[底层 Server](../advanced/low-level-server.md#the-dialect-is-json-schema-2020-12)** 上手写模式之前,没有什么需要选择的。 + !!! tip 类型提示在这里不是文档,而是**契约**。如果客户端发来 `"limit": "ten"`,SDK 会在你的函数运行之前就把它拒掉。 diff --git a/i18n/zh/pages/servers/uri-templates.md b/i18n/zh/pages/servers/uri-templates.md index 858593875d..acb5e62803 100644 --- a/i18n/zh/pages/servers/uri-templates.md +++ b/i18n/zh/pages/servers/uri-templates.md @@ -1,6 +1,6 @@ --- translation: - sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 101ef9d14bf4ec46, 4b6c4a845438abc7, f98b46bafbee4acd] + sections: [4a7033e1ed8ad602, 55dcbfff0c6271bf, 317f4256a650cab6, 4b6c4a845438abc7, f98b46bafbee4acd] tool: 1 --- # URI 模板与路径安全 {#uri-templates-and-path-safety} @@ -98,7 +98,7 @@ translation: 内置检查能拦住常见情况,但无从知道你的沙箱边界在哪。访问文件系统时,用 `safe_join` 解析路径并确认它仍在基础目录之内: -```python title="server.py" hl_lines="4 14" +```python title="server.py" hl_lines="5 15" --8<-- "docs_src/uri_templates/tutorial002.py" ``` @@ -124,10 +124,10 @@ translation: | `reject_null_bytes` | `True` | 拒绝包含 `\x00` 的值 | | `exempt_params` | 空 | 要跳过检查的参数名 | -这些检查只是启发式的预过滤;访问文件系统时,`safe_join` 仍然是真正的隔离边界。 +这些检查只是启发式的预过滤;访问文件系统时,`safe_join` 仍然是隔离边界。 !!! tip - 如果处理函数无法完成请求(文件不存在、id 未知),就抛出异常。SDK 会把它变成错误响应。协议错误和工具错误的区别见 **[处理错误](handling-errors.md)**。 + 如果处理函数无法完成请求(文件不存在、id 未知),就像上面的 `read_manual` 那样抛出 `ResourceNotFoundError`。客户端会收到 `-32602`,附带你的消息和 URI。意料之外的异常则会变成通用的 `-32603`。见 **[处理错误](handling-errors.md#a-resource-that-doesnt-exist)**。 ## 底层 Server 上的资源 {#resources-on-the-low-level-server} diff --git a/i18n/zh/pages/troubleshooting.md b/i18n/zh/pages/troubleshooting.md index 3636a87111..ca4cfc78dd 100644 --- a/i18n/zh/pages/troubleshooting.md +++ b/i18n/zh/pages/troubleshooting.md @@ -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 --- # 故障排查 {#troubleshooting} @@ -79,11 +79,11 @@ async def main() -> None: `__aexit__` 就是断开连接,所以不存在会忘记调用的 `client.close()`。**[测试](get-started/testing.md)** 正是建立在这个模式之上。 -## `Error executing tool : ` 和 `Unknown tool: ` {#error-executing-tool-name-message-and-unknown-tool-name} +## `Error executing tool : `、`Error executing tool ` 和 `Unknown tool: ` {#error-executing-tool-name-message-error-executing-tool-name-and-unknown-tool-name} 你看到的是一个**结果**,不是异常。`call_tool` 没有抛异常,而且对于失败的工具它永远不会抛。 -用服务器不认识的城市调用 `forecast`,它抛出的异常会随着一个标记为**成功**的请求返回: +用服务器不认识的城市调用 `forecast`,它抛出的 `ToolError` 会随着一个标记为**成功**的请求返回: ```python result.is_error # True @@ -95,6 +95,8 @@ result.structured_content # None 修复在客户端:**检查 `result.is_error`**。包在 `call_tool` 外面的 `try/except` 一个也抓不到,因为根本没有东西可抓。这是有意为之,也是本页最值得记住的一点:调用是**模型**选的,所以消息交给模型,让它有机会重试。详见 **[处理错误](servers/handling-errors.md)**,包括**确实**会抛异常的 `MCPError` 路径。 +不带消息的裸形式 `Error executing tool ` 表示工具**崩溃了**:一个它没有预料到的异常逃逸了出来(或者返回值没有通过输出模式校验),而那个异常的文本不会放到线路上。traceback 在**服务器日志**里,级别是 `ERROR`,内容是 `Tool '' 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} 你写的是 `@mcp.tool` 而不是 `@mcp.tool()`。`tool()` 是一个装饰器**工厂**:没有括号的话,Python 会把你的函数传给它的 `name=` 参数。 @@ -393,7 +395,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key ## 回顾 {#recap} * `ExceptionGroup: unhandled errors in a TaskGroup` 从来都不是真正的错误。读**最后一行**;在 `async with Client(...)` 块**内部**捕获 `MCPError` 可以完全跳过这层包装。 -* `call_tool` 不会因为工具失败而抛异常。`Error executing tool ...` 和 `Unknown tool: ...` 是结果:检查 `result.is_error`。 +* `call_tool` 不会因为工具失败而抛异常。`Error executing tool ...` 和 `Unknown tool: ...` 是结果:检查 `result.is_error`。工具名后面没有消息表示它崩溃了,traceback 在服务器日志里。 * `Client must be used within an async context manager` -> 用 `async with`。`Use @tool() instead of @tool` -> 加上括号。 * 服务器日志里的 `Tool already exists:` 是两个同名工具合并成一个的唯一迹象。 * 一个 421,三种写法:`Server returned an error response`(python `Client`)、`421 Misdirected Request` / `Invalid Host header`(其他所有地方)、`Invalid Host header: `(服务器日志)。修复:`transport_security=TransportSecuritySettings(allowed_hosts=[...])`。 diff --git a/i18n/zh/pages/whats-new.md b/i18n/zh/pages/whats-new.md index d29a319050..fa2be14ce4 100644 --- a/i18n/zh/pages/whats-new.md +++ b/i18n/zh/pages/whats-new.md @@ -1,6 +1,6 @@ --- translation: - sections: [cfe01c0c5863dfa2, 11d93f1fa09eadf5, a7392996acf1ad8f, 875eb2889263424e] + sections: [cfe01c0c5863dfa2, 1c58c5cfcc37d455, a7392996acf1ad8f, 875eb2889263424e] tool: 1 --- # v2 的新变化 {#whats-new-in-v2} @@ -41,9 +41,9 @@ v1 交给你的是三层嵌套:一个产出原始流的传输上下文管理 --8<-- "docs_src/client/tutorial001.py" ``` -`Client` 接受一个服务器对象(内存直连,没有传输:这就是测试的做法)、一个 URL(Streamable HTTP),或者任意传输上下文管理器,比如 `stdio_client(...)`。进入 `async with` 就会建立连接并协商协议版本,不管服务器讲的是哪一代协议;之后 `client.server_capabilities` 和 `client.protocol_version` 直接就在那里,服务器表明身份时 `client.server_info` 也一样(它现在是 `Implementation | None`,因为 2026 版的身份信息是可选的)。你在 v1 注册的采样和征询回调仍然能用(它们的函数体会看到和本页其他地方一样的 snake_case 属性重命名),现在还会回答 2026 风格的、嵌在结果里的请求(见下文),并且是并发运行而不是一次一个。想要底层接口的人仍然可以用底下的 `ClientSession`,`client.session` 会把它交给你;它也变了(运行在新的调度器引擎上,自身的一些签名也改了),所以下探之前先读 **[迁移指南](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**。 +`Client` 接受一个服务器对象(内存直连,没有传输:这就是测试的做法)、一个 URL(Streamable HTTP)、一个 `StdioServerParameters`(stdio 子进程),或者其他任意传输上下文管理器,比如 `sse_client(...)`。进入 `async with` 就会建立连接并协商协议版本,不管服务器讲的是哪一代协议;之后 `client.server_capabilities` 和 `client.protocol_version` 直接就在那里,服务器表明身份时 `client.server_info` 也一样(它现在是 `Implementation | None`,因为 2026 版的身份信息是可选的)。你在 v1 注册的采样和征询回调仍然能用(它们的函数体会看到和本页其他地方一样的 snake_case 属性重命名),现在还会回答 2026 风格的、嵌在结果里的请求(见下文),并且是并发运行而不是一次一个。想要底层接口的人仍然可以用底下的 `ClientSession`,`client.session` 会把它交给你;它也变了(运行在新的调度器引擎上,自身的一些签名也改了),所以下探之前先读 **[迁移指南](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)**。 -**[Client](client/index.md)** 介绍它,**[客户端传输](client/transports.md)** 讲三种连接形式,**[客户端回调](client/callbacks.md)** 讲回调本身,**[测试](get-started/testing.md)** 展示取代 v1 `create_connected_server_and_client_session()` 辅助函数的内存模式。 +**[Client](client/index.md)** 介绍它,**[客户端传输](client/transports.md)** 讲四种连接形式,**[客户端回调](client/callbacks.md)** 讲回调本身,**[测试](get-started/testing.md)** 展示取代 v1 `create_connected_server_and_client_session()` 辅助函数的内存模式。 ### 底层 `Server` 是重建,不是改名 {#the-low-level-server-was-rebuilt-not-renamed} @@ -129,7 +129,7 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentB 重命名会自己跳出来提醒你。下面这些不会: * **同步函数在工作线程上运行。** `def` 定义的工具(或资源、提示词、解析器)不再阻塞事件循环;代价是它的函数体不再 **在** 事件循环线程上运行,这对有线程亲和性的代码有影响。`async def` 处理函数不受影响。**[迁移指南](migration.md#sync-handler-functions-now-run-on-a-worker-thread)**。 -* **在工具内部抛出的 `MCPError`(v1 的 `McpError`)现在是协议错误。** 模型永远看不到它。其他所有异常仍然会变成模型能读到并做出反应的 `is_error=True` 结果。两者的划分见 **[错误处理](servers/handling-errors.md)**。 +* **在工具内部抛出的 `MCPError`(v1 的 `McpError`)现在是协议错误。** 模型永远看不到它。其他所有异常仍然会变成 `is_error=True` 结果,但只有 `ToolError` 的消息能到达模型:其他任何异常现在显示为 `Error executing tool `,traceback 则写进你的服务器日志。两者的划分见 **[错误处理](servers/handling-errors.md)**。 * **结果在发出之前会被校验。** 手工构建的 `Tool` 如果 `input_schema` 是 `{}`,现在会让 `tools/list` 失败(规范要求 `"type": "object"`)。基于 `@mcp.tool()` 构建的服务器永远不会遇到这个;它们的模式是 SDK 写的。 * **你的客户端会校验收到的东西。** `list_tools()` 和 `call_tool()` 会按协商好的协议版本检查服务器的答复,所以 v1 宽松解析能容忍的不太合规的服务器,现在会抛 `pydantic.ValidationError`。如果你连接的是自己不控制的服务器,要做好由你来发现它们的准备;细节见 **[迁移指南](migration.md#client-validates-inbound-traffic-against-the-protocol-schema)**。 * **URI 模板现在是真正的 RFC 6570。** `{+path}`、`{?query}` 之类都能用,匹配是精确的而不是正则式的宽松匹配,提取出的值里的路径穿越默认会被拒绝。更严格的模板在装饰时就失败,而不是等到第一个请求。**[URI 模板](servers/uri-templates.md)**。