Skip to content

Commit 00e9565

Browse files
committed
Address review comments on the translation tooling
Staging lays stored sections out by their recorded hashes before anything is re-imposed, so a reordered English page can no longer pair code blocks or heading ids with the wrong section; code fences are checked and restored per section and a mismatch after carry-forward goes through the repair turns like any other finding; list items and table rows are counted per section so a shortened reply is sent back whatever language its filler is in. The client is only built when a page will actually call the model, stage clears its titles marker with the tree and stages every language in one pass, and staged pages link to the English page and the API reference relative to themselves. The language switcher keeps the fragment and query. Older generated pages drop a front-matter key the tool no longer writes.
1 parent d37ff04 commit 00e9565

209 files changed

Lines changed: 548 additions & 357 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pre-commit run --all-files
128128

129129
## Documentation and Translations
130130

131-
Documentation contributions are English only: the pages under `docs/` are the source of truth, and the translated documentation sites are generated from them, guided by the per-language style guides and glossaries under `i18n/<lang>/`. Never edit the generated pages under `i18n/<lang>/pages/`—the next translation run overwrites them. To fix a translation, change that language's `instructions.md` or `glossary.json` (or the English page, if that's where the problem is), and the fix carries into every future run. See [`i18n/README.md`](i18n/README.md) for the details.
131+
Documentation contributions are English only: the pages under `docs/` are the source of truth, and the translated documentation sites are generated from them, guided by the per-language style guides and glossaries under `i18n/<lang>/`. Never edit the generated pages under `i18n/<lang>/pages/`—the tool can't tell a hand edit from its own output, so the edit persists unchecked, is carried forward into future runs, and hides the real fix. To fix a translation, change that language's `instructions.md` or `glossary.json` (or the English page, if that's where the problem is) and re-run `translate --pages` for the affected pages; the fix then carries into every future run. See [`i18n/README.md`](i18n/README.md) for the details.
132132

133133
## Pull Requests
134134

docs/js/language-switch.js

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,25 @@
77
const base = JSON.parse(document.getElementById("__config").textContent).base;
88
// The site root as a directory path; `base` lacks the trailing slash on 404 pages.
99
const site = new URL(base.replace(/\/?$/, "/"), location).pathname;
10+
const entries = ".md-select__link[hreflang]";
11+
12+
function samePage(entry) {
13+
const page = location.pathname.slice(site.length);
14+
return entry.dataset.site + (page.startsWith("api/") ? "" : page);
15+
}
1016

1117
document$.subscribe(() => {
12-
let page = location.pathname.slice(site.length);
13-
if (page.startsWith("api/")) page = "";
14-
for (const entry of document.querySelectorAll(".md-select__link[hreflang]")) {
18+
for (const entry of document.querySelectorAll(entries)) {
1519
entry.dataset.site ??= entry.getAttribute("href"); // the language root the theme rendered
16-
entry.href = entry.dataset.site + page;
20+
entry.href = samePage(entry);
1721
}
1822
});
23+
24+
// Headings carry the same ids on every site, so the reader's place carries over
25+
// too: query and fragment as they are when the switch happens, not at page load.
26+
function aim(event) {
27+
const entry = event.target instanceof Element ? event.target.closest(entries) : null;
28+
if (entry?.dataset.site && (event.type !== "keydown" || event.key === "Enter"))
29+
entry.href = samePage(entry) + location.search + location.hash;
30+
}
31+
for (const type of ["click", "auxclick", "keydown"]) document.addEventListener(type, aim, true);

i18n/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ The English pages under `docs/` are the source. This directory holds what steers
1212
```text
1313
uv run --frozen python scripts/docs/translations.py status [--lang CODE]
1414
uv run --frozen --group translate python scripts/docs/translations.py translate --lang CODE [--pages PATH ...]
15-
uv run --frozen python scripts/docs/translations.py stage --lang CODE
15+
uv run --frozen python scripts/docs/translations.py stage [--lang CODE]
1616
```
1717

18-
`status` is offline: per language it lists missing, outdated (with the sections that changed), current and removable pages (translations whose English page is gone — `git rm` them). `translate` calls the Claude API (`ANTHROPIC_API_KEY` in the environment; the registry's model, or `DOCS_TRANSLATE_MODEL` to trial another) for the missing and outdated pages, retranslating only the English sections that changed and keeping the rest byte for byte; `--pages` instead re-translates exactly the named pages from scratch, which is also how a glossary or instructions change reaches existing pages (each generated page records the English section hashes it reflects, so editing those inputs invalidates nothing). `stage` assembles the tree a language site is built from; `scripts/docs/build.sh` runs it for every language. Commit the generated pages in an ordinary pull request.
18+
`status` is offline: per language it lists missing, outdated (with the sections that changed), current and removable pages (translations whose English page is gone — `git rm` them). `translate` calls the Claude API (`ANTHROPIC_API_KEY` in the environment; the registry's model, or `DOCS_TRANSLATE_MODEL` to trial another) for the missing and outdated pages, retranslating only the English sections that changed and keeping the rest byte for byte; `--pages` instead re-translates exactly the named pages from scratch, which is also how a glossary or instructions change reaches existing pages (each generated page records the English section hashes it reflects, so editing those inputs invalidates nothing). `stage` assembles the tree each language site is built from (every language's, or one with `--lang`); `scripts/docs/build.sh` runs it before building them. Commit the generated pages in an ordinary pull request.
1919

2020
To add a language, add an entry to `languages.yml`, write `<code>/instructions.md` (the sections the `pt` file has) and `<code>/glossary.json`, then run `translate --lang <code>`.

i18n/de/glossary.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
{
8888
"source": "elicitation",
8989
"target": "Elicitation",
90-
"note": "OPEN QUESTION for native review: there is no established German term for the server asking the person at the host a question mid-request. Provisionally kept in English — die Elicitation — glossed on its first appearance per page as \"Elicitation (Rückfrage bei der Person am Host)\"; in running prose the act itself may be described with Rückfrage / zurückfragen. Do not coin Erhebung or Abfrage for it. `elicitation/create`, `ctx.elicit()` and the `Elicit` class stay Latin."
90+
"note": "OPEN QUESTION for native review: there is no established German term for the server asking the person at the host a question mid-request. Provisionally kept in English — die Elicitation — glossed on its first appearance per page as \"Elicitation (Rückfrage bei der Person am Host)\", or, when that first appearance already sits inside parentheses, with a spaced en dash instead — \"Elicitation – Rückfrage bei der Person am Host\" — never a nested parenthesis; in running prose the act itself may be described with Rückfrage / zurückfragen. Do not coin Erhebung or Abfrage for it. `elicitation/create`, `ctx.elicit()` and the `Elicit` class stay Latin."
9191
},
9292
{
9393
"source": "capability",

i18n/de/pages/advanced/middleware.md

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,31 @@ Eine **Middleware** ist eine einzelne async-Funktion, die jede Nachricht umschli
1010
Du schreibst sie als `async (ctx, call_next)` und hängst sie an `server.middleware` an. Das ist die ganze API.
1111

1212
!!! warning
13-
Die Middleware-Liste ist im Quellcode als **provisional** markiert: Signatur und Semantik können
14-
sich in einem 2.x-Minor-Release ändern. Nutze sie zum *Beobachten* (Timing, Logging, Tracing) und zum
15-
*Ablehnen* von Nachrichten; mach sie nicht zum Fundament, auf dem dein Server steht.
13+
Die Middleware-Liste ist im Quellcode als **provisorisch** markiert: Signatur und Semantik können
14+
sich in einem 2.x-Minor-Release ändern. Nutze sie zum *Beobachten* (Timing, Logging, Tracing) und
15+
zum *Ablehnen* von Nachrichten; mache sie nicht zum Fundament, auf dem dein Server steht.
1616

17-
`MCPServer` nimmt die Liste beim Erzeugen entgegen (`MCPServer(name, middleware=[...])`) und stellt sie als
18-
`mcp.middleware` bereit; der Low-Level-`Server` stellt dieselbe Liste als `server.middleware` bereit. Das Beispiel
19-
unten verwendet den Low-Level-`Server`; wenn `Server(name, on_call_tool=...)` neu für dich ist, lies zuerst
20-
**[Der Low-Level-Server](low-level-server.md)**.
17+
`MCPServer` nimmt die Liste bei der Konstruktion entgegen (`MCPServer(name, middleware=[...])`) und stellt
18+
sie als `mcp.middleware` bereit; der Low-Level-`Server` stellt dieselbe Liste als `server.middleware`
19+
bereit. Das Beispiel unten verwendet den Low-Level-`Server`; wenn `Server(name, on_call_tool=...)` neu
20+
für dich ist, lies zuerst **[Der Low-Level-Server](low-level-server.md)**.
2121

2222
## Eine Timing-Middleware {#a-timing-middleware}
2323

24-
Ein Server, ein Tool, eine Middleware, die loggt, wie lange jede Nachricht gebraucht hat:
24+
Ein Server, ein Tool, eine Middleware, die loggt, wie lange jede Nachricht gedauert hat:
2525

2626
```python title="server.py" hl_lines="39-45 49"
2727
--8<-- "docs_src/middleware/tutorial001.py"
2828
```
2929

3030
* `ctx` ist derselbe `ServerRequestContext`, den deine Handler erhalten. `ctx.method` ist der rohe
31-
Methoden-String; `ctx.params` sind die rohen Parameter, **vor** jeder Validierung.
32-
* `call_next(ctx)` führt den Rest der Kette aus: Validierung, die Suche nach dem Handler, deinen Handler.
31+
Methoden-String; `ctx.params` sind die rohen Params, **vor** jeder Validierung.
32+
* `call_next(ctx)` führt den Rest der Kette aus: Validierung, die Handler-Suche, deinen Handler.
3333
Gib zurück, was es zurückgegeben hat, und die Response bleibt unverändert.
34-
* Das `try`/`finally` ist Absicht: Auch ein Handler, der eine Exception auslöst, wird gemessen, denn der
35-
Fehler erreicht deine Middleware als Exception aus `call_next`.
34+
* Das `try`/`finally` ist Absicht: Ein Handler, der eine Exception auslöst, wird trotzdem gemessen,
35+
denn der Fehlschlag erreicht deine Middleware als Exception aus `call_next`.
3636
* `server.middleware.append(...)` registriert sie. Die Liste läuft von außen nach innen, also ist
37-
`middleware[0]` die Middleware, die der Leitung am nächsten ist.
37+
`middleware[0]` diejenige, die am nächsten an der Leitung sitzt.
3838

3939
### Ausprobieren {#try-it}
4040

@@ -46,78 +46,82 @@ tools/list took 0.1 ms
4646
tools/call took 0.1 ms
4747
```
4848

49-
Du hast zwei Aufrufe gemacht und drei Zeilen bekommen. Die erste ist `server/discover`: der Request, den der
50-
Client geschickt hat, um die Verbindung aufzubauen, bevor du irgendetwas angefordert hast.
49+
Du hast zwei Aufrufe gemacht und drei Zeilen bekommen. Die erste ist `server/discover`: der Request,
50+
den der Client zum Aufbau der Verbindung geschickt hat, bevor du irgendetwas angefordert hast.
5151

5252
Genau darum geht es. Middleware umschließt **jede** eingehende Nachricht:
5353

5454
* Den Verbindungsaufbau: `server/discover`, oder `initialize` und `notifications/initialized`
55-
auf einer Legacy-Session.
55+
in einer Legacy-Session.
5656
* Jeden Request und jede Benachrichtigung. Bei einer Benachrichtigung gilt `ctx.request_id is None`,
5757
`call_next(ctx)` gibt `None` zurück, und was immer du zurückgibst, wird verworfen.
5858
* Sogar eine Methode, für die der Server keinen Handler hat: `call_next` wirft den
5959
`MCPError(-32601, "Method not found")` *durch* deine Middleware hindurch auf dem Weg zum Client.
6060

61-
## Was innerhalb einer Middleware möglich ist {#what-you-can-do-inside-one}
61+
## Was du in einer Middleware tun kannst {#what-you-can-do-inside-one}
6262

6363
In aufsteigender Reihenfolge danach, wie sehr du zögern solltest:
6464

65-
* **Beobachten.** Messen, zählen, loggen. Das Beispiel oben.
66-
* **Ablehnen.** Löse einen `MCPError` aus, *statt* `call_next(ctx)` aufzurufen, und diese eine Nachricht wird
67-
mit einem JSON-RPC-Fehler beantwortet. Die Verbindung bleibt bestehen; die nächste Nachricht geht durch. So
68-
schränkt ein Server `subscriptions/listen` pro Aufrufer ein:
69-
**[Entscheiden, wer zusehen darf](../handlers/subscriptions.md#deciding-who-may-watch)** auf der Seite
70-
Abonnements geht das Schritt für Schritt durch.
65+
* **Beobachten.** Miss es, zähle es, logge es. Das Beispiel oben.
66+
* **Ablehnen.** Wirf einen `MCPError` *statt* `call_next(ctx)` aufzurufen, und diese eine Nachricht
67+
wird mit einem JSON-RPC-Fehler beantwortet. Die Verbindung bleibt bestehen; die nächste Nachricht
68+
geht durch. So beschränkt ein Server `subscriptions/listen` pro Aufrufer:
69+
**[Entscheiden, wer zusehen darf](../handlers/subscriptions.md#deciding-who-may-watch)** auf der
70+
Seite Abonnements führt es Schritt für Schritt vor.
7171
* **Umschreiben.** `ctx` ist eine Dataclass: `await call_next(dataclasses.replace(ctx, params=...))`
72-
reicht dem Rest der Kette andere Parameter weiter, als der Client geschickt hat. Tu das niemals bei
73-
`initialize`: Das Ergebnis, das der Client zurückbekommt, wird aus deinen umgeschriebenen Parametern gebaut,
74-
aber der Server legt seinen Verbindungszustand anhand der ursprünglichen Parameter von der Leitung fest. Beide
75-
Seiten können den Handshake abschließen und sich dabei uneinig sein, was sie ausgehandelt haben.
76-
* **Beantworten.** Gib ein Ergebnis zurück, ohne `call_next(ctx)` aufzurufen, und es geht als deine Response
77-
an den Client. `call_next` reicht dir die fertige Form für die Leitung, und die Pipeline bessert nie nach,
78-
was du zurückgibst – der ganze Umschlag gehört also dir: Auf einer Verbindung der 2026er-Generation schließt
79-
das den `_meta`-Stempel `serverInfo` ein, den das SDK an Handler-Ergebnisse anhängt, an deine aber nicht.
72+
reicht dem Rest der Kette andere Params weiter, als der Client geschickt hat. Tu das nie mit
73+
`initialize`: Das Ergebnis, das der Client zurückbekommt, wird aus deinen umgeschriebenen Params
74+
gebaut, aber der Server legt seinen Verbindungszustand anhand der ursprünglichen Params von der
75+
Leitung fest. Beide Seiten können den Handshake beenden und sich dabei uneinig sein, was sie
76+
ausgehandelt haben.
77+
* **Antworten.** Gib ein Ergebnis zurück, ohne `call_next(ctx)` aufzurufen, und es geht als deine
78+
Response an den Client. `call_next` reicht dir die fertige Form für die Leitung, und die Pipeline
79+
bessert nie nach, was du zurückgibst – der ganze Umschlag gehört also dir: Auf einer Verbindung der
80+
2026er-Generation gehört dazu der `serverInfo`-Stempel in `_meta`, den das SDK an Handler-Ergebnisse
81+
anfügt, an deine aber nicht.
8082

8183
!!! check
82-
`initialize` gehört zu den Dingen, die Middleware umschließt, und es ist der *einzige* Hook, den du
83-
dafür bekommst. Versuchst du, es mit `add_request_handler` zu übernehmen, lehnt das SDK ab:
84+
`initialize` gehört zu dem, was Middleware umschließt, und es ist der *einzige* Hook, den du
85+
dafür bekommst. Versuchst du, es mit `add_request_handler` zu übernehmen, weigert sich das SDK:
8486

8587
```text
8688
ValueError: 'initialize' is handled by the server runner and cannot be overridden;
8789
use Server.middleware to observe or wrap initialization
8890
```
8991

9092
!!! warning
91-
`initialize` wird inline verarbeitet: Der Server liest keine weiteren eingehenden Nachrichten, bis deine
92-
Middleware-Kette zurückkehrt. Auf einen Request vom Server an den Client zu warten (`ctx.session.send_request(...)`,
93-
eine Elicitation (Rückfrage bei der Person am Host)), während `initialize` verarbeitet wird, führt daher zu
94-
einem **Deadlock der Verbindung**: Die Response, auf die du wartest, kann nie gelesen werden.
95-
Fire-and-forget-Benachrichtigungen sind in Ordnung.
93+
`initialize` wird inline behandelt: Der Server liest keine weiteren eingehenden Nachrichten, bis
94+
deine Middleware-Kette zurückkehrt. Auf einen Server-zu-Client-Request zu warten
95+
(`ctx.session.send_request(...)`, eine Elicitation – Rückfrage bei der Person am Host), während
96+
`initialize` behandelt wird, **blockiert die Verbindung** daher **dauerhaft** (Deadlock): Die
97+
Response, auf die du wartest, kann nie gelesen werden. Benachrichtigungen nach dem
98+
Fire-and-forget-Prinzip sind in Ordnung.
9699

97100
## Die eine Middleware, die standardmäßig aktiv ist {#the-one-middleware-that-ships-on-by-default}
98101

99-
Das SDK liefert genau eine Middleware mit, und sie steht bereits auf der Liste deines Servers: die, die für
100-
jede Nachricht einen OpenTelemetry-Span erzeugt. Du hängst sie nicht an, und meistens denkst du gar nicht
101-
an sie. Sie tut nichts, bis du einen Exporter installierst, und sie hat ihre eigene Seite:
102+
Das SDK liefert genau eine Middleware mit, und sie steht bereits auf der Liste deines Servers: die,
103+
die für jede Nachricht einen OpenTelemetry-Span ausgibt. Du hängst sie nicht an, und meistens denkst
104+
du gar nicht an sie. Sie tut nichts, bis du einen Exporter installierst, und sie hat ihre eigene Seite:
102105
**[OpenTelemetry](../run/opentelemetry.md)**.
103106

104107
!!! info
105-
Wenn du schon ASGI-Middleware geschrieben hast, kennst du diese Form bereits. Starlettes
106-
`(scope, receive, send)` wurde zu `(ctx, call_next)`, und es läuft *nach* dem Transport, auf
107-
der dekodierten Nachricht statt auf dem rohen HTTP-Request. Beides lässt sich kombinieren: Starlette-Middleware
108-
auf `streamable_http_app()` sieht HTTP; diese hier sieht MCP.
108+
Wenn du schon ASGI-Middleware geschrieben hast, kennst du diese Form bereits. Aus Starlettes
109+
`(scope, receive, send)` wurde `(ctx, call_next)`, und sie läuft *nach* dem Transport, auf der
110+
dekodierten Nachricht statt auf dem rohen HTTP-Request. Beide lassen sich kombinieren:
111+
Starlette-Middleware auf `streamable_http_app()` sieht HTTP; diese hier sieht MCP.
109112

110113
## Zusammenfassung {#recap}
111114

112-
* Eine Middleware ist `async (ctx, call_next) -> result`, übergeben als `MCPServer(middleware=[...])` (oder
113-
an `mcp.middleware` angehängt) und beim Low-Level-`Server` an `server.middleware` angehängt.
114-
* Sie umschließt **jede** eingehende Nachricht (`server/discover`, `initialize`, Requests, Benachrichtigungen,
115-
unbekannte Methoden) und läuft von außen nach innen.
115+
* Eine Middleware ist `async (ctx, call_next) -> result`, übergeben als `MCPServer(middleware=[...])`
116+
(oder an `mcp.middleware` angehängt) und beim Low-Level-`Server` an `server.middleware` angehängt.
117+
* Sie umschließt **jede** eingehende Nachricht (`server/discover`, `initialize`, Requests,
118+
Benachrichtigungen, unbekannte Methoden) und läuft von außen nach innen.
116119
* An `ctx.request_id is None` unterscheidest du eine Benachrichtigung von einem Request.
117-
* Löse eine Exception aus, statt `call_next` aufzurufen, um eine einzelne Nachricht abzulehnen; die Verbindung überlebt.
118-
* Das eigene OpenTelemetry-Tracing des SDK ist ebenfalls eine Middleware, die schon auf der Liste steht. Siehe
120+
* Wirf eine Exception, statt `call_next` aufzurufen, um eine einzelne Nachricht abzulehnen; die
121+
Verbindung überlebt.
122+
* Das OpenTelemetry-Tracing des SDK ist ebenfalls eine Middleware und steht schon auf der Liste. Siehe
119123
**[OpenTelemetry](../run/opentelemetry.md)**.
120-
* Die gesamte Oberfläche ist provisorisch. Beobachte damit; baue nicht darauf.
124+
* Die ganze Oberfläche ist provisorisch. Beobachte damit; baue nicht darauf.
121125

122-
Das ist alles, was einen Request umschließt. **[Autorisierung](../run/authorization.md)** entscheidet, ob der Request
123-
überhaupt laufen darf.
126+
Das ist alles, was einen Request umschließt. **[Autorisierung](../run/authorization.md)** entscheidet,
127+
ob der Request überhaupt laufen darf.

i18n/ja/notices.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
translation:
33
sections: [aff1b3e872b7876a, 4d80558ad052d586, 0bb81f1e62062d26, d5c35dcec50156bc]
4-
inputs: 8113dbdcdf8cc017
54
tool: 1
65
---
76
# 翻訳に関するお知らせ {#translation-notices}

i18n/ja/pages/advanced/apps.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
translation:
33
sections: [0355618e5f4d5fe4, 1821eaf50f2d0b64, 82e0b28ebd3abf5a, 8ac39614c094f2d0, dab6ff945501ab2a, bd5565c3b2d4f959, 96819ce3d63a0487]
4-
inputs: 79c13fd594fb834d
54
tool: 1
65
---
76
# MCP Apps {#mcp-apps}

i18n/ja/pages/advanced/extensions.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
translation:
33
sections: [05891e7cc1938a13, b3c01a6af28c51ee, 7ffc91f5e38bdfe0, 717d3f235a8333a7, f471a13b2fe5d737, ed6af2df4b656dff]
4-
inputs: 8113dbdcdf8cc017
54
tool: 1
65
---
76
# 拡張機能 {#extensions}

i18n/ja/pages/advanced/index.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
translation:
33
sections: [ca6988b7503cd2d3]
4-
inputs: 79c13fd594fb834d
54
tool: 1
65
---
76
# 高度なトピック {#advanced}

i18n/ja/pages/advanced/low-level-server.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
---
22
translation:
33
sections: [2c79b6338e09b7ac, 7edc43b3fae11314, 1086e77ce561cd7f, a3f71823df5efc31, 9fc7109f72201cae, 7bf25983df655b66, 6330e1f4c6029683, 2f1749c8c133fa1c, b3530fcf4d11fd56, ebc33704fbd74262, cd0e9c933350390e]
4-
inputs: 79c13fd594fb834d
54
tool: 1
65
---
76
# 低レベルの Server {#the-low-level-server}

0 commit comments

Comments
 (0)