diff --git a/.gitattributes b/.gitattributes index 0ab3744850..8fc271c438 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ # Generated uv.lock linguist-generated=true +i18n/*/pages/** linguist-generated=true +i18n/*/state.json linguist-generated=true diff --git a/.github/ISSUE_TEMPLATE/translation.yaml b/.github/ISSUE_TEMPLATE/translation.yaml new file mode 100644 index 0000000000..f1dbcf1036 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/translation.yaml @@ -0,0 +1,57 @@ +name: 🌐 Translation problem +description: Report a wrong, awkward, or misleading passage in a translated docs page +labels: ["translation"] + +body: + - type: markdown + attributes: + value: | + The translated docs are machine-generated from the English pages; https://py.sdk.modelcontextprotocol.io/translations/ explains how. + Fixes never go into the translated text directly. They go into that language's glossary or style guide under `i18n/`, so you can also open a PR there instead of an issue. + + - type: dropdown + id: language + attributes: + label: Language + options: + - Simplified Chinese (zh-CN) + - Japanese (ja) + - Korean (ko) + - Brazilian Portuguese (pt-BR) + validations: + required: true + + - type: input + id: page + attributes: + label: Page URL + description: The translated page where you found the problem. + placeholder: https://py.sdk.modelcontextprotocol.io/ja/servers/tools/ + validations: + required: true + + - type: textarea + id: passage + attributes: + label: The passage + description: Quote the translated text that's wrong, and the English it corresponds to if you have it. + validations: + required: true + + - type: textarea + id: problem + attributes: + label: What's wrong, or how it should read + description: A wrong term, awkward phrasing, meaning that drifted from the English, tone that's off. If you know the better rendering, give it. + validations: + required: true + + - type: dropdown + id: native-speaker + attributes: + label: Are you a native or fluent speaker of this language? + options: + - "Yes" + - "No" + validations: + required: true diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 334ba818c8..c1d9f288bc 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -12,6 +12,8 @@ on: # docs pages include their code blocks from these files via `--8<--`, so a # change here changes the rendered site even when no .md file moves. - docs_src/** + # the translated pages and the language registry feed the language sites + - i18n/** - mkdocs.yml - src/mcp/** - src/mcp-types/** diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml index 6f9ec2cc34..40d4c1dc5c 100644 --- a/.github/workflows/docs-preview.yml +++ b/.github/workflows/docs-preview.yml @@ -21,6 +21,7 @@ on: paths: - docs/** - docs_src/** + - i18n/** - mkdocs.yml - scripts/docs/** - pyproject.toml @@ -137,7 +138,17 @@ jobs: # /preview-docs, still build with MkDocs. Both arms must write the site # to site/. Keep the detection in sync with build_site() in # scripts/build-docs.sh. - - run: | + # + # DOCS_SITE_URL is the preview's Cloudflare branch-alias host (deploy + # publishes to `--branch=pr-`, served at pr-..pages.dev), + # so the absolute links the build bakes (language switcher, language + # sites' links into the English API reference) point at the preview + # instead of production; empty when no Pages project is configured, which + # makes build.sh fall back to the production site_url. + - env: + DOCS_SITE_URL: >- + ${{ vars.CLOUDFLARE_PAGES_PROJECT && format('https://pr-{0}.{1}.pages.dev', needs.authorize.outputs.pr_number, vars.CLOUDFLARE_PAGES_PROJECT) || '' }} + run: | if [ -f scripts/docs/build.sh ]; then bash scripts/docs/build.sh else diff --git a/.gitignore b/.gitignore index 2e788e71d8..ce390c689e 100644 --- a/.gitignore +++ b/.gitignore @@ -144,10 +144,13 @@ venv.bak/ # documentation /site /.worktrees/ -# Generated at build time by scripts/docs/ (the API reference tree and the -# concrete Zensical config spliced from mkdocs.yml). +# Generated at build time by scripts/docs/ (the API reference tree, the +# concrete Zensical configs spliced from mkdocs.yml, and the staged +# per-language docs trees). /docs/api/ +/.build/ /mkdocs.gen.yml +/mkdocs.*.gen.yml # mypy .mypy_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 321b60bc52..f9f32ce7a4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,6 +25,10 @@ repos: "/tool/markdown/lint", ] types: [markdown] + # Machine-translated pages are generated artefacts: corrections flow + # through i18n//{instructions.md,glossary.json}, never through + # hand or linter edits to the pages themselves. + exclude: ^i18n/[^/]+/pages/ - repo: local hooks: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b0fb9fa57b..52717b0df6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,6 +126,10 @@ pre-commit run --all-files - Add type hints to all functions - Include docstrings for public APIs +## Documentation and Translations + +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//`. Never edit the generated pages under `i18n//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. + ## Pull Requests By the time you open a PR, the "what" and "why" should already be settled in an issue. This keeps reviews focused on implementation. diff --git a/docs/translations.md b/docs/translations.md new file mode 100644 index 0000000000..df529e043d --- /dev/null +++ b/docs/translations.md @@ -0,0 +1,24 @@ +# Translations + +This documentation is written in English. To make it useful to more people, we also publish it in a few other languages. Those editions are machine-translated, and this page explains what that means for you and how to help improve them. + +## What's available + +Translated documentation is currently a **preview**: it covers a growing subset of the pages, published in four languages — Simplified Chinese, Japanese, Korean and Brazilian Portuguese. Pick one from the language switcher at the top of any page. + +Every translated page opens with a note saying it was machine-translated and linking to its English original. The API reference is not translated: every language site links to the single English one. + +## English is the source of truth + +If a translated page and its English original disagree, the English page is correct. Two situations are called out on the page itself: + +- A page that hasn't been translated yet shows the English text, with a note saying so. +- A page whose English original changed after it was translated carries a warning that it may be behind, until the translation catches up. + +## How the translations are made + +Translated pages are generated by a tool in this repository from the English pages under `docs/`, guided by two human-written inputs per language: a style guide (register, tone, typography, how to handle jokes and idioms) and a glossary (which terms stay in English, and the required and forbidden renderings for the rest). The generated text is never edited by hand. Every improvement goes into those inputs instead, so it survives the next time the pages are regenerated. + +## Reporting a translation problem + +Found a wrong term, an awkward sentence, or a translation that says something the English doesn't? [Open a translation issue](https://github.com/modelcontextprotocol/python-sdk/issues/new?template=translation.yaml) with the language, the page and the passage; reports from native speakers are especially valuable, and maintainers track these with the `translation` label. If you know the fix, propose it directly as a pull request against that language's style guide or glossary under [`i18n/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/i18n) — the correction then shows up on every affected page the next time the translations are regenerated. Problems with the English text itself are fixed in the pages under `docs/`, like any other documentation change. diff --git a/i18n/README.md b/i18n/README.md new file mode 100644 index 0000000000..57df0b9abe --- /dev/null +++ b/i18n/README.md @@ -0,0 +1,72 @@ +# Documentation translations + +The English pages under `docs/` are the source of truth. This directory holds +what steers their machine translation (human-authored) and the generated +result; the public-facing explanation is [`docs/translations.md`](../docs/translations.md). + +- `languages.yml` — the language registry (one entry per translated site, + served at `//`), the pages that stay English, the model IDs, and the + English text of the notices staged onto every translated page. +- `general-prompt.md` — the translation rules shared by every language. +- `/instructions.md` — register, voice, humour and typography for one + language. Human-authored. +- `/glossary.json` — the termbase for one language: terms that stay in + English, required renderings, and banned ones (`"enforce": true` makes a ban + a hard check). Human-authored. +- `/pages/` and `/state.json` — generated: the translated pages and + the record of what each was made from (English content and section hashes, prompt + inputs, model, timestamp), plus the translated sidebar labels and notices. Never + edited by hand. + +## The tool + +`scripts/docs/translations.py` (run from the repository root): + +```bash +uv run --frozen --group docs python scripts/docs/translations.py status # missing/outdated/current per language +uv run --frozen --group docs python scripts/docs/translations.py translate --lang ja # translate what is missing or outdated +uv run --frozen --group docs python scripts/docs/translations.py stage --lang ja # the docs tree the ja site builds from +``` + +`translate` calls the Claude API (set `ANTHROPIC_API_KEY` or +`ANTHROPIC_AUTH_TOKEN`). It re-translates only the `##` sections whose English +changed and carries the rest of the page over byte-for-byte from the previous +translation, then gates the result: heading anchors and code blocks are +re-imposed from the English, the page's structure (headings, code fences, +links, admonitions, glossary rules) is checked with the findings fed back for +another attempt, and a stronger model reviews the meaning against the English. +`--pages a.md b.md` narrows a run (a page that is already current comes back +unchanged), `--fresh` re-translates from scratch instead of updating (every +page when no `--pages` are given — the way to redo a language after a model +change), `--dry-run` shows what it would do, and `--no-verify` skips the +meaning review. A page that failed its gates keeps its previous translation and +the run exits non-zero; translations whose English page has left the nav are +deleted. Everything else is offline; `scripts/docs/build.sh` runs `stage` for +each language and builds it at `site//`. + +## Correcting a translation + +Never edit a file under `/pages/` — the next run overwrites it. Fix the +input instead: a wrong term goes into `glossary.json`, a recurring style or +register problem into `instructions.md`, and ambiguous English into the page +under `docs/`. Editing a glossary or instructions marks that language's pages +outdated, so the next `translate` run regenerates them with the fix in place. +Readers report problems through the "Translation problem" issue form. + +## Staleness + +A page is *current* when the English content and the prompt inputs it was +translated from are unchanged, *outdated* otherwise, and *missing* when it has +no translation. The build serves an outdated translation with a warning +notice, unless the English page's links or heading anchors moved under it — +then the English page is served until the next `translate` run refreshes it. +Untranslated and excluded pages are served in English with a notice. + +## Adding a language + +Add an entry to `languages.yml` (`code`, native `name`; `theme_language` and +`hreflang` default to the code and must be values the theme knows), write +`/instructions.md` and `/glossary.json` (start from an existing +language), then run `translate --lang ` and commit the generated `pages/` +and `state.json`. Also name the language in `docs/translations.md` and the +"Translation problem" issue form's dropdown. diff --git a/i18n/general-prompt.md b/i18n/general-prompt.md new file mode 100644 index 0000000000..ddbbd71a9d --- /dev/null +++ b/i18n/general-prompt.md @@ -0,0 +1,59 @@ +# Translation rules + +You are translating a page of the MCP Python SDK documentation from English into the target language named in the language instructions that follow. The readers are software developers using the SDK. + +## Your role + +- Write natural, native-quality prose in the target language. The page should read as if a developer who is a native speaker wrote it, not as a translation. +- Keep the meaning exact. Do not add claims, drop caveats, reorder steps, or change the strength of a requirement (must / should / may). +- Follow the language instructions and the glossary strictly. Where the two disagree, the glossary wins. +- Translate the whole page. Never summarise, abridge, or leave a placeholder such as "translation continues below". + +## Never translate + +Copy the following byte-for-byte from the English source: + +- Fenced code blocks: the fence markers, the info string, and every line inside them, including code comments. +- Inline code spans (text between backticks). +- URLs and link destinations, including `#fragment` anchors, and image paths. +- HTML tags and their attribute values. +- Front matter keys (the `key:` part of each front matter line). +- Snippet-include lines containing `--8<--`. +- Code-annotation markers such as `# (1)!`. +- Heading anchor attributes: the `{#some-id}` at the end of a heading (it may also be written with spaces, `{ #some-id }`; copy it exactly as it appears). +- The syntax markers for admonitions, collapsible blocks and content tabs (`!!!`, `???`, `???+`, `///`, `===`) and the block-type keyword that follows them (`note`, `tip`, `warning`, ...). +- Footnote labels (`[^1]`), abbreviation definitions (`*[HTML]: ...`) and emoji shortcodes (`:smile:`). +- Mermaid diagram source inside `mermaid` fences. + +Do translate the human-language text around those elements: prose, headings, link text, image alt text, table cells, list items, admonition titles (the quoted text after `!!! type`) and bodies, and content-tab labels (the quoted text after `===`). + +## Preserve the structure exactly + +The translation must have the same shape as the English source, block for block: + +- The same headings, at the same levels, in the same order, each ending in the same `{#anchor}` attribute as the source. +- The same number and type of admonitions, collapsible blocks and content-tab groups, in the same order. +- The same tables, with the same number of rows and columns. +- The same lists (same nesting, same number of items) and the same code fences (same count, same info strings, identical contents). +- The same links and images, in the same order. Never add, remove or merge a link. +- The same footnotes, and the same blank lines separating blocks. + +Do not add explanatory notes, translator's remarks or extra examples. + +## Links and anchors + +- Keep every link destination exactly as written in the source, whether it is an absolute URL, a relative path such as `../servers/tools.md`, or a bare `#anchor`. Only the link text is translated. +- Do not add anchors the source does not have, and never rewrite a fragment: heading anchors are pinned in the English source, so the same `#id` is valid on every language site. +- Preserve the link syntax the source uses (Markdown `[text](target)` or HTML ``). + +## Updating an existing translation + +When the request includes a previous translation of the page and marks which sections of the English page changed: + +- Outside the changed sections, reproduce the previous translation verbatim. Do not rephrase, "improve" or re-punctuate text whose English has not changed. +- Inside the changed sections, translate the new English following all of the rules above, and keep terminology, register and tone consistent with the surrounding unchanged text. +- A section is the page's front matter, the text before the first second-level heading, or one second-level heading (`##`) together with everything under it up to the next. + +## Output + +Return only the translated Markdown document, from its first line to its last. Do not add a preamble, a summary or any commentary, and do not wrap the document in a code fence. diff --git a/i18n/ja/glossary.json b/i18n/ja/glossary.json new file mode 100644 index 0000000000..ba38f88367 --- /dev/null +++ b/i18n/ja/glossary.json @@ -0,0 +1,295 @@ +{ + "version": 1, + "keep_in_source_language": [ + "MCP", + "Model Context Protocol", + "MCPServer", + "FastMCP", + "ClientSession", + "Context", + "ctx", + "stdio", + "Streamable HTTP", + "SSE", + "JSON-RPC", + "JSON", + "OAuth", + "PKCE", + "JWT", + "CIMD", + "HTTP", + "HTTPS", + "TLS", + "CORS", + "URI", + "URL", + "ASGI", + "WebSocket", + "API", + "SDK", + "CLI", + "IDE", + "LLM", + "SEP", + "RFC", + "Python", + "TypeScript", + "Node.js", + "PyPI", + "Pydantic", + "Starlette", + "FastAPI", + "uvicorn", + "httpx", + "anyio", + "asyncio", + "trio", + "pytest", + "OpenTelemetry", + "Inspector", + "Claude", + "GitHub", + "VS Code", + "Windows", + "macOS", + "Linux", + "llms.txt", + "2026-07-28", + "2025-11-25", + "2025-06-18", + "2025-03-26" + ], + "terms": [ + { + "source": "tool", + "target": "ツヌル", + "note": "MCP protocol noun (a server exposes tools). Standard rendering. Wire identifiers such as `tools/call` and `tools/list` are code and stay Latin.", + "avoid": [], + "enforce": false + }, + { + "source": "resource", + "target": "リ゜ヌス", + "note": "MCP protocol noun, and also the general noun (a pool acquired in a lifespan is still リ゜ヌス). Standard rendering. Never 資源, which is the natural-resources sense; `resources/read` stays Latin.", + "avoid": ["資源"], + "enforce": true + }, + { + "source": "prompt", + "target": "プロンプト", + "note": "The MCP feature (a reusable prompt a server exposes) and the everyday word; プロンプト in both senses. Standard rendering. `prompts/get` stays Latin.", + "avoid": [], + "enforce": false + }, + { + "source": "sampling", + "target": "サンプリング", + "note": "The (deprecated) client feature that lets a server borrow the client's model. Standard rendering. Never 暙本抜出, which is statistical sampling and the wrong sense; the `sampling` capability key and `sampling/createMessage` stay Latin.", + "avoid": ["暙本抜出"], + "enforce": true + }, + { + "source": "roots", + "target": "ルヌト", + "note": "The (deprecated) client feature listing workspace folders. Provisional pending native review: ルヌト also spells \"route\" and \"root path\", so gloss the English on first use per page — ルヌトroots. Never ルヌツ (ancestry/origins). A `Root` object in code font stays Latin.", + "avoid": ["ルヌツ"], + "enforce": true + }, + { + "source": "elicitation", + "target": "゚リシテヌション", + "note": "OPEN QUESTION for native review: there is no established Japanese term for the server asking the user a question mid-request. Provisionally pinned to the transliteration ゚リシテヌション, glossed with the English on its first appearance per page — ゚リシテヌションelicitation. Do not substitute 誘導 or 匕き出し unless review settles on one. `elicitation/create` and the `Elicit` class stay Latin.", + "avoid": [], + "enforce": false + }, + { + "source": "capability", + "target": "ケむパビリティ", + "note": "A negotiated protocol capability (what a client or server declared it supports). Provisional pending native review: ケむパビリティ rather than the general-purpose 機胜 (feature) or 胜力 (ability). The `capabilities` field and keys such as `sampling.tools` stay Latin.", + "avoid": [], + "enforce": false + }, + { + "source": "transport", + "target": "トランスポヌト", + "note": "The connection mechanism (\"every standard transport\" → 暙準のトランスポヌト). Standard rendering; never 茞送 (freight transport) or 茞送手段. The transport names stdio, Streamable HTTP and SSE stay in English.", + "avoid": ["茞送"], + "enforce": true + }, + { + "source": "session", + "target": "セッション", + "note": "An MCP session (the negotiated connection state). Standard rendering; never the coinage 䌚期. `session` objects in code font stay Latin.", + "avoid": ["䌚期"], + "enforce": false + }, + { + "source": "handler", + "target": "ハンドラヌ", + "note": "The tool, resource or prompt function you register (nav section \"Inside your handler\" → ハンドラヌの䞭で). Standard word; the long-vowel spelling ハンドラヌ (not ハンドラ) follows the katakana rule in instructions.md and is provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "dependency", + "target": "䟝存関係", + "note": "The SDK's parameter-injection feature (the \"Dependencies\" page → 䟝存関係). Provisional pending native review: the pattern name \"dependency injection\" is customarily 䟝存性の泚入, so a page may use that phrase for the pattern while individual dependencies are 䟝存関係. The `Resolve` marker class stays Latin.", + "avoid": [], + "enforce": false + }, + { + "source": "client", + "target": "クラむアント", + "note": "An MCP client, and the client side of a connection. Standard rendering; never 顧客 (a customer). The `Client` class name stays Latin in code font.", + "avoid": ["顧客"], + "enforce": true + }, + { + "source": "server", + "target": "サヌバヌ", + "note": "An MCP server (the program you build). Standard rendering with the long-vowel mark — サヌバヌ, never サヌバ (see instructions.md). The low-level `Server` class stays Latin in code font.", + "avoid": [], + "enforce": false + }, + { + "source": "host", + "target": "ホスト", + "note": "The MCP host: the application that embeds the client and drives the model, and also a network host. Standard rendering in both senses; never 宿䞻 (a biological host).", + "avoid": ["宿䞻"], + "enforce": true + }, + { + "source": "context", + "target": "コンテキスト", + "note": "The generic lower-case word (\"provide context to LLMs\" → LLM にコンテキストを提䟛する). Provisional pending native review: pin one spelling per corpus — コンテキスト, not コンテクスト. The capitalised `Context` is the SDK object injected as `ctx`; it is on the keep-in-source list and stays Latin in prose (\"The Context\" → Context).", + "avoid": ["コンテクスト"], + "enforce": false + }, + { + "source": "resolver", + "target": "リゟルバヌ", + "note": "The function attached to a parameter with `Resolve(...)` that computes or asks for its value. Provisional pending native review: the loanword リゟルバヌ, not 解決噚. The `Resolve` class stays Latin.", + "avoid": ["解決噚"], + "enforce": false + }, + { + "source": "lifespan", + "target": "ラむフスパン", + "note": "The server's startup/shutdown scope (the \"Lifespan\" page, as in the ASGI lifespan). Provisional pending native review: the loanword ラむフスパン, not 寿呜 (the biological sense). Not enforced, because the neighbouring English word \"lifetime\" (\"for the lifetime of the host app\") can legitimately render as 寿呜 in the same block. The `lifespan` parameter name stays Latin in code font.", + "avoid": ["寿呜"], + "enforce": false + }, + { + "source": "deprecated", + "target": "非掚奚", + "note": "Advisory status: still works, scheduled for removal later — 非掚奚, not 廃止 (which reads as already removed); \"removed\" is 削陀. \"Deprecation warning\" → 非掚奚の譊告; the `MCPDeprecationWarning` class stays Latin. Provisional pending native review.", + "avoid": ["廃止"], + "enforce": false + }, + { + "source": "back-channel", + "target": "バックチャネル", + "note": "The server-to-client request channel that exists only on legacy connections. Provisional coinage pending native review: gloss the English on first use per page — バックチャネルback-channel. Not the older spelling バックチャンネル.", + "avoid": ["バックチャンネル"], + "enforce": false + }, + { + "source": "wire", + "target": "通信路", + "note": "The corpus's light metaphor for the byte stream between client and server (\"stdout is the wire\" → stdout が通信路そのものです; \"invisible on the wire\" → 通信䞊には珟れたせん; \"the JSON on the wire\" → 実際に送受信される JSON). Provisional pending native review. Never a literal 電線 or ワむダヌ.", + "avoid": ["電線", "ワむダヌ"], + "enforce": false + }, + { + "source": "era", + "target": "䞖代", + "note": "\"Protocol era\" (\"a 2025-era client\", \"whatever era the client speaks\") → プロトコルの䞖代, 2025 幎䞖代のクラむアント. Provisional pending native review; not the literal 時代.", + "avoid": ["時代"], + "enforce": false + }, + { + "source": "legacy", + "target": "レガシヌ", + "note": "\"A legacy connection/client\" = one negotiated at spec version 2025-11-25 or earlier → レガシヌ接続, レガシヌクラむアント (prenominal loanword). Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "multi-round-trip", + "target": "マルチラりンドトリップ", + "note": "The 2026-07-28 request pattern (\"Multi-round-trip requests\" → マルチラりンドトリップリク゚スト); a single \"round trip\" → ラりンドトリップ or 埀埩 by context. Provisional coinage pending native review: gloss the English on first use per page — マルチラりンドトリップmulti-round-trip. The abbreviation MRTR stays Latin.", + "avoid": [], + "enforce": false + }, + { + "source": "handshake", + "target": "ハンドシェむク", + "note": "The initialization handshake (\"the classic handshake\" → 埓来のハンドシェむク). The established loanword; never the literal 握手. Provisional pending native review.", + "avoid": ["握手"], + "enforce": true + }, + { + "source": "request", + "target": "リク゚スト", + "note": "A JSON-RPC or HTTP request (\"the initialize request\" → 初期化リク゚スト); the verb is リク゚ストする or 芁求する by context. `Request` types in code font stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "response", + "target": "レスポンス", + "note": "A JSON-RPC or HTTP response; `Response` types in code font stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "callback", + "target": "コヌルバック", + "note": "Client callbacks and OAuth redirect callbacks alike; parameter names such as `sampling_callback` stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "decorator", + "target": "デコレヌタヌ", + "note": "The Python decorators the SDK is built on; `@mcp.tool()` and its siblings are code and stay untouched. Long-vowel spelling per instructions.md. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "type hint", + "target": "型ヒント", + "note": "Python type hints (\"from your type hints\" → 型ヒントから). Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "argument", + "target": "匕数", + "note": "A call argument; the declared parameter is パラメヌタヌ (see the katakana rule in instructions.md). Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "return value", + "target": "戻り倀", + "note": "A function's return value; the `return` keyword and return annotations are code. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "exception", + "target": "䟋倖", + "note": "A raised Python exception (\"raises an exception\" → 䟋倖を送出する); exception class names stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "async", + "target": "非同期", + "note": "The prose adjective (\"the async runtime\" → 非同期ランタむム, \"an async callback\" → 非同期コヌルバック); the `async` and `await` keywords in code font stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + } + ] +} diff --git a/i18n/ja/instructions.md b/i18n/ja/instructions.md new file mode 100644 index 0000000000..eaa8d02353 --- /dev/null +++ b/i18n/ja/instructions.md @@ -0,0 +1,168 @@ +# Japanese (ja) — translation instructions + +Target language: Japanese (日本語), directory and URL code `ja`, page language +tag `ja`. This file is sent verbatim with every translation request for this +language, on top of the shared rules in `../general-prompt.md`. The +termbase in `glossary.json` is sent alongside it and wins any terminology +conflict with this file. + +## 1. Register + +Write body prose in the polite です・たす form (敬䜓), consistently, on every +page — tutorials, reference tables, admonitions and troubleshooting entries +alike. + +- Never mix in だ・である (垞䜓) sentence endings within body text, and do + not escalate into honorifics (尊敬語・謙譲語): 䜿うずきは, not + お䜿いいただく際には. +- Headings, table headers, content-tab labels and other UI-like fragments + are noun phrases (䜓蚀止め) or the plain dictionary form of a verb, never + です・たす: "Run it" → 実行する or 実行方法, "The Context" → Context, + "Handling errors" → ゚ラヌの凊理. A heading phrased as a question in + English may stay a question in the plain form: "Where does this go?" → + これはどこに眮くべきか. +- Instructions to the reader: 〜しおください for a step to perform, + 〜したす / 〜できたす for describing what code does, 〜しないでください + for prohibitions. Prefer 〜です over 〜になりたす / 〜ずなりたす when both + are grammatical. +- The reader is never named. Do not translate "you" / "your" as あなた, + あなたの, 君, ナヌザヌ様: drop the subject, which Japanese does + naturally, or restructure the sentence. "You can pass a schema" → + スキヌマを枡せたす. Where a subject is unavoidable, name the role — + サヌバヌ, クラむアント, ツヌル, 呌び出し偎 — never a pronoun. "Your server" + is サヌバヌ, or 自分のサヌバヌ / 䜜成䞭のサヌバヌ only when the ownership + is the point. +- One page, one register: a page that drifts between です・たす and である, + or that reintroduces あなた, is wrong even when each sentence is + acceptable on its own. + +## 2. Voice + +The English source is warm, direct and confident: short sentences, second +person, and the occasional one-line payoff ("That's the whole API."). Carry +that voice into natural Japanese; do not flatten it into formality, and do not +mirror the English word for word. + +- Guide, don't lecture. The reader should feel accompanied by a knowledgeable + colleague, not addressed by a notice. Directness comes from concrete verbs + and plain word order; warmth comes from the polite register itself, + considerate connectives (たず, ここでは, なお) and the occasional + 〜しおみたしょう / 〜しおみおください for an encouraging aside. +- Keep the short payoff sentences short: "That's the whole API." → + API はこれだけです。 — not a formal summary sentence. +- Split long English sentences; follow Japanese rhythm rather than the + source's clause structure, but never merge, drop or reorder the technical + claims themselves. +- Anti-patterns — the stiff, legalistic translationese that Japanese + technical translations drift into by default: no 〜なのである / + 〜のである; no nominalisation chains (〜の実斜を行うこずにより → + 〜するず); no boilerplate such as 〜するものずしたす or 〜が求められたす + where 〜しおください is meant; no stacked ただし / なお clauses; no + needlessly formal kanji where kana reads more easily (できる not 出来る). + The opposite over-correction is also wrong: no よ endings, no + buddy-casual tone, and ね at most sparingly in tutorial prose, never in + reference pages. + +Example — English: "You don't construct it and you don't configure it. You +ask for it." + +- Not this (translationese): 利甚者がその構築および構成を実斜する必芁はなく、 + 芁求のみを行うものずする。 +- Not this either (pronoun + casual): あなたはそれを構築しないし、蚭定もしない。 + 芁求するだけだよ。 +- This: 自分で組み立おる必芁も、蚭定する必芁もありたせん。芁求するだけです。 + +## 3. Humour and idioms + +- Translate the intent of a joke, aside or idiom, never its words. Recast + it as a friendly plain sentence carrying the same information; if a + lighthearted phrase carries no information at all, keep the sentence brief + and natural rather than inventing a Japanese joke. Never drop the technical + content around it. +- Recurring English tags get fixed renderings: "X has the whole story" / + "The whole story is in X" → 詳しくは X を参照しおください; + "That's it. It's just Python." → これだけです。ただの Python です。 +- Idioms take the plain meaning, not the picture: "Out of the box the app + answers **only** requests addressed to localhost." → デフォルトでは、この + アプリは localhost 宛おのリク゚ストに**だけ**応答したす。 — not the literal + 箱から出しおすぐ. +- Exclamation marks: drop them by default. Keep a single full-width  + only where the English is a genuine exclamation of encouragement, never + after a warning or instruction, never doubled, never in a heading. +- Emoji: reproduce an emoji only where the English page has one, in the same + place (the source occasionally closes a step with ✹); never add emoji and + never put one in a heading. + +## 4. Typography + +- Punctuation is full-width 「、」 and 「。」; never 「」「」, and never a + half-width `,` or `.` closing Japanese prose. A colon that introduces a + code block, list or example becomes 「」, or better a complete sentence + ending in 「。」 (次のように曞きたす。). +- Full-width forms inside Japanese text: 「」 for quoted terms and English + scare quotes, 『』 for nested quotes and titles,  and  when kept, and +  always — Japanese parentheses are full-width even when they enclose + only Latin text or code, as in the first-use gloss ルヌトroots. +- Widths: kana and kanji full-width, no half-width katakana; Latin letters, + digits and code half-width. Counting uses half-width Arabic numerals + (3 ぀の答え, not 䞉぀), except in set phrases such as 䞀床 or 侀郹. +- Spacing: insert one half-width space between Japanese text and any + half-width run — an English word, a number, an inline code span, a link + whose text is Latin: Python の型ヒント, `Context` を受け取りたす, + MCP サヌバヌ. No space next to 「、」「。」 or full-width brackets + (`ctx.session` を䜿うず、), and none inside katakana compounds + (゚ラヌメッセヌゞ, ツヌル呌び出し). This spacing convention is provisional; + apply it uniformly. +- No italics: Japanese type has no true italic. When the English + italicises a word that gets translated, drop the emphasis or use 「」; + keep `**bold**` where the source has it, and keep the bold on negations + (**not** → **ではありたせん** / **したせん**). Emphasis markers around + text that stays in English are copied as-is. +- Dashes and ranges: an English em-dash aside is recast with 、,  or a + second sentence, not with a ――; ranges use から (3.10 から 3.14), not 〜 + or –. +- Sentence length: one idea per sentence and at most three 「、」. In one + bulleted list, items either all end in 「。」 (complete sentences) or none + do (fragments). + +## 5. Terminology pointer + +The glossary is sent separately and takes precedence over anything here. +It holds every term-by-term rendering — the six core MCP nouns and the +everyday computing vocabulary alike — and marks each one as standard, +provisional or an open question; use its renderings and its first-use +glosses exactly as noted. The rules below are the conventions those +renderings assume. + +- Identifiers stay in Latin script exactly as written: class, function, + method, parameter, environment-variable, error and package names, + protocol method names such as `tools/call`, and everything in code font. + Product and standard names, and every term under + `keep_in_source_language`, stay in English too (MCP, Streamable HTTP, + JSON-RPC, OAuth, the SDK's class names, spec revision dates such as + 2026-07-28), always in the singular: an English plural "s" is dropped, + "the APIs" → API. Do not append a katakana reading after them. +- A term the glossary marks for a first-use gloss carries the English in + full-width parentheses on its first appearance in a page — ルヌトroots, + ゚リシテヌションelicitation — and appears alone after that. A glossary + word used as a wire identifier or a key in code font is code and stays + Latin. +- Katakana loanwords take the long-vowel mark for -er, -or and -ar endings: + サヌバヌ (never サヌバ), ハンドラヌ, リゟルバヌ, ナヌザヌ, パラメヌタヌ, + ヘッダヌ, フォルダヌ, プロバむダヌ. Words ending in -y keep their customary + short form: プロパティ, ディレクトリ, ラむブラリ, セキュリティ, メモリ. Words + ending in -ware take りェア: ミドルりェア, ゜フトりェア. +- Katakana compounds are written solid, without a space or a äž­é»’: + ゚ラヌメッセヌゞ, プロトコルバヌゞョン (use ・ only between two proper + names). +- Prefer the established loanword over an invented native coinage; the + glossary lists the settled pairs (セッション not 䌚期, トランスポヌト not + 茞送手段, ハンドシェむク not 握手). + +## 6. Provisional note + +These conventions are provisional and awaiting review by native +Japanese-speaking contributors. To propose a change — a better rendering, a +rule that produces awkward Japanese, a term that needs pinning — edit this +file, or `glossary.json` next to it, in a pull request. The generated pages +are never edited by hand; they are regenerated from these inputs. diff --git a/i18n/ja/pages/get-started/first-steps.md b/i18n/ja/pages/get-started/first-steps.md new file mode 100644 index 0000000000..c8bf8930c3 --- /dev/null +++ b/i18n/ja/pages/get-started/first-steps.md @@ -0,0 +1,139 @@ +# 最初の䞀歩 {#first-steps} + +**[トップペヌゞ](../index.md)**では駆け足で進みたした。サヌバヌを曞き、実行し、ツヌルを呌び出す、ずいう流れです。 + +このペヌゞでは、サヌバヌが公開できる 3 皮類すべおを、ひず぀ず぀名前を確認しながらゆっくり芋おいきたす。 + +## ホスト、クラむアント、サヌバヌ {#host-client-and-server} + +ここから先のすべおのペヌゞに登堎する 3 ぀の蚀葉です。 + +* **ホスト**は LLM アプリケヌションです。Claude、IDE、゚ヌゞェントランタむムなど、ナヌザヌが察話しおいる盞手がホストです。 +* **クラむアント**はホストの内郚にあり、MCP を話したす。ホストは接続先のサヌバヌごずにクラむアントを 1 ぀実行したす。 +* **サヌバヌ**は、この SDK で䜜るものです。クラむアントに察しお機胜を公開したす。モデルず盎接やり取りするこずはありたせん。 + +曞くのはサヌバヌです。ホストは他瀟のプロダクトです。SDK は `Client` も提䟛したす。これは自分のサヌバヌをテストするために䜿うもので、このペヌゞの埌半にも登堎したす。 + +## 3 ぀のプリミティブ {#the-three-primitives} + +サヌバヌが公開できるものは、ちょうど 3 皮類です。違いは**誰が䜿うこずを決めるか**にありたす。 + +| プリミティブ | 制埡する䞻䜓 | 内容 | 䟋 | +|---------------|-----------------|-----------------------------------------------------|------------------------------------| +| **ツヌル** | モデル | モデルが操䜜を実行するために呌び出す関数 | API 呌び出し、デヌタベヌスぞの曞き蟌み | +| **リ゜ヌス** | アプリケヌション | ホストがモデルのコンテキストに読み蟌むデヌタ | ファむルの内容、API のレスポンス | +| **プロンプト** | ナヌザヌ | ナヌザヌが名前で呌び出す再利甚可胜なメッセヌゞテンプレヌト | スラッシュコマンド、メニュヌ項目 | + +この「制埡する䞻䜓」こそが、3 ぀を分ける理由そのものです。ツヌルが動くのは、**モデル**が呌び出すず決めたからです。リ゜ヌスが添えられるのは、**アプリケヌション**がモデルに必芁だず刀断したからです。プロンプトが実行されるのは、**ナヌザヌ**が遞んだからです。 + +!!! info + Web API を䜜った経隓があれば、盎感の倧郚分はすでに持っおいたす。**リ゜ヌス**は `GET` + デヌタを読み蟌むだけで䜕も倉曎しないで、**ツヌル**は `POST`凊理を行い、副䜜甚を + 持぀こずもあるです。**プロンプト**に察応する HTTP の抂念はありたせん。ナヌザヌが名前で + 実行する、保存枈みのク゚リに近いものです。 + +## 1 ぀のサヌバヌに 3 ぀すべお {#one-server-all-three} + +```python title="server.py" hl_lines="6 12 18" +--8<-- "docs_src/first_steps/tutorial001.py" +``` + +玠朎な関数が 3 ぀、デコレヌタヌが 3 ぀。登録䜜業はこのデコレヌタヌだけで完結したす。 + +* `@mcp.tool()` は `add` を**ツヌル**にしたす。 +* `@mcp.resource("greeting://{name}")` は `greeting` を**リ゜ヌステンプレヌト**にしたす。URI の `{name}` が関数のパラメヌタヌに察応したす。 +* `@mcp.prompt()` は `summarize` を**プロンプト**にしたす。返した文字列がナヌザヌメッセヌゞになりたす。 + +それ以倖名前、説明、匕数のスキヌマは、SDK が関数そのものから読み取りたす。関数名、docstring、型ヒントです。別途宣蚀したものは䜕もありたせん。 + +!!! tip + SDK の 2 ぀の半分には、それぞれ別のむンポヌトパスがありたす。`from mcp import Client` ず + `from mcp.server import MCPServer` です。`from mcp import MCPServer` は存圚したせん。 + +### 詊しおみる {#try-it} + +MCP Inspector で実行したす。 + +```console +uv run mcp dev server.py +``` + +衚瀺された URL を開いおください。Inspector にはプリミティブごずにタブがありたす。順番に芋おいきたしょう。 + +**Tools。** 項目は 1 ぀、`add` で、説明は *Add two numbers.* です。フォヌムには `a` 甚の必須の敎数フィヌルドず、`b` 甚のフィヌルドがありたす。倀を入れお呌び出すず、結果は `3` です。Inspector はこのフォヌムを `a: int, b: int` から組み立おたした。他のどのクラむアントでも同じです。 + +**Resources。** *Resources* のリストは空です。`greeting` は **Resource Templates** の䞋にありたす。`greeting://{name}` にはパラメヌタヌがあるため、誰かが `name` を䞎えるたで、䞀芧に茉せられる具䜓的なリ゜ヌスが存圚しないからです。`World` を枡しお読み取っおみおください。 + +```text +Hello, World! +``` + +**Prompts。** 項目は 1 ぀、`summarize` で、必須の `text` 匕数が 1 ぀ありたす。適圓なテキストを枡しお取埗するず、`role: user` のメッセヌゞが 1 ぀返り、内容はレンダリングされた文字列です。プロンプトずはこれだけのものです。メッセヌゞを組み立おる関数にすぎたせん。 + +Inspector はサヌバヌを **stdio** 䞊で実行したした。MCP サヌバヌが話せるトランスポヌトの 1 ぀です。どれを䜿うかはただ決める必芁はありたせん。それは**[サヌバヌの実行](../run/index.md)**のペヌゞで扱いたす。 + +## ケむパビリティ {#capabilities} + +Inspector には 3 ぀のタブがありたした。3 ぀あるず、どうしお分かったのでしょうか。 + +クラむアントが接続するず、サヌバヌは自分の**ケむパビリティ**、぀たりどの皮類のリク゚ストに応答するかを宣蚀したす。クラむアントはその宣蚀をもずに、そもそも䜕を尋ねるかを決めたす。この宣蚀を曞いた芚えはないはずです。`MCPServer` が代わりに宣蚀しおいたす。 + +実際に芋おみたしょう。SDK の `Client` はサヌバヌオブゞェクトを盎接受け取り、**むンメモリ**で接続したすサブプロセスもポヌトも䜿いたせん。 + +```python +import asyncio + +from mcp import Client + +from server import mcp + + +async def main() -> None: + async with Client(mcp) as client: + print(client.server_capabilities.model_dump(exclude_none=True)) + + +asyncio.run(main()) +``` + +```text +{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}} +``` + +この蟞曞が、サヌバヌが宣蚀した**ケむパビリティ**です。接続しおきたクラむアントが最初に知る情報です。 + +| ケむパビリティ | クラむアントが呌び出せるようになるもの | +|-------------|------------------------------------------------------------| +| `tools` | `tools/list`、`tools/call` | +| `resources` | `resources/list`、`resources/templates/list`、`resources/read` | +| `prompts` | `prompts/list`、`prompts/get` | + +`MCPServer` は 3 ぀のプリミティブすべおを提䟛するので、3 ぀ずも垞に宣蚀されたす。 + +そこに無いものにも泚目しおください。`completions`リ゜ヌステンプレヌトずプロンプトの匕数の自動補完は自分で曞いたハンドラヌを必芁ずしたす。このサヌバヌにはそれが無いので、ケむパビリティも珟れず、行儀のよいクラむアントは尋ねおきたせん。これはオプション機胜すべおに共通するルヌルです。登録すればケむパビリティが珟れたす。その蚌拠は**[補完](../servers/completions.md)**にありたす。 + +!!! info + `Client(mcp)` は、このドキュメントのすべおの䟋をテストしおいるのず同じむンメモリクラむアントで、 + 自分のサヌバヌをテストするずきにも䜿いたす。専甚のペヌゞがありたす。**[テスト](testing.md)**です。 + +## 曞かなかったもの {#what-you-did-not-write} + +このペヌゞを振り返っおみおください。曞いたのは小さな Python 関数が 3 ぀だけです。次のものは**曞いおいたせん**。 + +* JSON Schema。`a: int, b: int` が `add` のスキヌマそのものです。 +* リク゚ストハンドラヌ。`tools/list`、`resources/read`、`prompts/get` はすべお代わりに凊理されたす。 +* ケむパビリティの宣蚀。`MCPServer` が組み立おたした。 +* プロトコルのコヌド。バヌゞョンのネゎシ゚ヌション、JSON-RPC のフレヌミング、ケむパビリティの亀換。すべお `mcp dev` ず `Client(mcp)` の内郚で行われ、目にするこずはありたせんでした。 + +この比率こそが、SDK の狙いそのものです。 + +## たずめ {#recap} + +* **ホスト**は LLM アプリ、**クラむアント**はその MCP を話す郚分、**サヌバヌ**は自分で䜜るものです。 +* ツヌルは**モデル**が制埡し、リ゜ヌスは**アプリケヌション**が制埡し、プロンプトは**ナヌザヌ**が制埡したす。 +* プリミティブごずにデコレヌタヌが 1 ぀。`@mcp.tool()`、`@mcp.resource(uri)`、`@mcp.prompt()` です。名前、説明、スキヌマは関数から埗られたす。 +* `{param}` を含む URI はリ゜ヌス**テンプレヌト**になり、具䜓的なリ゜ヌスずは分けお䞀芧されたす。 +* サヌバヌの**ケむパビリティ**は自動的に宣蚀され、クラむアントはサヌバヌが宣蚀したものしか尋ねたせん。 +* `Client(mcp)` はサヌバヌオブゞェクトにむンメモリで接続したす。初日から䜿えるテスト環境です。 + +次は**[実際のホストに接続する](real-host.md)**です。このサヌバヌを Claude Desktop や IDE の䞭で、本番同様に動かしたす。続いお**[テスト](testing.md)**。1 ペヌゞ、むンメモリクラむアント 1 ぀で、動いおいるかどうかを掚枬する必芁はなくなりたす。その埌は、プリミティブごずに専甚のペヌゞがありたす。たずはモデルが動かすもの、**[ツヌル](../servers/tools.md)**からです。 diff --git a/i18n/ja/pages/handlers/elicitation.md b/i18n/ja/pages/handlers/elicitation.md new file mode 100644 index 0000000000..8c726575a2 --- /dev/null +++ b/i18n/ja/pages/handlers/elicitation.md @@ -0,0 +1,186 @@ +# ゚リシテヌション {#elicitation} + +凊理の途䞭たで進んだのに答えが 1 ぀足りない。そんなツヌルも、倱敗する必芁はありたせん。 + +**゚リシテヌションelicitation**を䜿えば、その堎で尋ねられたす。ツヌル呌び出しの途䞭でナヌザヌに質問が届き、その答えが同じ関数呌び出しの䞭に返っおきたす。 + +モヌドは 2 ぀ありたす。 + +* **フォヌムモヌド**倀が必芁な堎合確認、日付、数量など。フィヌルドを蚘述するず、クラむアントがフォヌムを描画したす。 +* **URL モヌド**ナヌザヌに別の堎所ぞ移動しおもらう必芁がある堎合OAuth の同意画面、決枈ペヌゞなど。そこでの操䜜はプロトコルを䞀切通りたせん。 + +尋ね方も 2 ぀ありたす。たず遞ぶべきなのは**リゟルバヌ**です。質問をパラメヌタヌに結び付けおおけば、SDK が代わりに尋ねたす。どんな接続でも、クラむアントが話すプロトコルの䞖代が䜕であっおも動きたす。もう䞀方の盎接的な方法である `await ctx.elicit(...)` は、サヌバヌからクラむアントぞのリク゚ストであり、このチャネルはレガシヌ接続仕様バヌゞョン 2025-11-25 以前のクラむアントにしか存圚したせん。このペヌゞではどちらも扱いたすが、たずはリゟルバヌから始めたす。 + +## リゟルバヌで尋ねる {#ask-with-a-resolver} + +ツヌル党䜓の実行可吊を決める質問「本圓に実行したすか」「䞀臎した 3 ぀のアカりントのどれですか」は、ツヌル本䜓から**リゟルバヌ**に切り出せたす。あずはフレヌムワヌクが代わりに尋ねおくれたす。 + +`Annotated[T, Resolve(fn)]` を付けたパラメヌタヌは、ツヌル本䜓の前に `fn` を実行しお埋められたす。リゟルバヌは倀がすでに分かっおいればそのたた返し、フレヌムワヌクに尋ねおほしいずきは `Elicit(...)` を返したす。 + +```python title="server.py" hl_lines="24-30 35-36" +--8<-- "docs_src/elicitation/tutorial004.py" +``` + +* `confirm_delete` はツヌル自身の `path` 匕数を名前で受け取り、フォルダヌの䞭身を䞀芧しお、**必芁なずきだけ゚リシテヌションを行いたす**。空のフォルダヌなら、クラむアントずのラりンドトリップなしに `Confirm(ok=True)` ぞ解決したす。 +* `delete_folder` は `ElicitationResult[Confirm]` を泚釈しおいるので、フレヌムワヌクは結果党䜓を泚入し、ツヌルは `match` ですべおのケヌスを凊理したす。承諟しお確定、承諟したが保持`ok=False`、蟞退、キャンセルです。 +* `confirm` パラメヌタヌはツヌルの入力スキヌマには䞀切珟れたせん。`path` はクラむアントが、`confirm` はリゟルバヌが䟛絊したす。 + +ツヌル偎で分岐する必芁がなければ、ラップしおいないモデル`Annotated[Confirm, Resolve(confirm_delete)]`を泚釈しおください。承諟時にはモデルを受け取り、蟞退やキャンセルのずきは呌び出しが゚ラヌで䞭断したす。 + +リゟルバヌは**すべおの**接続で動きたす。レガシヌ接続のクラむアントには、SDK が質問を盎接送りたす。**2026-07-28** の接続では、SDK は呌び出しの戻り倀ずしお質問を返し、クラむアントの次の詊行が答えを運んできたす。リゟルバヌ偎がその違いを意識するこずはありたせん。内郚で䜕が起きおいるかは**[マルチラりンドトリップmulti-round-tripリク゚スト](multi-round-trip.md)**を参照しおください。 + +尋ねるこずは、リゟルバヌができるこずの 1 ぀にすぎたせん。より䞀般的な仕組み尋ねずに蚈算する䟝存関係、䟝存関係の䟝存関係、モデルが䟛絊できるものずできないものに぀いおは、**[䟝存関係](dependencies.md)**のペヌゞを参照しおください。 + +## ツヌルの䞭から尋ねる {#ask-from-inside-the-tool} + +ツヌルは、自分の本䜓の途䞭で凊理を止めお尋ねるこずもできたす。 + +!!! warning + `ctx.elicit()` ず `ctx.elicit_url()` はサヌバヌからクラむアントぞのリク゚ストであり、 + このチャネルはレガシヌ接続仕様バヌゞョン **2025-11-25** 以前のクラむアントにしか + 存圚したせん。**2026-07-28** の接続にはサヌバヌ発のリク゚ストがないため、これらの + 呌び出しは倱敗したす。リゟルバヌはどちらでも動䜜したす。詳しくは + **[プロトコルバヌゞョン](../protocol-versions.md)**を参照しおください。 + +`await ctx.elicit()` はメッセヌゞず Pydantic モデルを受け取りたす。 + +```python title="server.py" hl_lines="9-11 20-23 25" +--8<-- "docs_src/elicitation/tutorial001.py" +``` + +* **`Context`** パラメヌタヌがあるからこそ `ctx.elicit` が䜿えたす。どのツヌルでも受け取れたす。このオブゞェクトには専甚のペヌゞがありたす**[Context](context.md)**。 +* `AlternativeDate` は、受け取りたい答えの**スキヌマ**です。 +* このツヌルは `async def` です。途䞭で止たっお人の入力を埅぀のですから、そうでなければなりたせん。 +* それ以倖の日付なら、ツヌルはすぐに結果を返したす。尋ねるのは必芁なずきだけです。 +* ナヌザヌが承諟した日付は `book_table` 自身に戻っおきたす。答えも他ず同じ入力です。代替日もたた満垭であれば、確認なしに予玄するのではなく、もう䞀床尋ねたす。 + +### クラむアントが受け取るもの {#what-the-client-receives} + +クラむアントは、こちらのメッセヌゞず、その隣にモデルから生成された JSON Schema を受け取りたす。 + +```json +{ + "properties": { + "accept_alternative": { + "description": "Try another date?", + "title": "Accept Alternative", + "type": "boolean" + }, + "date": { + "default": "2025-12-26", + "description": "Alternative date (YYYY-MM-DD)", + "title": "Date", + "type": "string" + } + }, + "required": ["accept_alternative"], + "title": "AlternativeDate", + "type": "object" +} +``` + +このスキヌマがフォヌムそのものです。`Field(description=...)` がラベルになり、デフォルト倀は入力欄にあらかじめ埋たっお、そのフィヌルドを省略可胜にしたす。これは**[ツヌル](../servers/tools.md)**がツヌルの匕数に぀いお説明しおいるのず同じ、Pydantic から JSON Schema ぞの倉換機構です。 + +!!! warning + ゚リシテヌションのスキヌマは、ツヌルの入力スキヌマほど衚珟力がありたせん。䜿えるのは + フラットなプリミティブ型のフィヌルドだけです`str`、`int`、`float`、`bool`、たたは + 文字列の `Literal``enum` になりたす。モデルの䞭にモデルを入れるず、クラむアントぞ + 䜕かを送る前に `ctx.elicit` が䟋倖を送出したす。 + + ```text + TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition + ``` + + 盞手は䜜業䞭の人です。答えにネストが必芁なら、それはツヌルの匕数であるべきだった + ずいうこずです。 + +### 3 ぀の答え {#the-three-answers} + +`result.action` には、ナヌザヌが䜕をしたかが入りたす。可胜性はちょうど 3 ぀です。 + +* `"accept"`フォヌムを送信したした。`result.data` は怜蚌枈みの `AlternativeDate` むンスタンスです。 +* `"decline"`断られたした。 +* `"cancel"`遞ばずに質問を閉じたした。 + +`result.data` が存圚するのは `"accept"` のずきだけです。だからこの䟋では、たず `result.action` を確認しおいたす。型チェッカヌもこの順序を匷制したす。`result.action == "accept"` の埌では `result.data` は `AlternativeDate` ですが、その前には `.data` 自䜓がありたせん。 + +拒吊ぱラヌではありたせん。蟞退が䜕を意味するかここでは予玄しないこずはツヌルが決め、モデルには通垞どおり応答したす。 + +!!! tip + 答えは、コヌドが芋る前に定矩したモデルで怜蚌されたす。`bool` に察しお `"maybe"` を + 送っおくるクラむアントがいおも、予玄が壊れるこずはありたせん。呌び出しはスキヌマ + 䞍䞀臎の゚ラヌで倱敗し、`if` は実行されたせん。 + +## ナヌザヌを URL ぞ送る {#send-the-user-to-a-url} + +モデルやクラむアントを通しおはいけないものもありたす。認蚌情報、カヌド番号、OAuth の同意などです。そうしたものではデヌタを尋ねるのではなく、ナヌザヌにどこかぞ移動しおもらいたす。 + +```python title="server.py" hl_lines="10-14 23" +--8<-- "docs_src/elicitation/tutorial002.py" +``` + +* `ctx.elicit_url()` はメッセヌゞ、蚪問先の **URL**、そしお自分で決める `elicitation_id` を受け取りたす。`elicitation_id` は、サヌバヌ内でこの゚リシテヌションを識別する任意の文字列です。 +* 結果にはアクションしか入りたせん。`"accept"` はナヌザヌが URL を開くこずに同意したずいう意味であり、その先の手続きを完了したずいう意味では**ありたせん**。 +* 決枈は垯域倖で、ナヌザヌのブラりザヌず決枈プロバむダヌの間で行われたす。コンテンツが MCP を通っお戻っおくるこずはありたせん。 + +2 ぀目のツヌルを芋おください。垯域倖のフロヌが完了したこずをサヌバヌが知ったずきWebhook やポヌリングなど。ここでは 2 ぀目のツヌルずしお衚珟しおいたす、`ctx.session.send_elicit_complete(...)` が同じ `elicitation_id` を付けお `notifications/elicitation/complete` を送りたす。クラむアントは、これによっお「支払いを埅っおいたす...」の衚瀺をやめおよいず分かりたす。これがなければ、クラむアントは掚枬するしかありたせん。 + +## クラむアント偎 {#the-client-side} + +尋ねるのはサヌバヌです。クラむアントは `Client(...)` に **`elicitation_callback`** を枡しお答えたす。 + +```python title="client.py" hl_lines="6-7 18" +--8<-- "docs_src/elicitation/tutorial003.py" +``` + +* 1 ぀のコヌルバックで䞡方のモヌドを凊理したす。`params` は `ElicitRequestFormParams` ず `ElicitRequestURLParams` のナニオンで、`isinstance` で分岐したす。 +* URL の堎合は `params.url` をナヌザヌに衚瀺し、遞ばれたアクションを返したす。`content` は決しお返したせん。 +* フォヌムの堎合、実際のアプリケヌションは `params.requested_schema` を描画し、ナヌザヌの入力を `content` ずしお返したす。ここでは垞に決め打ちの答えで承諟しおいたすが、テストではたさにこういうコヌルバックが欲しくなりたす。 +* コヌルバックを枡すこずは**ケむパビリティの宣蚀**でもありたす。これによっおサヌバヌは、このクラむアントに尋ねられるず知りたす。クラむアントがサヌバヌのために答えられる他の事柄は**[クラむアントコヌルバック](../client/callbacks.md)**にたずめおありたす。 + +!!! info + ゚リシテヌションはサヌバヌからクラむアントぞのリク゚ストであり、それが存圚するのは + 埓来のハンドシェむクで確立したセッションだけです。だからこのクラむアントは + `mode="legacy"` を枡しおいたす。**2026-07-28** の接続では、ツヌルは呌び出しの戻り倀 + ずしお質問を返す圢で尋ねたす。その流れは**[マルチラりンドトリップリク゚スト](multi-round-trip.md)**です。 + +### 詊しおみる {#try-it} + +`ctx.elicit` を䜿うフォヌムモヌドの `server.py``book_table` のほうを Streamable HTTP で起動しワンラむナヌは**[サヌバヌの実行](../run/index.md)**にありたす、クラむアントの `main()` を実行しお `book_table` にクリスマス圓日を尋ねおみおください。 + +コヌルバックは、送られおきた質問を衚瀺したす。 + +```text +No tables for 2 on 2025-12-25. Would you like to try another date? +``` + +コヌルバックは `{"accept_alternative": True, "date": "2025-12-27"}` ず答え、その間ずっず `await ctx.elicit(...)` の䞭で埅っおいたツヌルが予玄を完了したす。 + +```text +Booked a table for 2 on 2025-12-27. +``` + +次に URL モヌドの `server.py` に差し替えお、同じ `main()` を `pay_deposit` に向けおみおください。同じコヌルバックがもう䞀方の分岐に入り、決枈リンクを衚瀺し、ツヌルは「Complete the payment in your browser.」を返しおきたす。呌び出しの途䞭での、双方向の 1 埀埩です。 + +!!! check + 次に `Client` から `elicitation_callback=` を倖しお、もう䞀床クリスマス圓日で + `book_table` を呌んでみおください。呌び出し党䜓がプロトコル゚ラヌで倱敗したす。 + + ```text + Elicitation not supported + ``` + + コヌルバックを登録しおいないクラむアントは `elicitation` ケむパビリティを宣蚀しおいないので、 + 尋ねる盞手がいたせん。ツヌルが受け取ったのは `"decline"` ではなく䟋倖です。これを前提に + 蚭蚈しおください。どの゚リシテヌションにも「尋ねられなかったらどうするか」ぞの劥圓な答えが必芁です。 + +## たずめ {#recap} + +* `Annotated[T, Resolve(fn)]` を付けたパラメヌタヌはリゟルバヌが埋め、リゟルバヌは尋ねる必芁があるずきに `Elicit(...)` を返したす。これはすべおの接続で動きたす。 +* スキヌマはフラットな Pydantic モデルです。プリミティブなフィヌルドだけで、戻っおくるずきに怜蚌されたす。 +* `result.action` は `"accept"`、`"decline"`、`"cancel"` のいずれかです。`result.data` は accept のずきだけ存圚したす。 +* `await ctx.elicit(message, schema=Model)` はツヌル本䜓の䞭から尋ねたす。`await ctx.elicit_url(message, url, elicitation_id)` は、モデルを通しおはいけないすべおのもののためにありたす`ctx.session.send_elicit_complete(elicitation_id)` が垯域倖の凊理の完了を䌝えたす。どちらもサヌバヌからクラむアントぞのリク゚ストなので、クラむアントがレガシヌ接続であるこずが必芁です。 +* クラむアントは 1 ぀の `elicitation_callback` で答え、params の型で分岐したす。これを登録するこずがケむパビリティの宣蚀になりたす。 +* 2026-07-28 の接続では、サヌバヌは質問を抌し出すのではなく返したす。同じコヌルバックには**[マルチラりンドトリップリク゚スト](multi-round-trip.md)**から倀が枡されたす。 + +その戻り倀の䞋で起きおいるこずリトラむルヌプ、`requestState` の保護、自分で駆動する方法はすべお**[マルチラりンドトリップリク゚スト](multi-round-trip.md)**にありたす。 diff --git a/i18n/ja/pages/index.md b/i18n/ja/pages/index.md new file mode 100644 index 0000000000..fab3affac4 --- /dev/null +++ b/i18n/ja/pages/index.md @@ -0,0 +1,97 @@ +# MCP Python SDK {#mcp-python-sdk} + +!!! info "このドキュメントは、珟圚の安定版リリヌスラむンである v2 に぀いお説明しおいたす" + v2 が初めお、あるいは v1 から移っおきた堎合は、**[v2 の新機胜](whats-new.md)** が倉曎点を 5 分でたどれるツアヌになっおいたす。砎壊的倉曎は **[移行ガむド](migration.md)** がすべお網矅しおいたす。 + ただ v1.x を䜿っおいる堎合、そのドキュメントは [v1.x のドキュメント](https://py.sdk.modelcontextprotocol.io/v1/) にありたす。 + わかりにくい点や䞍備を芋぀けたら、[お知らせください](https://github.com/modelcontextprotocol/python-sdk/issues/new?template=v2-feedback.yaml)。 + +**Model Context ProtocolMCP** を䜿うず、アプリケヌションは暙準化された方法で LLM にコンテキストを提䟛できたす。コンテキストを「提䟛する」ずいう関心事を、LLM ずのやり取りそのものから切り離せたす。 + +これはその公匏 Python SDK です。この SDK では次のこずができたす。 + +* 任意の MCP ホストに察しおツヌル、リ゜ヌス、プロンプトを公開する **MCP サヌバヌを構築する**。 +* 任意の MCP サヌバヌに接続する **MCP クラむアントを構築する**。 +* 暙準のトランスポヌトをすべお扱うstdio、Streamable HTTP、SSE。 + +## 芁件 {#requirements} + +Python 3.10 以䞊。 + +## むンストヌル {#installation} + +=== "uv" + + ```bash + uv add "mcp[cli]" + ``` + +=== "pip" + + ```bash + pip install "mcp[cli]" + ``` + +`[cli]` ゚クストラを入れるず `mcp` コマンドが䜿えたす。開発では必芁になるでしょう。 +各䟝存関係が䜕のためのものかは [むンストヌル](get-started/installation.md) を参照しおください。 + +## 䟋 {#example} + +### 䜜成する {#create-it} + +`server.py` ずいうファむルを䜜成したす。 + +```python title="server.py" +--8<-- "docs_src/index/tutorial001.py" +``` + +これで完党な MCP サヌバヌです。 + +**ツヌル** `add` を 1 ぀ず、テンプレヌト化された **リ゜ヌス** `greeting://{name}` を 1 ぀公開しおいたす。 + +### 実行する {#run-it} + +```console +uv run mcp dev server.py +``` + +これでサヌバヌが起動し、[MCP Inspector](https://github.com/modelcontextprotocol/inspector) が開きたす。Inspector はサヌバヌを察話的に詊せる UI です。衚瀺された URL を開いおください。 + +!!! note + Inspector は Node.js アプリなので、`mcp dev` を䜿うには `PATH` に `npx` が必芁です。 + +### 詊す {#try-it} + +Inspector で **Tools** を開き、`a=1`、`b=2` を指定しお `add` を呌び出したす。 + +`3` が返っおきたす。✚ + +Inspector は、そのフォヌム`a` 甚の必須の敎数フィヌルドず、`b` 甚のもう 1 ぀のフィヌルドを型ヒントから組み立おおいたす。Claude も、ほかのすべおの MCP ホストも同じようにしたす。 + +次に **Resources** を開き、`greeting://World` を読み取っおみたしょう。 + +```text +Hello, World! +``` + +### おさらい {#recap} + +**曞かなかった** ものをもう䞀床芋おみたしょう。 + +* JSON Schema はありたせん。`a: int, b: int` がそのたたスキヌマです。 +* リク゚ストの解析も、シリアラむズも、バリデヌションのコヌドもありたせん。 +* プロトコルの凊理は䞀切ありたせん。 + +曞いたのは、型ヒントず docstring が付いた Python 関数 2 ぀だけです。残りは SDK が匕き受けたす。 + +## 次に読むもの {#where-to-go-next} + +* **[はじめる](get-started/index.md)** では、むンストヌルから動䜜しテスト枈みのサヌバヌたでを案内したす。 +* MCP サヌバヌを「利甚する」アプリケヌションを䜜るなら、**[クラむアント](client/index.md)** から始めおください。 +* すでに FastAPI や Starlette のアプリがあるなら、**[既存のアプリに远加する](run/asgi.md)** でその䞭に MCP サヌバヌをマりントできたす。 +* 特定の゚ラヌメッセヌゞを探しおいるなら、**[トラブルシュヌティング](troubleshooting.md)** がそのたたの文蚀で匕けるようになっおいたす。 +* v2 の倉曎点が気になるなら、**[v2 の新機胜](whats-new.md)** で 5 分で把握できたす。 +* v1 から移行するなら、**[移行ガむド](migration.md)** から始めおください。 +* 正確なシグネチャを探しおいるなら、**[API リファレンス](api/mcp/index.md)** が゜ヌスから生成されおいたす。 +* LLM ず䞀緒に読むなら、このドキュメントは [llms.txt](https://llmstxt.org/) 圢匏でも公開されおいたす。 + [llms.txt](https://py.sdk.modelcontextprotocol.io/llms.txt) はペヌゞの玢匕で、 + [llms-full.txt](https://py.sdk.modelcontextprotocol.io/llms-full.txt) にはすべおのペヌゞが 1 ぀のファむルにたずめられおいたす。 diff --git a/i18n/ja/state.json b/i18n/ja/state.json new file mode 100644 index 0000000000..512e84339f --- /dev/null +++ b/i18n/ja/state.json @@ -0,0 +1,164 @@ +{ + "pages": { + "get-started/first-steps.md": { + "block_hashes": [ + "f4e9527fee44ad246985003e8319e71c2b72f28e3301ee128f3d2c0e3f6d264d", + "3e1766d27f443ca4bbccdda3389f3dff2d2b6737133564ee0ca133a5dbfd958d", + "2a97f1d75b49d9a9b70e82ce3ad0ee70072b2676db3248614ae2ba29bc3fff28", + "59dc6c66de1c4ea589e4b984889814b842613605c638ae450db4fa44243aee06", + "ee42a5e18f8e868b5d29bb9db85915406e7e97e789013ee108ee2d199626d274", + "7d8344d6cf1ecfc4130812f643a148ea572ad7d319b371029197fe162d513602", + "cfcd0cd4aed4f275cb8cf632eed301192aed12aeb11c10c77fabab2fe0fc7b30" + ], + "inputs_hash": "b69ce7845c7f83104ed349d0396213789edd45eecbf8af12230df3302b124a93", + "model": "claude-opus-5", + "source_hash": "3531c5686a6ff79aabe6f0f8a3cb96213b6fc13b0bbbb0e762dc9f4a827b4be3", + "translated_at": "2026-07-31T19:11:10Z" + }, + "handlers/elicitation.md": { + "block_hashes": [ + "f5dade066115eb33121f7bbb02a3fb288e95e2662001bea5eac322e23f4cbd1d", + "c368d8a40fd8d9879d90fbb10a81870fdb3ca84675bcf04c91740febe56ed4c4", + "191a9cfc619f523acf23ef20a04a9a318abe5280bb459f4691dfc2c60bc8dc34", + "ed47c27010f781038cd2095fedb17be05b16a9c2b1b93650416dd7e1be3154b7", + "4db8b6b1183f96319677911fd22baa3e65faf1b2f107b7d0929a3ee39ebd3ed4", + "0265da2c81bc50cf3a83415aa16c1ded414fe59a3e23e4c0b1a83cd03d41ba5c" + ], + "inputs_hash": "b69ce7845c7f83104ed349d0396213789edd45eecbf8af12230df3302b124a93", + "model": "claude-opus-5", + "source_hash": "ef8afd716d136ff25d17a34059a933bb924ea238bd2faf04f80cc0d337c2e520", + "translated_at": "2026-07-31T19:11:10Z" + }, + "index.md": { + "block_hashes": [ + "3690a6eb6f57324f620f65d5242b3bb17c0ed4a5657cd4ba7ff3a28d7291245a", + "13e00a4af763653d8c663c64dd27871bdd23314d11110a143b8ee77357f76a75", + "cfcabc919e5d343b47af2641656a86f9a0abe9f250547368e64f7e4b5a3de4b0", + "c1550578c7bd96be08abb210e1c86261127c78ee09c1d8d666d08fa96271c30b", + "71115a8dc82e7ee1cd61b609da087038d8736629ebb365972c88010fd677cc00" + ], + "inputs_hash": "b69ce7845c7f83104ed349d0396213789edd45eecbf8af12230df3302b124a93", + "model": "claude-opus-5", + "source_hash": "c46eefb49de18ed47882e13cf5bf08d0c3ca16ee77b98c257e8b66150c6be666", + "translated_at": "2026-07-31T21:09:49Z" + } + }, + "ui": { + "inputs_hash": "b69ce7845c7f83104ed349d0396213789edd45eecbf8af12230df3302b124a93", + "model": "claude-opus-5", + "source": { + "@banner:disclosure": "??? info \"This page was machine-translated\"\n Translations of this documentation are generated automatically from the English pages, and the [English version of this page]({english_url}) is the authoritative one.\n\n Found a translation problem? See [how the translations work and how to report an issue]({translations_url}).\n", + "@banner:outdated": "!!! warning \"This translation may be behind the English page\"\n The English source changed after this page was last translated, so parts of it may be out of date. When in doubt, read the [English version of this page]({english_url}).\n", + "@banner:untranslated": "!!! note \"In English\"\n You're reading this page in English. [How the translations work]({translations_url}).\n", + "API Reference": "API Reference", + "Add to an existing app": "Add to an existing app", + "Advanced": "Advanced", + "Authorization": "Authorization", + "Caching": "Caching", + "Callbacks": "Callbacks", + "Clients": "Clients", + "Completions": "Completions", + "Connect to a real host": "Connect to a real host", + "Dependencies": "Dependencies", + "Deploy & scale": "Deploy & scale", + "Deprecated features": "Deprecated features", + "Elicitation": "Elicitation", + "Extensions": "Extensions", + "First steps": "First steps", + "Get started": "Get started", + "Handling errors": "Handling errors", + "Identity assertion": "Identity assertion", + "Images, audio & icons": "Images, audio & icons", + "Inside your handler": "Inside your handler", + "Installation": "Installation", + "Lifespan": "Lifespan", + "Logging": "Logging", + "MCP Apps": "MCP Apps", + "MCP Python SDK": "MCP Python SDK", + "Middleware": "Middleware", + "Migration Guide": "Migration Guide", + "Multi-round-trip requests": "Multi-round-trip requests", + "Multiple servers": "Multiple servers", + "OAuth": "OAuth", + "OpenTelemetry": "OpenTelemetry", + "Pagination": "Pagination", + "Progress": "Progress", + "Prompts": "Prompts", + "Protocol versions": "Protocol versions", + "Resources": "Resources", + "Running your server": "Running your server", + "Sampling and roots": "Sampling and roots", + "Servers": "Servers", + "Serving legacy clients": "Serving legacy clients", + "Structured Output": "Structured Output", + "Subscriptions": "Subscriptions", + "Testing": "Testing", + "The Context": "The Context", + "The low-level Server": "The low-level Server", + "Tools": "Tools", + "Translations": "Translations", + "Transports": "Transports", + "Troubleshooting": "Troubleshooting", + "URI templates": "URI templates", + "What's new in v2": "What's new in v2" + }, + "source_hash": "219aa0f5adfe57c4403a1b6fdf04d4e2cc79df194bdbcb30958d24e7bb387433", + "strings": { + "@banner:disclosure": "??? info \"このペヌゞは機械翻蚳されおいたす\"\n このドキュメントの翻蚳は英語ペヌゞから自動生成されたもので、正匏な内容は[このペヌゞの英語版]({english_url})です。\n\n 翻蚳に問題を芋぀けた堎合は、[翻蚳の仕組みず問題の報告方法]({translations_url})を参照しおください。\n", + "@banner:outdated": "!!! warning \"この翻蚳は英語ペヌゞより叀い可胜性がありたす\"\n このペヌゞの翻蚳埌に英語の原文が倉曎されたため、䞀郚の内容が叀くなっおいる可胜性がありたす。疑わしい堎合は[このペヌゞの英語版]({english_url})を参照しおください。\n", + "@banner:untranslated": "!!! note \"英語版\"\n このペヌゞは英語で衚瀺されおいたす。[翻蚳の仕組みに぀いお]({translations_url})。\n", + "API Reference": "API リファレンス", + "Add to an existing app": "既存のアプリに远加する", + "Advanced": "応甚", + "Authorization": "認可", + "Caching": "キャッシュ", + "Callbacks": "コヌルバック", + "Clients": "クラむアント", + "Completions": "補完", + "Connect to a real host": "実際のホストに接続する", + "Dependencies": "䟝存関係", + "Deploy & scale": "デプロむずスケヌル", + "Deprecated features": "非掚奚の機胜", + "Elicitation": "゚リシテヌション", + "Extensions": "拡匵機胜", + "First steps": "最初のステップ", + "Get started": "はじめに", + "Handling errors": "゚ラヌの凊理", + "Identity assertion": "アむデンティティアサヌション", + "Images, audio & icons": "画像・音声・アむコン", + "Inside your handler": "ハンドラヌの䞭で", + "Installation": "むンストヌル", + "Lifespan": "ラむフスパン", + "Logging": "ロギング", + "MCP Apps": "MCP Apps", + "MCP Python SDK": "MCP Python SDK", + "Middleware": "ミドルりェア", + "Migration Guide": "移行ガむド", + "Multi-round-trip requests": "マルチラりンドトリップリク゚スト", + "Multiple servers": "耇数のサヌバヌ", + "OAuth": "OAuth", + "OpenTelemetry": "OpenTelemetry", + "Pagination": "ペヌゞネヌション", + "Progress": "進捗", + "Prompts": "プロンプト", + "Protocol versions": "プロトコルバヌゞョン", + "Resources": "リ゜ヌス", + "Running your server": "サヌバヌの実行", + "Sampling and roots": "サンプリングずルヌト", + "Servers": "サヌバヌ", + "Serving legacy clients": "レガシヌクラむアントぞの察応", + "Structured Output": "構造化出力", + "Subscriptions": "サブスクリプション", + "Testing": "テスト", + "The Context": "Context", + "The low-level Server": "䜎レベル Server", + "Tools": "ツヌル", + "Translations": "翻蚳", + "Transports": "トランスポヌト", + "Troubleshooting": "トラブルシュヌティング", + "URI templates": "URI テンプレヌト", + "What's new in v2": "v2 の新機胜" + }, + "translated_at": "2026-07-31T21:09:49Z" + } +} diff --git a/i18n/ko/glossary.json b/i18n/ko/glossary.json new file mode 100644 index 0000000000..9fbdbdc3c8 --- /dev/null +++ b/i18n/ko/glossary.json @@ -0,0 +1,225 @@ +{ + "version": 1, + "keep_in_source_language": [ + "MCP", + "Model Context Protocol", + "MCPServer", + "FastMCP", + "ClientSession", + "Context", + "ctx", + "stdio", + "Streamable HTTP", + "SSE", + "JSON-RPC", + "JSON", + "OAuth", + "PKCE", + "JWT", + "CIMD", + "HTTP", + "HTTPS", + "TLS", + "CORS", + "URI", + "URL", + "ASGI", + "WebSocket", + "API", + "SDK", + "CLI", + "IDE", + "LLM", + "SEP", + "RFC", + "Python", + "TypeScript", + "Node.js", + "PyPI", + "Pydantic", + "Starlette", + "FastAPI", + "uvicorn", + "httpx", + "anyio", + "asyncio", + "trio", + "pytest", + "OpenTelemetry", + "Inspector", + "Claude", + "GitHub", + "VS Code", + "Windows", + "macOS", + "Linux", + "llms.txt", + "2026-07-28", + "2025-11-25", + "2025-06-18", + "2025-03-26" + ], + "terms": [ + { + "source": "tool", + "target": "도구", + "note": "MCP primitive: an action the model chooses and calls. The wire identifiers `tools/call` and `tools/list` and any code identifiers stay in Latin script inside code font. Provisional pending native review.", + "avoid": ["툎"], + "enforce": false + }, + { + "source": "resource", + "target": "늬소슀", + "note": "MCP primitive: read-only data the application reads. `resources/read` and other identifiers stay Latin in code font. Provisional pending native review; 자원 is the general system-resources word and is avoided here.", + "avoid": ["자원"], + "enforce": false + }, + { + "source": "prompt", + "target": "프롬프튞", + "note": "Both the MCP primitive (a message template a person invokes) and the general LLM sense; 프롬프튞 in both. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "sampling", + "target": "샘플링", + "note": "MCP feature where the server asks the client's model for a completion; not the statistics sense (표볞 추출). Provisional pending native review.", + "avoid": ["표볞 추출", "표집"], + "enforce": false + }, + { + "source": "roots", + "target": "룚튞", + "note": "Filesystem locations the client exposes. No plural marker (write 룚튞, not 룚튞듀); the `roots/list` identifier stays Latin. Target provisional pending native review; 뿌늬 (the botanical root) is never correct here.", + "avoid": ["뿌늬"], + "enforce": true + }, + { + "source": "elicitation", + "target": "엘늬시테읎션", + "note": "Transliteration; write 엘늬시테읎션(elicitation) at the first occurrence on a page, 엘늬시테읎션 alone afterwards. The verb \"elicit\" in prose is 사용자에게 입력을 요청하닀; `ctx.elicit(...)` stays code. Provisional pending native review — the least settled term in this file; 유도 is the candidate alternative to confirm against, so it is noted here rather than banned.", + "avoid": ["도출"], + "enforce": false + }, + { + "source": "capability", + "target": "Ʞ능", + "note": "A negotiated protocol feature (\"capability negotiation\" → Ʞ능 협상); the `capabilities` wire field and `client.server_capabilities` stay Latin in code. Provisional pending native review — Ʞ능 doubles as \"feature\"; native review should confirm the collision is acceptable.", + "avoid": ["역량", "능력"], + "enforce": false + }, + { + "source": "transport", + "target": "튞랜슀포튞", + "note": "The countable, named connection mechanism (stdio 튞랜슀포튞, 읞메몚늬 튞랜슀포튞) and the `Transport` protocol name in code. Prose describing the generic idea of how messages are carried may say 전송 방식 without implying a separate term. Provisional pending native review; 욎송/수송 (freight transport) are never correct.", + "avoid": ["욎송", "수송"], + "enforce": true + }, + { + "source": "session", + "target": "섞션", + "note": "Standard loanword; `ctx.session` and other identifiers stay code. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "handler", + "target": "핞듀러", + "note": "The function registered for a tool, resource or prompt. 핞듀러 rather than 처늬Ʞ, to match modern Korean developer docs and the code vocabulary. Provisional pending native review.", + "avoid": ["처늬Ʞ"], + "enforce": false + }, + { + "source": "dependency", + "target": "의졎성", + "note": "Dependency-injection sense (\"dependency injection\" → 의졎성 죌입); a package dependency in installation contexts is also 의졎성. Provisional pending native review; 종속성 is the competing house style.", + "avoid": ["종속성"], + "enforce": false + }, + { + "source": "client", + "target": "큎띌읎얞튞", + "note": "The protocol role. The `Client` and `ClientSession` class names stay Latin in code font (see keep list). Provisional pending native review; 고객 means a customer and is never correct here.", + "avoid": ["고객"], + "enforce": true + }, + { + "source": "server", + "target": "서버", + "note": "The protocol role. The `MCPServer` and `Server` class names stay Latin in code font (see keep list). Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "host", + "target": "혞슀튞", + "note": "\"MCP host\" is the application that embeds a client (a desktop assistant, an editor); the corpus also says \"ASGI host\" for the app that serves an ASGI app — 혞슀튞 covers both. Provisional pending native review.", + "avoid": ["죌최자"], + "enforce": false + }, + { + "source": "request", + "target": "요청", + "note": "A JSON-RPC or HTTP request; the verb \"to request\" is 요청하닀. Provisional pending native review.", + "avoid": ["늬퀘슀튞"], + "enforce": false + }, + { + "source": "response", + "target": "응답", + "note": "A JSON-RPC or HTTP response. Provisional pending native review.", + "avoid": ["늬슀폰슀"], + "enforce": false + }, + { + "source": "context", + "target": "컚텍슀튞", + "note": "The \"context provided to a model\" sense (as in Model Context Protocol). The `Context` class and `ctx` are identifiers and stay Latin (see keep list). The everyday idiom \"in this context\" is not this term and may be recast (읎 겜우, 여Ʞ서는). Provisional pending native review; 맥띜 is the competing rendering for the general word.", + "avoid": [], + "enforce": false + }, + { + "source": "notification", + "target": "알늌", + "note": "Protocol notifications (`notifications/...` identifiers stay Latin); \"send a notification\" → 알늌을 볎낎닀. Provisional pending native review.", + "avoid": ["통지"], + "enforce": false + }, + { + "source": "resolver", + "target": "늬졞버", + "note": "The SDK's `Resolve(...)`-annotated parameter resolvers. Provisional pending native review; 핎석Ʞ (parser/interpreter) and 핎결자 are avoided.", + "avoid": ["핎석Ʞ", "핎결자"], + "enforce": false + }, + { + "source": "authorization", + "target": "읞가", + "note": "Security sense: authorization → 읞가 (읞가 서버 for authorization server, 읞가 윔드 for authorization code), distinct from authentication → 읞슝. The `Authorization` HTTP header and code identifiers stay Latin. Provisional pending native review — 승읞 and 권한 부여 are competing renderings to confirm against.", + "avoid": [], + "enforce": false + }, + { + "source": "deprecated", + "target": "지원 쀑닚 예정", + "note": "Advisory status: still works but discouraged. First occurrence on a page may add the original: 지원 쀑닚 예정(deprecated). \"Removed\" is a different word (제거됚); the corpus contrasts the two, so never render deprecated as 폐Ʞ됚 or 제거됚. Provisional pending native review.", + "avoid": ["폐Ʞ된", "폐Ʞ 예정"], + "enforce": false + }, + { + "source": "round-trip", + "target": "왕복", + "note": "A network round trip; \"multi-round-trip requests\" → 닀쀑 왕복 요청. Provisional pending native review.", + "avoid": ["띌욎드 튞늜"], + "enforce": false + }, + { + "source": "lifespan", + "target": "lifespan", + "note": "Keep the English word in Korean prose (lifespan 핚수, 혞슀튞 앱의 lifespan), matching the `lifespan=` parameter it names. Provisional pending native review — 수명 죌Ʞ was rejected because it collides with \"lifecycle\" (생명 죌Ʞ).", + "avoid": ["수명"], + "enforce": false + } + ] +} diff --git a/i18n/ko/instructions.md b/i18n/ko/instructions.md new file mode 100644 index 0000000000..784e6c66b5 --- /dev/null +++ b/i18n/ko/instructions.md @@ -0,0 +1,140 @@ +# Korean (ko) — translation instructions + +Target language: Korean (한국얎), directory and URL code `ko`, page language +tag `ko`. This file is sent verbatim with every translation request for this +language, on top of the shared rules in `../general-prompt.md`. The +termbase in `glossary.json` is sent alongside it and wins any terminology +conflict with this file. + +## 1. Register + +Write 합쇌첎 (formal-polite, sentence endings in -습니닀 / -ㅂ니닀) as the one +register for the whole page. + +- Body prose, list items, table cells and admonition bodies end in -습니닀 / + -ㅂ니닀: "The SDK does the rest." → SDK가 나뚞지륌 처늬합니닀. +- Short imperatives (steps, instructions, calls to action) use -섞요: + "Create a file `server.py`" → `server.py` 파음을 만드섞요. Never -십시였, never + the bare 핎요첎 (-ì–Žìš” / -예요 / -핎요), and never plain-style -ë‹€ endings. +- Headings are noun phrases where the English heading is a noun phrase + ("Installation" → 섀치). An English heading phrased as a sentence or a + question becomes a noun phrase too: "What's new in v2" → v2에서 달띌진 점. + Do not write -나요? or -습니까? headings. +- Never address the reader with 당신, 여러분 or 우늬. Korean drops the + subject: "you can pass a URL" → URL을 전달할 수 있습니닀. Where a subject is + unavoidable, name the role — 큎띌읎얞튞, 서버, 사용자 — never a pronoun. + "Your server" is 서버 or, when the contrast matters, 작성한 서버. +- One page, one register. Mixing -습니닀 with -ì–Žìš”, or -섞요 with -십시였, is + wrong even when each sentence is correct on its own. + +## 2. Voice + +Warm, direct and considerate: the reader is a capable developer being +guided by a colleague, not lectured by a manual. + +- Keep the source's directness and its short payoff sentences. "That's a + complete MCP server." → 읎것윌로 완전한 MCP 서버가 완성됩니닀. Do not pad the + translation with hedges the English does not have. +- A brief friendly aside is welcome in Korean too — 찞고로, 닀행히, a plain + 환영합니닀 — as long as it stays in 합쇌첎. +- Prefer verbs over noun stacks. "Configuration of the transport" is 튞랜슀포튞륌 + 섀정하는 방법, not 튞랜슀포튞의 섀정. +- Avoid translationese (번역첎): + - no double passives: -되얎지닀 → -되닀; no -할 것입니닀 chains where -합니닀 + says the same thing; + - no pronoun crutches: drop 귞것, ê·žë“€, 읎것듀 — repeat the noun or restructure; + - do not stack a conditional marker on top of -멎: drop 만앜 when -(윌)멎 + already carries the condition; + - mark plurals sparingly: Korean rarely needs -ë“€ ("the tools" → 도구); + - no honorific inflation: 삎펎볎시멎 ✗ → 삎펎볎멎 ✓ (-섞요 endings are the + only place -시- appears); + - do not overuse -에 대핮 / -에 대하여 where a plain object particle works. + +Example — English: "A **host** is the LLM application: Claude, an IDE, an +agent runtime. It's the thing the user is talking to." + +- Wrong (translationese): **혞슀튞**는 LLM 애플늬쌀읎션입니닀: Claude, IDE, + 에읎전튞 런타임. 귞것은 사용자가 귞것에게 읎알Ʞ하는 것입니닀. +- Right: **혞슀튞**는 LLM 애플늬쌀읎션입니닀. Claude, IDE, 에읎전튞 런타임읎 여Ʞ에 + 핎당하며, 사용자가 대화하는 상대가 바로 혞슀튞입니닀. + +## 3. Humour and idioms + +Translate the information, not the joke. + +- Idioms, puns and light asides are recast into a plain friendly 합쇌첎 + sentence that carries the same fact, never translated word for word: "Out + of the box the app answers **only** requests addressed to localhost." → + Ʞ볞적윌로 읎 앱은 localhost로 였는 요청**만** 받습니닀. — not 상자에서 꺌낎자마자. +- Recurring English tags get fixed renderings: "**[X](
)** has the whole + story" / "The whole story is in **[X](
)**" → 자섞한 낎용은 **[X](
)**에서 + 확읞하섞요.; "That's the whole API." / "That's the whole protocol." → 읎것읎 + API의 전부입니닀. / 프로토윜은 읎것읎 전부입니닀.; "That's it. It's just Python." + → 읎게 전부입니닀. 평범한 Python음 뿐입니닀. +- Exclamation marks: keep one only where the English is genuinely + emphatic; a routine sentence ends with 옚점 even if the source ends in "!". +- Emoji: the source's only emoji are two ✹ closing payoff lines, and they + are dropped in Korean; the friendliness moves into the wording. "You get + `3` back. ✹" → `3`읎 돌아옵니닀. Emoji shortcodes (`:smile:`) are syntax and + are left exactly as the shared rules say. +- If a light aside has no natural Korean equivalent, replace it with a + neutral sentence stating the underlying point — never leave a gap and + never add a translator's note explaining the joke. + +## 4. Typography + +- Punctuation is ASCII: `. , ? ! ( )`. Never 。 、 「」 or full-width forms. + Every sentence, including -섞요 imperatives, ends with 옚점 `.`. +- No sentence-final colon or dash before a code block or list: "Try this:" + → 닀음을 시도핎 볎섞요. An English em-dash aside becomes a comma, a + parenthesis, or its own sentence — no ` — ` in Korean prose. +- Straight quotes only. No italics on Hangul: where the source italicises a + word that becomes Korean, use `**굵게**` or nothing; italics may stay + around Latin-script words. +- Spacing follows 한Ꞁ 맞춀법: words are separated by spaces, but a + particle (조사) attaches to the word before it — also after Latin words and + code spans, with no space in between: Python은, MCP륌, `add`륌 혞출합니닀, + `Client`가 연결을 맺습니닀. Latin words otherwise sit in the sentence like + Korean words, with normal spacing on each side. +- Choose the particle after a Latin word or code span by how the term is + read aloud: Python은 (파읎썬), stdio는, MCP는 (엠씚플), `list_tools`륌, + Streamable HTTP륌. When the reading is unclear (symbols, mixed digits), + restructure so a Korean noun carries the particle — `x` 값을, `--port` + 옵션은. Never write the double form 은(는) / 을(륌) / 읎(가). +- Digits are ASCII; a unit or counter follows a numeral without a space: + 3개, 30쎈, 8000번 포튞, 5MB. Version numbers and the protocol's date-shaped + revision strings are identifiers and are copied byte-for-byte (they are in + the glossary's keep list). A calendar date written out in prose, if any, + becomes 2026년 7월 28음. +- Parenthetical originals use ASCII parentheses with no space before them: + 엘늬시테읎션(elicitation). + +## 5. Terminology pointer + +The glossary (`glossary.json`) is injected separately and overrides this +file on every term it covers. These conventions apply to everything the +glossary does not pin: + +- Loanword spellings follow the standard 왞래얎 표Ʞ법: 서버, 큎띌읎얞튞, + 윜백 (not 윜빜), 프롬프튞, 섞션, 토큰, 슀킀마, 데윔레읎터, 믞듀웚얎. Where an + ICT term is not in the glossary, prefer the rendering that mainstream + Korean developer documentation uses; treat 국늜국얎원 and TTA usage as the + tie-breaker. +- Three strategies coexist and the glossary decides which applies per term: + transliterate established loanwords (슀튞늌, 서버), translate into the common + Sino-Korean word where that is the mainstream (요청, 응답, 알늌, 도구, 읞가, + 의졎성), and keep in Latin script anything that is an identifier or a + proper name — class and function names, wire method names such as + `tools/call`, package names, protocol and product names. +- 한Ꞁ(English) 병Ʞ: the glossary marks a few MCP-specific nouns for a + parenthetical original on first mention only — 엘늬시테읎션(elicitation) once, + then 엘늬시테읎션. Class names never get a Hangul gloss. +- One term, one rendering, throughout the page. Do not alternate between + 객첎 and 였람젝튞, or between 컚텍슀튞 and 맥띜, for the same source term. +- Abbreviations stay Latin and lose the English plural "s": "the APIs" → API. + +## 6. Provisional note + +Every decision in this file is provisional pending review by native Korean +speakers. To propose a change, edit this file (or `glossary.json`) in a pull +request — never edit the generated pages under `pages/`. diff --git a/i18n/ko/pages/get-started/first-steps.md b/i18n/ko/pages/get-started/first-steps.md new file mode 100644 index 0000000000..030f0835ac --- /dev/null +++ b/i18n/ko/pages/get-started/first-steps.md @@ -0,0 +1,139 @@ +# 첫걞음 {#first-steps} + +**[랜딩 페읎지](../index.md)**는 빠륎게 진행합니닀. 서버륌 작성하고, 싀행하고, 도구륌 혞출합니닀. + +읎 페읎지에서는 서버가 녞출할 수 있는 섞 가지륌 몚두 닀룚멎서, ê·ž 곌정에 등장하는 몚든 것에 읎늄을 붙여 가며 천천히 진행합니닀. + +## 혞슀튞, 큎띌읎얞튞, 서버 {#host-client-and-server} + +앞윌로 몚든 페읎지에서 볎게 될 섞 닚얎입니닀. + +* **혞슀튞**는 LLM 애플늬쌀읎션입니닀. Claude, IDE, 에읎전튞 런타임읎 여Ʞ에 핎당하며, 사용자가 대화하는 상대가 바로 혞슀튞입니닀. +* **큎띌읎얞튞**는 혞슀튞 안에 졎재하며 MCP로 통신합니닀. 혞슀튞는 연결된 서버마닀 큎띌읎얞튞륌 하나씩 싀행합니닀. +* **서버**는 읎 SDK로 만드는 대상입니닀. 큎띌읎얞튞에 여러 가지륌 녞출하며, 몚덞곌 직접 대화하지는 않습니닀. + +작성하는 쪜은 서버입니닀. 혞슀튞는 닀륞 누군가의 제품입니닀. SDK는 `Client`도 핚께 제공합니닀. 서버륌 테슀튞할 때 사용하며, 읎 페읎지 뒀쪜에서 닀시 등장합니닀. + +## 섞 가지 프늬믞티람 {#the-three-primitives} + +서버가 녞출하는 것은 정확히 섞 종류입니닀. 읎듀을 가륎는 Ʞ쀀은 **누가 사용을 결정하는가**입니닀. + +| 프늬믞티람 | 제얎 죌첎 | 섀명 | 예시 | +|---------------|-----------------|-----------------------------------------------------|------------------------------------| +| **도구** | 몚덞 | 몚덞읎 동작을 수행하Ʞ 위핎 혞출하는 핚수 | API 혞출, 데읎터베읎슀 ì“°êž° | +| **늬소슀** | 애플늬쌀읎션 | 혞슀튞가 몚덞의 컚텍슀튞로 불러였는 데읎터 | 파음 낎용, API 응답 | +| **프롬프튞** | 사용자 | 사용자가 읎늄윌로 혞출하는 재사용 가능한 메시지 템플늿 | 슬래시 명령, 메뉎 항목 | + +읎 구분의 핵심은 바로 "제얎 죌첎"입니닀. 도구는 **몚덞**읎 혞출하Ʞ로 결정했Ʞ 때묞에 싀행됩니닀. 늬소슀는 **애플늬쌀읎션**읎 몚덞에 필요하닀고 판닚했Ʞ 때묞에 첚부됩니닀. 프롬프튞는 **사용자**가 선택했Ʞ 때묞에 싀행됩니닀. + +!!! info + 웹 API륌 만듀얎 볞 적읎 있닀멎 필요한 감은 대부분 읎믞 갖춘 셈입니닀. **늬소슀**는 `GET`읎고 + (데읎터륌 읜얎 올 뿐 아묎것도 바꟞지 않습니닀), **도구**는 `POST`입니닀(작업을 수행하며 부수 + 횚곌가 있을 수 있습니닀). **프롬프튞**에 대응하는 HTTP 개념은 없윌며, 사용자가 읎늄윌로 싀행하는 + 저장된 쿌늬에 더 가깝습니닀. + +## 하나의 서버, 섞 가지 몚두 {#one-server-all-three} + +```python title="server.py" hl_lines="6 12 18" +--8<-- "docs_src/first_steps/tutorial001.py" +``` + +평범한 핚수 섞 개와 데윔레읎터 섞 개입니닀. 각 데윔레읎터가 등록의 전부입니닀. + +* `@mcp.tool()` 데윔레읎터는 `add`륌 **도구**로 만듭니닀. +* `@mcp.resource("greeting://{name}")` 데윔레읎터는 `greeting`을 **늬소슀 템플늿**윌로 만듭니닀. URI의 `{name}`읎 핚수의 맀개변수가 됩니닀. +* `@mcp.prompt()` 데윔레읎터는 `summarize`륌 **프롬프튞**로 만듭니닀. 반환하는 묞자엎읎 사용자 메시지가 됩니닀. + +나뚞지(읎늄, 섀명, 읞자 슀킀마)는 SDK가 핚수 자첎에서 읜얎 냅니닀. 핚수 읎늄, 독슀튞링, 타입 힌튞가 ê·ž 출처입니닀. 귞쀑 얎느 것도 따로 선얞하지 않았습니닀. + +!!! tip + SDK의 두 축은 임포튞 겜로도 둘로 나뉩니닀. `from mcp import Client`와 + `from mcp.server import MCPServer`입니닀. `from mcp import MCPServer`는 없습니닀. + +### 직접 핮 볎Ʞ {#try-it} + +MCP Inspector로 싀행하섞요. + +```console +uv run mcp dev server.py +``` + +출력된 URL을 여섞요. Inspector에는 프늬믞티람마닀 탭읎 하나씩 있윌니 순서대로 삎펎볎멎 됩니닀. + +**도구.** 항목은 하나, *Add two numbers.*띌는 섀명읎 붙은 `add`입니닀. 폌에는 `a`와 `b`륌 위한 필수 정수 필드가 각각 하나씩 있습니닀. 값을 채우고 혞출하멎 결곌는 `3`입니닀. Inspector는 `a: int, b: int`로부터 ê·ž 폌을 만듀었습니닀. 닀륞 몚든 큎띌읎얞튞도 마찬가지입니닀. + +**늬소슀.** *Resources* 목록은 비얎 있습니닀. `greeting`은 **Resource Templates** 아래에 있는데, `greeting://{name}`에 맀개변수가 있Ʞ 때묞입니닀. 누군가 `name`을 제공하Ʞ 전까지는 목록에 올늎 구첎적읞 늬소슀가 없습니닀. `World`륌 넣고 읜얎 볎섞요. + +```text +Hello, World! +``` + +**프롬프튞.** 항목은 하나, 필수 읞자 `text` 하나륌 받는 `summarize`입니닀. 적당한 텍슀튞륌 넣얎 가젞였멎 `role: user`와 렌더링된 묞자엎을 낎용윌로 닎은 메시지 하나가 돌아옵니닀. 프롬프튞는 읎게 전부입니닀. 메시지륌 만드는 핚수음 뿐입니닀. + +Inspector는 방ꞈ ê·ž 서버륌 **stdio**로 싀행했습니닀. stdio는 MCP 서버가 사용할 수 있는 튞랜슀포튞 쀑 하나입니닀. 아직 묎엇을 쓞지 고륌 필요는 없습니닀. ê·ž 읎알Ʞ는 **[서버 싀행하Ʞ](../run/index.md)**에서 닀룹니닀. + +## Ʞ능 {#capabilities} + +Inspector에서 탭 섞 개륌 뎀습니닀. Inspector는 탭읎 섞 개띌는 것을 얎떻게 알았을까요? + +큎띌읎얞튞가 연결하멎 서버는 자신의 **Ʞ능**, 슉 ì–Žë–€ 계엎의 요청에 응답할지륌 선얞합니닀. 큎띌읎얞튞는 ê·ž 선얞을 볎고 묎엇을 요청할지 결정합니닀. 읎 선얞은 직접 작성하지 않았습니닀. `MCPServer`가 대신 선얞핎 쀍니닀. + +직접 확읞핎 볎섞요. SDK의 `Client`는 서버 객첎륌 귞대로 받아 **읞메몚늬**로 연결합니닀(하위 프로섞슀도, 포튞도 필요 없습니닀). + +```python +import asyncio + +from mcp import Client + +from server import mcp + + +async def main() -> None: + async with Client(mcp) as client: + print(client.server_capabilities.model_dump(exclude_none=True)) + + +asyncio.run(main()) +``` + +```text +{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}} +``` + +읎 딕셔너늬가 서버가 선얞한 **Ʞ능**입니닀. 연결하는 몚든 큎띌읎얞튞가 가장 뚌저 알게 되는 정볎입니닀. + +| Ʞ능 | 큎띌읎얞튞가 혞출할 수 있게 되는 것 | +|-------------|------------------------------------------------------------| +| `tools` | `tools/list`, `tools/call` | +| `resources` | `resources/list`, `resources/templates/list`, `resources/read` | +| `prompts` | `prompts/list`, `prompts/get` | + +`MCPServer`는 섞 프늬믞티람륌 몚두 제공하므로 셋 ë‹€ 항상 선얞됩니닀. + +ë¹ ì ž 있는 것도 눈여겚볎섞요. `completions`(늬소슀 템플늿곌 프롬프튞의 읞자 자동 완성)는 직접 작성한 핞듀러가 필요한데 읎 서버에는 없윌므로, 핎당 Ʞ능은 선얞되지 않고 규앜을 잘 지킀는 큎띌읎얞튞는 요청하지 않습니닀. 선택적읞 몚든 것에 같은 규칙읎 적용됩니닀. 핎당 항목을 등록하멎 Ʞ능읎 나타나며, **[자동 완성](../servers/completions.md)**읎 읎륌 볎여 쀍니닀. + +!!! info + `Client(mcp)`는 읎 묞서의 몚든 예제륌 테슀튞할 때 쓰는 바로 ê·ž 읞메몚늬 큎띌읎얞튞읎며, 직접 만든 + 서버도 읎것윌로 테슀튞하게 됩니닀. 여Ʞ에는 페읎지 하나가 통짞로 할애되얎 있습니닀. **[테슀튞](testing.md)**륌 확읞하섞요. + +## 작성하지 않은 것 {#what-you-did-not-write} + +읎 페읎지륌 되짚얎 볎섞요. 작성한 것은 작은 Python 핚수 섞 개뿐입니닀. 닀음은 작성하지 **않았습니닀**. + +* JSON 슀킀마. `add`의 슀킀마는 `a: int, b: int` ê·ž 자첎입니닀. +* 요청 핞듀러. `tools/list`, `resources/read`, `prompts/get` 몚두 대신 처늬됩니닀. +* Ʞ능 ì„ ì–ž. `MCPServer`가 만듀얎 죌었습니닀. +* 프로토윜 윔드 한 쀄. 버전 협상, JSON-RPC 프레읎밍, Ʞ능 교환까지 몚두 `mcp dev`와 `Client(mcp)` 낎부에서 음얎났고, 겉윌로 드러나지 않았습니닀. + +읎 비윚읎 바로 SDK의 졎재 읎유입니닀. + +## 요앜 {#recap} + +* **혞슀튞**는 LLM 앱읎고, **큎띌읎얞튞**는 귞쀑 MCP로 통신하는 부분읎며, **서버**는 직접 만드는 것입니닀. +* 도구는 **몚덞**읎, 늬소슀는 **애플늬쌀읎션**읎, 프롬프튞는 **사용자**가 제얎합니닀. +* 프늬믞티람마닀 데윔레읎터 하나씩입니닀. `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`. 읎늄, 섀명, 슀킀마는 핚수에서 가젞옵니닀. +* `{param}`읎 듀얎간 URI는 늬소슀 **템플늿**읎 되며, 구첎적읞 늬소슀와는 따로 나엎됩니닀. +* 서버의 **Ʞ능**은 대신 선얞되며, 큎띌읎얞튞는 서버가 선얞한 것만 요청합니닀. +* `Client(mcp)`는 서버 객첎에 읞메몚늬로 연결합니닀. 첫날부터 쓞 수 있는 테슀튞 도구입니닀. + +닀음은 **[싀제 혞슀튞에 연결하Ʞ](real-host.md)**입니닀. 지ꞈ 만든 서버륌 Claude Desktop읎나 IDE 안에서 싀제로 동작시쌜 뎅니닀. 귞닀음은 **[테슀튞](testing.md)**입니닀. 페읎지 하나와 읞메몚늬 큎띌읎얞튞 하나멎 제대로 동작하는지 짐작할 음읎 없얎집니닀. 읎후에는 프늬믞티람마닀 페읎지가 하나씩 읎얎지며, 몚덞읎 죌도하는 **[도구](../servers/tools.md)**부터 시작합니닀. diff --git a/i18n/ko/pages/handlers/elicitation.md b/i18n/ko/pages/handlers/elicitation.md new file mode 100644 index 0000000000..e50dd0ff24 --- /dev/null +++ b/i18n/ko/pages/handlers/elicitation.md @@ -0,0 +1,185 @@ +# 엘늬시테읎션 {#elicitation} + +작업을 절반쯀 진행한 도구가 답 하나륌 얻지 못했닀고 핎서 싀팚할 필요는 없습니닀. + +**엘늬시테읎션(elicitation)**을 사용하멎 도구가 직접 묌얎볌 수 있습니닀. 도구 혞출 도쀑에 사용자에게 질묞읎 전달되고, ê·ž 답은 같은 핚수 혞출 안윌로 돌아옵니닀. + +몚드는 두 가지입니닀. + +* **폌 몚드**: 값읎 필요한 겜우(확읞, 날짜, 수량). 필드륌 섀명하멎 큎띌읎얞튞가 폌을 렌더링합니닀. +* **URL 몚드**: 사용자륌 닀륞 곳윌로 볎낎알 하는 겜우(OAuth 동의 화멎, 결제 페읎지). 거Ʞ서 하는 음은 프로토윜을 전혀 거치지 않습니닀. + +귞늬고 묻는 방법도 두 가지입니닀. 뚌저 손읎 가알 할 쪜은 **늬졞버**입니닀. 질묞을 파띌믞터에 달아 두멎 SDK가 대신 묻습니닀. ì–Žë–€ 연결에서든, 큎띌읎얞튞가 얎느 시대의 프로토윜을 사용하든 동작합니닀. 직접적읞 방법읞 `await ctx.elicit(...)`은 **서버**가 **큎띌읎얞튞**에게 볎낎는 요청읎며, 읎 채널은 레거시 연결의 큎띌읎얞튞에만 졎재합니닀(사양 버전 2025-11-25 읎하). 읎 페읎지에서는 둘 ë‹€ 닀룚지만, 늬졞버부터 시작하섞요. + +## 늬졞버로 묻Ʞ {#ask-with-a-resolver} + +도구 전첎륌 좌우하는 질묞, 읎륌테멎 **정말 ꎜ찮습니까? 음치하는 섞 계정 쀑 얎느 것입니까?** 같은 질묞은 도구 볞묞 밖윌로 빌낎 **늬졞버**에 둘 수 있고, 귞러멎 프레임워크가 대신 묌얎뎅니닀. + +`Annotated[T, Resolve(fn)]`로 애녞테읎션된 파띌믞터는 도구 볞묞볎닀 뚌저 `fn`을 싀행핎 채워집니닀. 늬졞버는 값을 읎믞 알고 있윌멎 귞대로 반환하고, 프레임워크가 묻게 하렀멎 `Elicit(...)`을 반환합니닀. + +```python title="server.py" hl_lines="24-30 35-36" +--8<-- "docs_src/elicitation/tutorial004.py" +``` + +* `confirm_delete`는 도구 자신의 `path` 읞자륌 읎늄윌로 읜얎 폮더 목록을 확읞하고, **ꌭ 필요할 때만 사용자에게 묻습니닀**. 빈 폎더띌멎 큎띌읎얞튞와 왕복 없읎 `Confirm(ok=True)`로 핎결됩니닀. +* `delete_folder`는 `ElicitationResult[Confirm]`윌로 애녞테읎션하므로 프레임워크가 결곌 전첎륌 죌입하고, 도구는 몚든 겜우륌 `match`로 처늬합니닀. 수띜 후 확읞, 수띜했지만 유지(`ok=False`), 거절, 췚소가 귞것입니닀. +* `confirm` 파띌믞터는 도구의 입력 슀킀마에 전혀 나타나지 않습니닀. `path`는 큎띌읎얞튞가, `confirm`은 늬졞버가 제공합니닀. + +도구가 ë¶„êž°í•  필요가 없닀멎 감싞지 않은 몚덞(`Annotated[Confirm, Resolve(confirm_delete)]`)로 애녞테읎션하섞요. 수띜 시에는 몚덞을 받고, 거절읎나 췚소 시에는 혞출읎 였류와 핚께 쀑닚됩니닀. + +늬졞버는 **몚든** 연결에서 동작합니닀. 레거시 연결의 큎띌읎얞튞에는 SDK가 질묞을 직접 볎낎고, **2026-07-28** 연결에서는 SDK가 혞출의 결곌로 질묞을 **반환**하며 큎띌읎얞튞의 닀음 시도가 답을 싀얎 옵니닀. 늬졞버는 ê·ž 찚읎륌 전혀 알지 못합니닀. ê·ž 아래에서 벌얎지는 음은 **[닀쀑 왕복 요청](multi-round-trip.md)**에서 닀룹니닀. + +묻는 것은 늬졞버가 할 수 있는 음 쀑 하나음 뿐입니닀. 묻지 않고 계산하는 의졎성, 의졎성의 의졎성, 몚덞읎 제공할 수 있는 것곌 없는 것 같은 음반적읞 메컀니슘은 **[의졎성](dependencies.md)** 페읎지에서 닀룹니닀. + +## 도구 낎부에서 묻Ʞ {#ask-from-inside-the-tool} + +도구는 자신의 볞묞 도쀑에 멈춰서 직접 묌얎볌 수도 있습니닀. + +!!! warning + `ctx.elicit()`곌 `ctx.elicit_url()`은 **서버**가 **큎띌읎얞튞**에게 볎낎는 요청읎며, + 읎 채널은 레거시 연결의 큎띌읎얞튞에만 졎재합니닀(사양 버전 **2025-11-25** 읎하). + **2026-07-28** 연결에는 서버가 시작하는 요청읎 없윌므로 읎 혞출은 싀팚합니닀. + 늬졞버는 양쪜 몚두에서 동작합니닀. 자섞한 낎용은 **[프로토윜 버전](../protocol-versions.md)**에서 + 확읞하섞요. + +`await ctx.elicit()`은 메시지와 Pydantic 몚덞을 받습니닀. + +```python title="server.py" hl_lines="9-11 20-23 25" +--8<-- "docs_src/elicitation/tutorial001.py" +``` + +* `ctx.elicit`을 쓞 수 있게 핮 죌는 것은 **`Context`** 파띌믞터읎며, ì–Žë–€ 도구든 읎 파띌믞터륌 받을 수 있습니닀. 읎 객첎는 별도 페읎지읞 **[Context](context.md)**에서 닀룹니닀. +* `AlternativeDate`는 받고 싶은 답의 **슀킀마**입니닀. +* 읎 도구는 `async def`입니닀. 귞럎 수밖에 없습니닀. 쀑간에 멈춰서 사람을 Ʞ닀늬Ʞ 때묞입니닀. +* 닀륞 날짜띌멎 도구는 곧바로 반환합니닀. ꌭ 필요할 때만 묻습니닀. +* 사용자가 수띜한 날짜는 `book_table` 자첎륌 닀시 거칩니닀. 답도 닀륞 입력곌 똑같읎 췚꞉되므로, 대안 날짜마저 예앜읎 ꜉ 찌닀멎 묎턱대고 확정하지 않고 닀시 묌얎뎅니닀. + +### 큎띌읎얞튞가 받는 것 {#what-the-client-receives} + +큎띌읎얞튞는 메시지와 핚께 몚덞에서 생성된 JSON Schema륌 받습니닀. + +```json +{ + "properties": { + "accept_alternative": { + "description": "Try another date?", + "title": "Accept Alternative", + "type": "boolean" + }, + "date": { + "default": "2025-12-26", + "description": "Alternative date (YYYY-MM-DD)", + "title": "Date", + "type": "string" + } + }, + "required": ["accept_alternative"], + "title": "AlternativeDate", + "type": "object" +} +``` + +읎 슀킀마가 ê³§ 폌입니닀. `Field(description=...)`은 레읎랔읎 되고, Ʞ볞값은 입력란을 믞늬 채우멎서 핎당 필드륌 선택 사항윌로 만듭니닀. **[도구](../servers/tools.md)**에서 도구 읞자륌 두고 섀명한 것곌 같은 Pydantic-to-JSON-Schema 장치입니닀. + +!!! warning + 엘늬시테읎션 슀킀마는 도구의 입력 슀킀마만큌 표현력읎 높지 않습니닀. 평평한 Ʞ볞 타입 필드만 + 허용됩니닀. `str`, `int`, `float`, `bool`, 또는 묞자엎 `Literal`(읎는 `enum`읎 됩니닀)입니닀. + 몚덞 안에 몚덞을 넣윌멎 큎띌읎얞튞로 아묎것도 전송되Ʞ 전에 `ctx.elicit`읎 예왞륌 발생시킵니닀. + + ```text + TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition + ``` + + 읎 Ʞ능은 작업 쀑읞 사람을 가로막는 음입니닀. 답에 쀑첩 구조가 필요하닀멎, 귞것은 애쎈에 + 도구의 읞자여알 했습니닀. + +### 섞 가지 답 {#the-three-answers} + +`result.action`은 사용자가 묎엇을 했는지 알렀죌며, 가능한 값은 정확히 섞 가지입니닀. + +* `"accept"`: 사용자가 폌을 제출했습니닀. `result.data`는 읎믞 검슝을 마친 `AlternativeDate` 읞슀턎슀입니닀. +* `"decline"`: 사용자가 거절했습니닀. +* `"cancel"`: 사용자가 선택하지 않고 질묞을 닫았습니닀. + +`result.data`는 `"accept"`음 때만 졎재하므로, 예제는 `result.action`을 뚌저 확읞합니닀. 타입 검사Ʞ가 읎 순서륌 강제합니닀. `result.action == "accept"` 읎후에 `result.data`는 `AlternativeDate`읎고, ê·ž 전에는 `.data` 자첎가 없습니닀. + +거절은 였류가 아닙니닀. 거절읎 묎엇을 뜻하는지(여Ʞ서는 예앜하지 않음)는 도구가 정하고, 몚덞에는 평소처럌 답합니닀. + +!!! tip + 답은 윔드가 볎Ʞ 전에 몚덞을 Ʞ쀀윌로 검슝됩니닀. `bool` 자늬에 `"maybe"`륌 볎낎는 + 큎띌읎얞튞가 있얎도 예앜은 망가지지 않습니닀. 혞출읎 슀킀마 불음치 였류로 싀팚하므로 + 작성한 `if` 묞은 싀행되지 않습니닀. + +## 사용자륌 URL로 볎낎Ʞ {#send-the-user-to-a-url} + +자격 슝명, 칎드 번혞, OAuth 동의처럌 몚덞읎나 큎띌읎얞튞륌 거쳐서는 안 되는 것듀읎 있습니닀. 읎런 겜우에는 데읎터륌 요청하는 대신 사용자에게 얎딘가로 읎동핎 달띌고 요청합니닀. + +```python title="server.py" hl_lines="10-14 23" +--8<-- "docs_src/elicitation/tutorial002.py" +``` + +* `ctx.elicit_url()`은 메시지, 방묞할 **URL**, 귞늬고 직접 정하는 `elicitation_id`륌 받습니닀. `elicitation_id`는 서버 안에서 읎 엘늬시테읎션을 식별하는 임의의 묞자엎입니닀. +* 결곌에는 액션만 있고 ê·ž 밖에는 아묎것도 없습니닀. `"accept"`는 사용자가 URL 엎Ʞ에 동의했닀는 뜻읎지, ê·ž 너뚞의 음을 끝냈닀는 뜻은 **아닙니닀**. +* 결제는 대역 왞에서, 사용자의 람띌우저와 결제 제공자 사읎에서 읎룚얎집니닀. ì–Žë–€ 낎용도 MCP륌 통핎 돌아였지 않습니닀. + +두 번짞 도구륌 볎섞요. 대역 왞 흐늄읎 끝났닀는 사싀을 서버가 알게 되멎(웹훅읎나 폎링윌로, 여Ʞ서는 두 번짞 도구로 몚덞링했습니닀) `ctx.session.send_elicit_complete(...)`가 같은 `elicitation_id`로 `notifications/elicitation/complete`륌 볎냅니닀. 큎띌읎얞튞는 읎륌 통핎 **"결제 대Ʞ 쀑..."** 표시륌 멈춰도 된닀는 것을 알게 됩니닀. 읎 알늌읎 없윌멎 큎띌읎얞튞는 추잡할 수밖에 없습니닀. + +## 큎띌읎얞튞 쪜 {#the-client-side} + +서버가 묻고 큎띌읎얞튞가 답합니닀. 큎띌읎얞튞는 `Client(...)`에 **`elicitation_callback`**을 전달핎 답합니닀. + +```python title="client.py" hl_lines="6-7 18" +--8<-- "docs_src/elicitation/tutorial003.py" +``` + +* 윜백 하나가 두 몚드륌 몚두 처늬합니닀. `params`는 `ElicitRequestFormParams`와 `ElicitRequestURLParams`의 유니옚읎며, `isinstance`로 분Ʞ합니닀. +* URL 몚드에서는 `params.url`을 사용자에게 볎여죌고 사용자가 선택한 액션을 반환합니닀. `content`는 절대 반환하지 않습니닀. +* 폌 몚드에서는 싀제 애플늬쌀읎션읎띌멎 `params.requested_schema`륌 렌더링하고 사용자의 입력을 `content`로 반환합니닀. 여Ʞ 있는 윜백은 믞늬 정핎 둔 답윌로 항상 수띜하는데, 테슀튞에는 바로 읎런 윜백읎 적합합니닀. +* 윜백을 전달하는 것은 ê³§ **Ʞ능 ì„ ì–ž**읎Ʞ도 합니닀. 서버는 읎륌 통핎 읎 큎띌읎얞튞에 질묞할 수 있닀는 사싀을 알게 됩니닀. 큎띌읎얞튞가 서버륌 대신핎 답할 수 있는 나뚞지 항목은 **[큎띌읎얞튞 윜백](../client/callbacks.md)**에서 닀룹니닀. + +!!! info + 엘늬시테읎션은 **서버**가 **큎띌읎얞튞**에게 볎낎는 요청읎고, 읎런 요청은 큎래식 핞드셰읎크 + 섞션에서만 졎재합니닀. 귞래서 읎 큎띌읎얞튞는 `mode="legacy"`륌 전달합니닀. + **2026-07-28** 연결에서는 도구가 혞출의 결곌로 질묞을 **반환**핮 묻습니닀. + ê·ž 흐늄은 **[닀쀑 왕복 요청](multi-round-trip.md)**에서 닀룹니닀. + +### 직접 핮 볎Ʞ {#try-it} + +`ctx.elicit` 폌 몚드 `server.py`(`book_table`읎 있는 쪜)륌 Streamable HTTP로 싀행하고(한 쀄 명령은 **[서버 싀행하Ʞ](../run/index.md)**에 있습니닀), 큎띌읎얞튞의 `main()`을 싀행핎 `book_table`에 크늬슀마슀 당음을 요청하섞요. + +윜백은 전달받은 질묞을 출력합니닀. + +```text +No tables for 2 on 2025-12-25. Would you like to try another date? +``` + +윜백읎 `{"accept_alternative": True, "date": "2025-12-27"}`윌로 답하멎, 귞동안 계속 `await ctx.elicit(...)` 안에서 Ʞ닀늬던 도구가 예앜을 마칩니닀. + +```text +Booked a table for 2 on 2025-12-27. +``` + +읎제 URL 몚드 `server.py`로 바꟞고 같은 `main()`을 `pay_deposit`윌로 향하게 핮 볎섞요. 같은 윜백읎 닀륞 분Ʞ륌 타서 결제 링크륌 출력하고, 도구는 **"Complete the payment in your browser."**륌 돌렀쀍니닀. 혞출 도쀑에 양방향윌로 한 번 왕복한 것입니닀. + +!!! check + 읎제 `Client`에서 `elicitation_callback=`을 제거하고 크늬슀마슀 당음로 `book_table`을 + 닀시 혞출핎 볎섞요. 혞출 전첎가 프로토윜 였류로 싀팚합니닀. + + ```text + Elicitation not supported + ``` + + 윜백을 등록하지 않은 큎띌읎얞튞는 `elicitation` Ʞ능을 선얞하지 않았윌므로 묌얎볌 상대가 + 없습니닀. 도구가 받은 것은 `"decline"`읎 아니띌 예왞입니닀. 읎륌 엌두에 두고 섀계하섞요. + 몚든 엘늬시테읎션에는 "묌얎볌 수 없윌멎 얎떻게 할 것읞가"에 대한 합늬적읞 답읎 필요합니닀. + +## 정늬 {#recap} + +* `Annotated[T, Resolve(fn)]`로 애녞테읎션된 파띌믞터는 늬졞버가 채우며, 늬졞버는 묌얎뎐알 할 때 `Elicit(...)`을 반환합니닀. 몚든 연결에서 동작합니닀. +* 슀킀마는 평평한 Pydantic 몚덞입니닀. Ʞ볞 타입 필드만 쓞 수 있고, 돌아였는 Ꞟ에 검슝됩니닀. +* `result.action`은 `"accept"`, `"decline"`, `"cancel"` 쀑 하나읎며, `result.data`는 수띜음 때만 졎재합니닀. +* `await ctx.elicit(message, schema=Model)`은 도구 볞묞 안에서 묻고, `await ctx.elicit_url(message, url, elicitation_id)`은 몚덞을 거쳐서는 안 되는 몚든 것에 사용합니닀(`ctx.session.send_elicit_complete(elicitation_id)`는 대역 왞 작업읎 끝났음을 알늜니닀). 둘 ë‹€ 서버에서 큎띌읎얞튞로 볎낎는 요청읎므로 큎띌읎얞튞가 레거시 연결읎얎알 합니닀. +* 큎띌읎얞튞는 `elicitation_callback` 하나로 답하며, params 타입에 따띌 분Ʞ합니닀. 읎 윜백을 등록하는 것읎 ê³§ Ʞ능 선얞입니닀. +* 2026-07-28 연결에서는 서버가 질묞을 밀얎 볎낎지 않고 반환하며, 같은 윜백에 질묞을 전달하는 것은 **[닀쀑 왕복 요청](multi-round-trip.md)**입니닀. + +ê·ž 반환 아래에서 벌얎지는 몚든 것(재시도 룚프, `requestState` 볎혞, 직접 구동하Ʞ)은 **[닀쀑 왕복 요청](multi-round-trip.md)**에서 닀룹니닀. diff --git a/i18n/ko/pages/index.md b/i18n/ko/pages/index.md new file mode 100644 index 0000000000..377517609a --- /dev/null +++ b/i18n/ko/pages/index.md @@ -0,0 +1,97 @@ +# MCP Python SDK {#mcp-python-sdk} + +!!! info "읎 묞서는 현재 안정 버전 띌읞읞 v2륌 닀룹니닀" + v2륌 처음 접하거나 v1에서 넘얎왔닀멎, **[v2에서 달띌진 점](whats-new.md)**에서 변겜 사항을 5분 만에 훑얎볌 수 있고, **[마읎귞레읎션 가읎드](migration.md)**에서 몚든 혞환성 파ꎎ 변겜을 확읞할 수 있습니닀. + 아직 v1.x륌 사용 쀑읎띌멎 핎당 묞서는 [v1.x 묞서](https://py.sdk.modelcontextprotocol.io/v1/)에 있습니닀. + 불펞하거나 헷갈늬는 부분읎 있닀멎 [알렀죌섞요](https://github.com/modelcontextprotocol/python-sdk/issues/new?template=v2-feedback.yaml). + +**Model Context Protocol(MCP)**은 애플늬쌀읎션읎 표쀀화된 방식윌로 LLM에 컚텍슀튞륌 제공하게 핎죌며, 컚텍슀튞륌 *제공하는* 음곌 LLM곌 상혞작용하는 음을 분늬합니닀. + +읎 묞서는 ê·ž 공식 Python SDK륌 닀룹니닀. 읎 SDK로 닀음을 할 수 있습니닀. + +* 몚든 MCP 혞슀튞에 도구, 늬소슀, 프롬프튞륌 녞출하는 **MCP 서버 만듀Ʞ**. +* 몚든 MCP 서버에 연결하는 **MCP 큎띌읎얞튞 만듀Ʞ**. +* 몚든 표쀀 튞랜슀포튞 사용하Ʞ: stdio, Streamable HTTP, SSE. + +## 요구 사항 {#requirements} + +Python 3.10 읎상. + +## 섀치 {#installation} + +=== "uv" + + ```bash + uv add "mcp[cli]" + ``` + +=== "pip" + + ```bash + pip install "mcp[cli]" + ``` + +`[cli]` 엑슀튞띌륌 섀치하멎 `mcp` 명령을 사용할 수 있윌며, 개발할 때 필요합니닀. +각 의졎성읎 ì–Žë–€ 역할을 하는지는 [섀치](get-started/installation.md)에서 확읞하섞요. + +## 예제 {#example} + +### 만듀Ʞ {#create-it} + +`server.py` 파음을 만드섞요. + +```python title="server.py" +--8<-- "docs_src/index/tutorial001.py" +``` + +읎것윌로 완전한 MCP 서버가 완성됩니닀. + +읎 서버는 **도구** `add` 하나와 템플늿 **늬소슀** `greeting://{name}` 하나륌 녞출합니닀. + +### 싀행하Ʞ {#run-it} + +```console +uv run mcp dev server.py +``` + +읎 명령은 서버륌 싀행하고, 서버륌 직접 조작핎 볌 수 있는 대화형 UI읞 [MCP Inspector](https://github.com/modelcontextprotocol/inspector)륌 엜니닀. 출력된 URL을 여섞요. + +!!! note + Inspector는 Node.js 앱읎므로 `mcp dev`륌 싀행하렀멎 `PATH`에 `npx`가 있얎알 합니닀. + +### 사용핎 볎Ʞ {#try-it} + +Inspector에서 **Tools**로 읎동핎 `a=1`, `b=2`로 `add`륌 혞출하섞요. + +`3`읎 돌아옵니닀. + +Inspector는 타입 힌튞륌 볎고 ê·ž 폌(`a`에 필요한 정수 필드 하나, `b`에 또 하나)을 만듀었습니닀. Claude륌 비롯한 닀륞 몚든 MCP 혞슀튞도 마찬가지로 동작합니닀. + +읎제 **Resources**로 읎동핎 `greeting://World`륌 읜얎 볎섞요. + +```text +Hello, World! +``` + +### 정늬 {#recap} + +작성하지 **않은** 것을 닀시 삎펎볎섞요. + +* JSON Schema가 없습니닀. `a: int, b: int`가 *ê³§* 슀킀마입니닀. +* 요청 파싱도, 직렬화도, 검슝 윔드도 없습니닀. +* 프로토윜 처늬 윔드는 전혀 없습니닀. + +타입 힌튞와 독슀튞링읎 있는 Python 핚수 두 개륌 작성했을 뿐입니닀. 나뚞지는 SDK가 처늬합니닀. + +## 닀음윌로 볌 낎용 {#where-to-go-next} + +* **[시작하Ʞ](get-started/index.md)**는 섀치부터 동작하고 테슀튞까지 마친 서버까지 안낎합니닀. +* MCP 서버륌 *사용하는* 애플늬쌀읎션을 만든닀멎 **[큎띌읎얞튞](client/index.md)**부터 시작하섞요. +* 읎믞 FastAPI나 Starlette 앱읎 있닀멎, **[Ʞ졎 앱에 추가하Ʞ](run/asgi.md)**에서 ê·ž 안에 MCP 서버륌 마욎튞할 수 있습니닀. +* 특정 였류 메시지륌 ì°Ÿê³  있닀멎, **[묞제 핎결](troubleshooting.md)**읎 메시지 원묞을 Ʞ쀀윌로 정늬되얎 있습니닀. +* v2에서 묎엇읎 바뀌었는지 궁ꞈ하닀멎, **[v2에서 달띌진 점](whats-new.md)**에서 5분 만에 훑얎볌 수 있습니닀. +* v1에서 마읎귞레읎션한닀멎 **[마읎귞레읎션 가읎드](migration.md)**부터 시작하섞요. +* 정확한 시귞니처륌 ì°Ÿê³  있닀멎, **[API 레퍌런슀](api/mcp/index.md)**가 소슀에서 생성되얎 있습니닀. +* LLM곌 핚께 읜고 있닀멎, 읎 묞서는 [llms.txt](https://llmstxt.org/) 형식윌로도 제공됩니닀. + [llms.txt](https://py.sdk.modelcontextprotocol.io/llms.txt)는 페읎지 목록읎고, + [llms-full.txt](https://py.sdk.modelcontextprotocol.io/llms-full.txt)에는 몚든 페읎지가 하나의 파음에 닎겚 있습니닀. diff --git a/i18n/ko/state.json b/i18n/ko/state.json new file mode 100644 index 0000000000..27b839bef9 --- /dev/null +++ b/i18n/ko/state.json @@ -0,0 +1,164 @@ +{ + "pages": { + "get-started/first-steps.md": { + "block_hashes": [ + "f4e9527fee44ad246985003e8319e71c2b72f28e3301ee128f3d2c0e3f6d264d", + "3e1766d27f443ca4bbccdda3389f3dff2d2b6737133564ee0ca133a5dbfd958d", + "2a97f1d75b49d9a9b70e82ce3ad0ee70072b2676db3248614ae2ba29bc3fff28", + "59dc6c66de1c4ea589e4b984889814b842613605c638ae450db4fa44243aee06", + "ee42a5e18f8e868b5d29bb9db85915406e7e97e789013ee108ee2d199626d274", + "7d8344d6cf1ecfc4130812f643a148ea572ad7d319b371029197fe162d513602", + "cfcd0cd4aed4f275cb8cf632eed301192aed12aeb11c10c77fabab2fe0fc7b30" + ], + "inputs_hash": "7b1e7bbf5eab17443d0f3afe129549118e19d3dc6d408ac1704c19e59c3a09ca", + "model": "claude-opus-5", + "source_hash": "3531c5686a6ff79aabe6f0f8a3cb96213b6fc13b0bbbb0e762dc9f4a827b4be3", + "translated_at": "2026-07-31T19:11:09Z" + }, + "handlers/elicitation.md": { + "block_hashes": [ + "f5dade066115eb33121f7bbb02a3fb288e95e2662001bea5eac322e23f4cbd1d", + "c368d8a40fd8d9879d90fbb10a81870fdb3ca84675bcf04c91740febe56ed4c4", + "191a9cfc619f523acf23ef20a04a9a318abe5280bb459f4691dfc2c60bc8dc34", + "ed47c27010f781038cd2095fedb17be05b16a9c2b1b93650416dd7e1be3154b7", + "4db8b6b1183f96319677911fd22baa3e65faf1b2f107b7d0929a3ee39ebd3ed4", + "0265da2c81bc50cf3a83415aa16c1ded414fe59a3e23e4c0b1a83cd03d41ba5c" + ], + "inputs_hash": "7b1e7bbf5eab17443d0f3afe129549118e19d3dc6d408ac1704c19e59c3a09ca", + "model": "claude-opus-5", + "source_hash": "ef8afd716d136ff25d17a34059a933bb924ea238bd2faf04f80cc0d337c2e520", + "translated_at": "2026-07-31T19:11:09Z" + }, + "index.md": { + "block_hashes": [ + "3690a6eb6f57324f620f65d5242b3bb17c0ed4a5657cd4ba7ff3a28d7291245a", + "13e00a4af763653d8c663c64dd27871bdd23314d11110a143b8ee77357f76a75", + "cfcabc919e5d343b47af2641656a86f9a0abe9f250547368e64f7e4b5a3de4b0", + "c1550578c7bd96be08abb210e1c86261127c78ee09c1d8d666d08fa96271c30b", + "71115a8dc82e7ee1cd61b609da087038d8736629ebb365972c88010fd677cc00" + ], + "inputs_hash": "7b1e7bbf5eab17443d0f3afe129549118e19d3dc6d408ac1704c19e59c3a09ca", + "model": "claude-opus-5", + "source_hash": "c46eefb49de18ed47882e13cf5bf08d0c3ca16ee77b98c257e8b66150c6be666", + "translated_at": "2026-07-31T21:09:57Z" + } + }, + "ui": { + "inputs_hash": "7b1e7bbf5eab17443d0f3afe129549118e19d3dc6d408ac1704c19e59c3a09ca", + "model": "claude-opus-5", + "source": { + "@banner:disclosure": "??? info \"This page was machine-translated\"\n Translations of this documentation are generated automatically from the English pages, and the [English version of this page]({english_url}) is the authoritative one.\n\n Found a translation problem? See [how the translations work and how to report an issue]({translations_url}).\n", + "@banner:outdated": "!!! warning \"This translation may be behind the English page\"\n The English source changed after this page was last translated, so parts of it may be out of date. When in doubt, read the [English version of this page]({english_url}).\n", + "@banner:untranslated": "!!! note \"In English\"\n You're reading this page in English. [How the translations work]({translations_url}).\n", + "API Reference": "API Reference", + "Add to an existing app": "Add to an existing app", + "Advanced": "Advanced", + "Authorization": "Authorization", + "Caching": "Caching", + "Callbacks": "Callbacks", + "Clients": "Clients", + "Completions": "Completions", + "Connect to a real host": "Connect to a real host", + "Dependencies": "Dependencies", + "Deploy & scale": "Deploy & scale", + "Deprecated features": "Deprecated features", + "Elicitation": "Elicitation", + "Extensions": "Extensions", + "First steps": "First steps", + "Get started": "Get started", + "Handling errors": "Handling errors", + "Identity assertion": "Identity assertion", + "Images, audio & icons": "Images, audio & icons", + "Inside your handler": "Inside your handler", + "Installation": "Installation", + "Lifespan": "Lifespan", + "Logging": "Logging", + "MCP Apps": "MCP Apps", + "MCP Python SDK": "MCP Python SDK", + "Middleware": "Middleware", + "Migration Guide": "Migration Guide", + "Multi-round-trip requests": "Multi-round-trip requests", + "Multiple servers": "Multiple servers", + "OAuth": "OAuth", + "OpenTelemetry": "OpenTelemetry", + "Pagination": "Pagination", + "Progress": "Progress", + "Prompts": "Prompts", + "Protocol versions": "Protocol versions", + "Resources": "Resources", + "Running your server": "Running your server", + "Sampling and roots": "Sampling and roots", + "Servers": "Servers", + "Serving legacy clients": "Serving legacy clients", + "Structured Output": "Structured Output", + "Subscriptions": "Subscriptions", + "Testing": "Testing", + "The Context": "The Context", + "The low-level Server": "The low-level Server", + "Tools": "Tools", + "Translations": "Translations", + "Transports": "Transports", + "Troubleshooting": "Troubleshooting", + "URI templates": "URI templates", + "What's new in v2": "What's new in v2" + }, + "source_hash": "219aa0f5adfe57c4403a1b6fdf04d4e2cc79df194bdbcb30958d24e7bb387433", + "strings": { + "@banner:disclosure": "??? info \"읎 페읎지는 Ʞ계 번역되었습니닀\"\n 읎 묞서의 번역은 영얎 페읎지에서 자동윌로 생성되며, [읎 페읎지의 영얎 버전]({english_url})읎 Ʞ쀀입니닀.\n\n 번역에서 묞제륌 발견했닀멎 [번역읎 동작하는 방식곌 묞제륌 볎고하는 방법]({translations_url})을 확읞하섞요.\n", + "@banner:outdated": "!!! warning \"읎 번역은 영얎 페읎지볎닀 였래되었을 수 있습니닀\"\n 읎 페읎지륌 마지막윌로 번역한 읎후 영얎 원묞읎 변겜되얎 음부 낎용읎 였래되었을 수 있습니닀. 확싀하지 않닀멎 [읎 페읎지의 영얎 버전]({english_url})을 읜얎 볎섞요.\n", + "@banner:untranslated": "!!! note \"영얎 페읎지\"\n 읎 페읎지는 영얎로 표시되고 있습니닀. [번역읎 동작하는 방식]({translations_url}).\n", + "API Reference": "API 레퍌런슀", + "Add to an existing app": "Ʞ졎 앱에 추가하Ʞ", + "Advanced": "고꞉", + "Authorization": "읞가", + "Caching": "캐싱", + "Callbacks": "윜백", + "Clients": "큎띌읎얞튞", + "Completions": "자동 완성", + "Connect to a real host": "싀제 혞슀튞에 연결하Ʞ", + "Dependencies": "의졎성", + "Deploy & scale": "배포와 확장", + "Deprecated features": "지원 쀑닚 예정 Ʞ능", + "Elicitation": "엘늬시테읎션", + "Extensions": "확장", + "First steps": "첫걞음", + "Get started": "시작하Ʞ", + "Handling errors": "였류 처늬", + "Identity assertion": "신원 얎섀션", + "Images, audio & icons": "읎믞지, 였디였, 아읎윘", + "Inside your handler": "핞듀러 낎부", + "Installation": "섀치", + "Lifespan": "Lifespan", + "Logging": "로깅", + "MCP Apps": "MCP Apps", + "MCP Python SDK": "MCP Python SDK", + "Middleware": "믞듀웚얎", + "Migration Guide": "마읎귞레읎션 가읎드", + "Multi-round-trip requests": "닀쀑 왕복 요청", + "Multiple servers": "닀쀑 서버", + "OAuth": "OAuth", + "OpenTelemetry": "OpenTelemetry", + "Pagination": "페읎지넀읎션", + "Progress": "진행 상황", + "Prompts": "프롬프튞", + "Protocol versions": "프로토윜 버전", + "Resources": "늬소슀", + "Running your server": "서버 싀행", + "Sampling and roots": "샘플링곌 룚튞", + "Servers": "서버", + "Serving legacy clients": "레거시 큎띌읎얞튞 지원", + "Structured Output": "구조화된 출력", + "Subscriptions": "구독", + "Testing": "테슀튞", + "The Context": "Context", + "The low-level Server": "저수쀀 Server", + "Tools": "도구", + "Translations": "번역", + "Transports": "튞랜슀포튞", + "Troubleshooting": "묞제 핎결", + "URI templates": "URI 템플늿", + "What's new in v2": "v2에서 달띌진 점" + }, + "translated_at": "2026-07-31T21:09:57Z" + } +} diff --git a/i18n/languages.yml b/i18n/languages.yml new file mode 100644 index 0000000000..dcd4bdb247 --- /dev/null +++ b/i18n/languages.yml @@ -0,0 +1,46 @@ +# The translated documentation sites: English at docs/ is the source and each +# entry below is one machine-translated site published next to it at //. +# Read by the docs build (scripts/docs/build_config.py) and by the translation +# tool (scripts/docs/translations.py). docs/translations.md and the translation +# issue form name the languages too — keep them in step when adding one. + +languages: + - code: zh-CN # directory under i18n/ and the site's URL prefix + name: 简䜓䞭文 # native name shown in the language switcher + theme_language: zh # the theme's `theme.language` (UI strings, search) + hreflang: zh-Hans # announced in + - code: ja + name: 日本語 + - code: ko + name: 한국얎 + - code: pt-BR + name: Português (Brasil) + +# Pages the language sites serve in English (as written in mkdocs.yml's nav; +# a trailing /** covers a subtree). The API reference is never translated: the +# language sites link the single English one. +exclude_pages: + - migration.md + +# Publicly documented Claude model IDs for the translation and the meaning +# review; the reviewer is deliberately the stronger of the two. +models: + translate: claude-opus-5 + verify: claude-fable-5 + +# The notices staged onto every page of a language site, in English; the +# tool translates them with each language's UI strings. {english_url} is the +# page's English original and {translations_url} the page explaining how the +# translated sites work. +banners: + disclosure: | + ??? info "This page was machine-translated" + Translations of this documentation are generated automatically from the English pages, and the [English version of this page]({english_url}) is the authoritative one. + + Found a translation problem? See [how the translations work and how to report an issue]({translations_url}). + outdated: | + !!! warning "This translation may be behind the English page" + The English source changed after this page was last translated, so parts of it may be out of date. When in doubt, read the [English version of this page]({english_url}). + untranslated: | + !!! note "In English" + You're reading this page in English. [How the translations work]({translations_url}). diff --git a/i18n/pt-BR/glossary.json b/i18n/pt-BR/glossary.json new file mode 100644 index 0000000000..f18caee835 --- /dev/null +++ b/i18n/pt-BR/glossary.json @@ -0,0 +1,232 @@ +{ + "version": 1, + "keep_in_source_language": [ + "MCP", + "Model Context Protocol", + "MCPServer", + "FastMCP", + "ClientSession", + "Context", + "ctx", + "stdio", + "Streamable HTTP", + "SSE", + "JSON-RPC", + "JSON", + "OAuth", + "PKCE", + "JWT", + "CIMD", + "HTTP", + "HTTPS", + "TLS", + "CORS", + "URI", + "URL", + "ASGI", + "WebSocket", + "API", + "SDK", + "CLI", + "IDE", + "LLM", + "SEP", + "RFC", + "Python", + "TypeScript", + "Node.js", + "PyPI", + "Pydantic", + "Starlette", + "FastAPI", + "uvicorn", + "httpx", + "anyio", + "asyncio", + "trio", + "pytest", + "OpenTelemetry", + "Inspector", + "Claude", + "GitHub", + "VS Code", + "Windows", + "macOS", + "Linux", + "llms.txt", + "2026-07-28", + "2025-11-25", + "2025-06-18", + "2025-03-26" + ], + "terms": [ + { + "source": "tool", + "target": "ferramenta", + "note": "MCP protocol noun (a server exposes tools), feminine: a ferramenta / as ferramentas. Wire identifiers such as `tools/call` and the `@mcp.tool()` decorator are code and stay untouched. First mention on a page may read \"ferramenta (tool)\". Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "resource", + "target": "recurso", + "note": "MCP protocol noun (data a server exposes for reading), masculine: o recurso / os recursos. `resources/read` and `@mcp.resource()` are code. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "prompt", + "target": "prompt", + "note": "MCP protocol noun for the reusable message templates a server exposes, and the general AI sense; kept in English as Brazilian AI writing does. Masculine: o prompt / os prompts. `prompts/get` and `@mcp.prompt()` are code. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "sampling", + "target": "amostragem", + "note": "MCP feature where the server asks the client for an LLM completion. First mention on a page reads \"amostragem (sampling)\" so the reader can map it to `sampling/createMessage`, which is code. Keeping the English word instead is the open alternative — provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "roots", + "target": "roots", + "note": "MCP client capability (the directories a client exposes to the server); kept in English because readers meet it as the identifier `roots/list`. Masculine plural: os roots. May take the gloss \"roots (diretórios raiz)\" on first mention. Translating it as raízes is the open alternative — provisional pending native review.", + "avoid": ["raízes"], + "enforce": false + }, + { + "source": "elicitation", + "target": "elicitação", + "note": "MCP feature where the server asks the user a question through the client (`elicitation/create`, `ctx.elicit()` are code). The Portuguese noun is rare but exact; first mention on a page reads \"elicitação (elicitation)\". Feminine: a elicitação. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "capability", + "target": "capacidade", + "note": "What client and server declare during initialization (\"negociação de capacidades\"). Note that funcionalidade is the word for a feature, which is a different thing. Provisional pending native review.", + "avoid": ["habilidade"], + "enforce": false + }, + { + "source": "transport", + "target": "transporte", + "note": "The connection layer. The individual transports — stdio, Streamable HTTP, SSE — are names on the keep list and stay in English (\"o transporte stdio\"). Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "session", + "target": "sessão", + "note": "Feminine: a sessão / as sessões (\"ID de sessão\"). The `session` object, `ClientSession` and `ServerSession` are code and stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "handler", + "target": "handler", + "note": "The functions you register on a server; kept in English as everyday Brazilian developer usage does. Masculine: o handler / os handlers. \"manipulador\" reads dated in this audience — provisional pending native review.", + "avoid": ["manipulador"], + "enforce": false + }, + { + "source": "dependency", + "target": "dependência", + "note": "Both package dependencies and the SDK's dependency injection — parameters declared with `Resolve(...)` (\"injeção de dependência\"). Feminine: a dependência. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "resolver", + "target": "resolvedor", + "note": "The plain function that fills a `Resolve(...)`-annotated parameter before the tool runs. Rendered as the noun \"resolvedor\" (masculine: o resolvedor) because in Portuguese \"resolver\" is a verb; keeping the English noun is the open alternative — provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "client", + "target": "cliente", + "note": "Masculine: o cliente. The `Client` class and the `mcp.client` module are code and stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "server", + "target": "servidor", + "note": "Masculine: o servidor. The `MCPServer`, `Server` and `ServerSession` classes are code and stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "host", + "target": "host", + "note": "The MCP host — the application the user talks to (Claude Desktop, an IDE) — kept in English as Brazilian developers say it. Masculine: o host / os hosts. Provisional pending native review.", + "avoid": ["anfitrião", "hospedeiro"], + "enforce": true + }, + { + "source": "request", + "target": "requisição", + "note": "HTTP and JSON-RPC requests (\"requisição HTTP\", \"a requisição de inicialização\"). Feminine: a requisição. solicitação is understood too, but use requisição throughout rather than alternating. Provisional pending native review.", + "avoid": ["pedido"], + "enforce": false + }, + { + "source": "token", + "target": "token", + "note": "Kept in English for both OAuth tokens (\"token de acesso\", \"refresh token\") and LLM tokens. Masculine: o token / os tokens. Provisional pending native review.", + "avoid": ["ficha"], + "enforce": false + }, + { + "source": "lifespan", + "target": "lifespan", + "note": "The SDK feature and its `lifespan=` parameter; kept in English so the prose matches the parameter name. Masculine: o lifespan. May take the gloss \"lifespan (ciclo de vida do servidor)\" on first mention. Provisional pending native review.", + "avoid": ["expectativa de vida", "vida útil"], + "enforce": true + }, + { + "source": "callback", + "target": "callback", + "note": "Kept in English, covering both OAuth redirect callbacks and function callbacks. Masculine: o callback / os callbacks. Provisional pending native review.", + "avoid": ["retorno de chamada"], + "enforce": false + }, + { + "source": "deploy", + "target": "deploy", + "note": "Borrow the noun, not a verb: \"o deploy\", \"fazer o deploy\", \"depois do deploy\". Masculine. \"implantação\" is acceptable in formal contexts but pin \"deploy\" for consistency. Provisional pending native review.", + "avoid": ["deployar", "deployado"], + "enforce": true + }, + { + "source": "library", + "target": "biblioteca", + "note": "Feminine: a biblioteca. The false friend \"livraria\" means a bookshop and is never right here.", + "avoid": ["livraria"], + "enforce": true + }, + { + "source": "back-channel", + "target": "canal de retorno", + "note": "This documentation's term for the server calling back into the client during a request. First mention on a page reads \"canal de retorno (back-channel)\" so the reader can connect it to the `NoBackChannelError` exception, which is code. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "file", + "target": "arquivo", + "note": "Masculine: o arquivo. \"ficheiro\" is European Portuguese and is always an error in this Brazilian Portuguese target.", + "avoid": ["ficheiro"], + "enforce": true + }, + { + "source": "user", + "target": "usuário", + "note": "Masculine as the generic form: o usuário / os usuários. \"utilizador\" is European Portuguese and is always an error in this Brazilian Portuguese target.", + "avoid": ["utilizador"], + "enforce": true + } + ] +} diff --git a/i18n/pt-BR/instructions.md b/i18n/pt-BR/instructions.md new file mode 100644 index 0000000000..4fafc0b070 --- /dev/null +++ b/i18n/pt-BR/instructions.md @@ -0,0 +1,190 @@ +# Brazilian Portuguese (pt-BR) — translation instructions + +Target language: Brazilian Portuguese (Português do Brasil), directory and +URL code `pt-BR`, page language tag `pt-BR`. This file is sent verbatim with +every translation request for this language, on top of the shared translation +rules in `../general-prompt.md`. The termbase in `glossary.json` is sent +alongside it and wins any terminology conflict with this file. + +## 1. Register + +Write the casual-neutral register Brazilian developer documentation uses: +professional, relaxed, and direct. + +- Address the reader as **você**, with third-person-singular verb forms to + match. Never o senhor / a senhora, never tu, never vós, and never a mix. + The rule holds in body prose, headings, admonition titles, table cells and + link text. +- Instructions and steps are direct imperatives in the você form: "Install + the SDK, then run the server" → Instale o SDK e depois execute o servidor — + not Instala o SDK (tu form), and not Você deve instalar o SDK (needless + modal). A bare imperative per step is fine; a por favor in front of every + step is not. +- Portuguese drops the subject pronoun freely. Write você where a sentence + needs an explicit subject or a contrast, and let the verb carry the person + otherwise; three or four você in one paragraph is a signal to rephrase. + Object pronouns follow the same person: para você / a você, never the + tu-form te / ti. +- The authorial "we" is nós (Recomendamos, chamamos), never the spoken + a gente. +- The register is uniform across a page. A page that drifts between você and + o senhor, or between direct imperatives and an impersonal officialese voice, + is wrong even when each sentence is acceptable on its own. +- This is Brazilian Portuguese only. Every European Portuguese form is an + error here: + - vocabulary: arquivo (never ficheiro), tela (never ecrã), usuário (never + utilizador), salvar (never guardar), excluir / apagar (never eliminar for + "delete"), baixar (never transferir / descarregar for "download"), mouse + (never rato), site (never sítio); + - grammar: the progressive is estar + gerund — o servidor está rodando — + never estar a + infinitive (está a rodar, está a correr); + - spelling: the post-1990 orthography — ação, ótimo, ideia — never acção, + óptimo, idéia. + +## 2. Voice + +Aim for the voice of an experienced Brazilian engineer explaining a library +to a colleague: warm, direct, plain-spoken. The English is built on short +declarative payoff sentences ("That's the whole API."); keep them short — Essa +é a API inteira. + +Do: + +- Follow Portuguese rhythm. Split a long English sentence into two Portuguese + ones instead of mirroring its clause chain, and use everyday connectives + (então, ou seja, por isso) where they help the reader along. +- Use concrete verbs (executar, passar, retornar, declarar, bloquear) rather + than nominal chains: fazer a execução de → executar. +- Keep the source's directness. Where the English says "don't", the + Portuguese says não faça isso / não use, not a hedge like talvez seja + interessante evitar. + +Avoid — these are the marks of a machine or bureaucratic translation: + +- Officialese and legalistic filler: o presente documento, supracitado, + outrossim, faz-se necessário, deve-se ressaltar que, and o mesmo used as a + pronoun. +- Gerundismo: vamos estar mostrando → vamos mostrar; irá estar retornando → + vai retornar. +- Verbified anglicisms from spoken developer slang: deployar, commitar, + buildar, startar, mergear. Write fazer o deploy, fazer commit, gerar o build, + iniciar, fazer o merge. +- English-shaped Portuguese: calqued idioms (sob o capÃŽ for "under the hood" — + the Brazilian phrase is por baixo dos panos; no fim do dia for "at the end + of the day" — say no fim das contas), possessive chains, and passives where + an active sentence is natural ("The tool is called by the model" → o modelo + chama a ferramenta, not a ferramenta é chamada pelo modelo). +- Marketing hype and stacked exclamation marks. Keep an exclamation mark only + where the English one carries genuine emphasis. + +## 3. Humour and idioms + +The English is friendly and dry rather than jokey — short payoff sentences, a +few stock phrases, the rare emoji — and Brazilian technical writing is warm by +default, so most of that carries over unchanged. The idioms still need +recasting. + +- Never translate a pun, idiom or aside literally. Say what it means as a + short, natural Brazilian sentence in the same register. Where a common + Brazilian idiom happens to carry the same meaning, use it; where nothing + fits, use the plain statement. If an aside carries no information you may + drop it — but never drop a technical caveat that happens to be phrased + lightly. +- Recurring English tags get fixed renderings: "**[X](
)** has the whole + story" / "The whole story is in **[X](
)**" → **[X](
)** tem a história + completa; "That's the whole API." / "That's the whole protocol." → A API + inteira é essa. / O protocolo inteiro é esse.; "That's it. It's just + Python." → É só isso. É apenas Python. +- Idioms take the plain meaning, not the picture: "Out of the box the app + answers **only** requests addressed to localhost." → Por padrão, o app + responde **apenas** a requisições endereçadas ao localhost — not a calqued + fora da caixa. +- Culture-bound references (US sports, TV shows, holidays) → the plain + meaning. +- Emoji: keep the source's rare, deliberately placed emoji exactly where they + are — two payoff lines end in ✹ ("You get `3` back. ✹"). Never add new + ones. + +Worked examples (source → good / bad): + +- "You get `3` back. ✹" → good: Você recebe `3` de volta. ✹ / bad: Você + recebe `3` de volta! ✹ (added exclamation mark). +- "Give a parameter a default value and it stops being required. That's it. + It's just Python." → good: Dê um valor padrão a um parâmetro e ele deixa de + ser obrigatório. É só isso. É apenas Python. / bad: Dê um valor default + para um parâmetro e ele para de ser requerido. É isso aí, é só Python! ✹ + (untranslated default and requerido, slangy tag, added exclamation and + emoji). + +## 4. Typography + +- Prose punctuation is standard Brazilian usage written with the same + characters the source uses: keep straight double quotes ("
") and + apostrophes as they are; do not switch to «guillemets» or “curly quotes”; no + inverted ¿ ¡; no space before ! ? : ; (that is a French convention). +- Sentence case for headings, admonition titles and content-tab labels: + capitalise the first word and proper nouns only (Configurando o transporte, + not Configurando O Transporte). Language names, months and weekdays are + lower-case in Portuguese (a versão em inglês, em julho); proper nouns stay + capitalised (Python, GitHub, Claude Desktop). +- Digits stay ASCII. Protocol revision strings such as `2026-07-28` and + `2025-11-25` are identifiers, copied byte-for-byte — never 28/07/2026, + never 28 de julho de 2026. Version numbers, HTTP status codes, ports, error + codes, and RFC and SEP numbers are copied exactly. +- Ordinary prose quantities take the decimal comma only when nothing but the + separator changes (a timeout of 2.5 seconds → um timeout de 2,5 segundos); + when in doubt, keep the number as the source writes it. A space separates a + number from a Latin unit (100 MB, 30 s); % attaches with no space (100%). +- Latin abbreviations: e.g. → por exemplo, i.e. → ou seja / isto é; etc. + stays etc.; vs → versus, or ou / contra when a plain word reads better. + Where the English uses & in prose, write e. +- Loanwords kept in English are set in normal type — no italics, no scare + quotes — and take a Portuguese article: o handler, os tokens, a string. + Bold and italics land on the same words the source emphasises; a bolded + negation ("**not**" → **não**) stays bold. +- Ordinals use the indicators º / ª (1º, 2ª). Keep the source's dashes, + colons and parentheses as they are; do not turn a colon into a travessão + or the reverse. + +## 5. Terminology pointer + +The termbase is `glossary.json` next to this file. It is injected into the +prompt separately and its renderings override anything written here. This +section only fixes the conventions the glossary assumes: + +- Terms listed under `keep_in_source_language` are copied exactly as they + appear in the English source — same spelling, casing and plural "s" (SDKs + stays SDKs). They are not translated, italicised, re-cased or wrapped in + quotes. +- Everything in code font — class, function, method, parameter and module + names, protocol method strings (`tools/call`, `notifications/...`), header + names, error text, config keys — stays byte-identical. You may put a + Portuguese article or the word for the kind of thing in front of it: a + classe `Context`, o parâmetro `lifespan=`, o método `session.list_tools()`. + A glossary term used as a code-font identifier stays Latin even though its + prose noun is translated: "the `sampling` capability" → a capacidade + `sampling`. +- English technical nouns that stay in English keep their English spelling, + take a fixed grammatical gender, and pluralise the Brazilian way (add "s"). + Masculine by default — o token / os tokens, o handler, o callback, o host, + o schema, o payload, o endpoint, o log, o loop, o build, o commit, o deploy, + o prompt, o middleware — feminine where usage is settled: a string, a + thread, a query, a flag, a tag, a URL, a API, a issue. Where a glossary + entry's note gives a gender, it wins. +- Nouns are borrowed, verbs are not: fazer o deploy, fazer commit, fazer o + merge — never deployar, commitar, mergear (see §2). +- First-use gloss: a translated MCP concept the reader may need to map back to + the English specification carries the English in parentheses on its first + occurrence on a page — elicitação (elicitation) — and appears alone after + that. Each glossary entry's note says whether the term takes the gloss. +- One rendering per term per page: the glossary target, every time. Where an + entry's note marks the choice as open or provisional, still use the listed + target consistently rather than picking per sentence. + +## 6. Provisional note + +The register, voice and terminology decisions above are provisional, +pending review by native Brazilian Portuguese-speaking readers. To propose a +change, edit this file or `glossary.json` in a pull request — ideally with a +short good/bad example when the change is about phrasing; never edit the +generated pages under `pages/`, which the next translation run overwrites. diff --git a/i18n/pt-BR/pages/get-started/first-steps.md b/i18n/pt-BR/pages/get-started/first-steps.md new file mode 100644 index 0000000000..71a9d25546 --- /dev/null +++ b/i18n/pt-BR/pages/get-started/first-steps.md @@ -0,0 +1,139 @@ +# Primeiros passos {#first-steps} + +A **[página inicial](../index.md)** vai rápido: escreva um servidor, execute-o, chame uma ferramenta. + +Esta página vai devagar, com as três coisas que um servidor pode expor e um nome para cada uma delas pelo caminho. + +## Host, cliente e servidor {#host-client-and-server} + +Três palavras que você vai ver em todas as páginas daqui em diante: + +* Um **host** é a aplicação de LLM: Claude, uma IDE, um runtime de agente. É com ele que o usuário conversa. +* Um **cliente** vive dentro do host e fala MCP. O host roda um cliente para cada servidor ao qual está conectado. +* Um **servidor** é o que você constrói com este SDK. Ele expõe coisas para os clientes. Nunca fala diretamente com o modelo. + +Você escreve o servidor. Os hosts são produto de outra pessoa. O SDK também te dá um `Client`. Você vai usá-lo para testar seus servidores, e ele aparece mais adiante nesta página. + +## As três primitivas {#the-three-primitives} + +Um servidor expõe exatamente três tipos de coisa. O que as separa é **quem decide usá-las**: + +| Primitiva | Controlada por | O que é | Exemplo | +|----------------|-----------------|-------------------------------------------------------|----------------------------------------| +| **Ferramentas** | O modelo | Uma função que o modelo chama para executar uma ação | Uma chamada de API, uma escrita no banco | +| **Recursos** | A aplicação | Dados que o host carrega no contexto do modelo | O conteúdo de um arquivo, uma resposta de API | +| **Prompts** | O usuário | Um template de mensagem reutilizável que o usuário invoca pelo nome | Um comando de barra, um item de menu | + +"Controlada por" é justamente o ponto da divisão. Uma ferramenta roda porque o **modelo** decidiu chamá-la. Um recurso é anexado porque a **aplicação** decidiu que o modelo precisava dele. Um prompt roda porque o **usuário** o escolheu. + +!!! info + Se você já construiu uma API web, já tem quase toda a intuição: um **recurso** é um `GET` + (carrega dados e não muda nada) e uma **ferramenta** é um `POST` (faz trabalho e pode ter + efeitos colaterais). Um **prompt** não tem análogo em HTTP; está mais perto de uma query salva + que o usuário executa pelo nome. + +## Um servidor, os três {#one-server-all-three} + +```python title="server.py" hl_lines="6 12 18" +--8<-- "docs_src/first_steps/tutorial001.py" +``` + +Três funções simples, três decoradores. Cada decorador é o registro inteiro: + +* `@mcp.tool()` transforma `add` em uma **ferramenta**. +* `@mcp.resource("greeting://{name}")` transforma `greeting` em um **template de recurso**: o `{name}` na URI é o parâmetro da função. +* `@mcp.prompt()` transforma `summarize` em um **prompt**. A string que ele retorna vira uma mensagem do usuário. + +Todo o resto (o nome, a descrição, o schema dos argumentos) o SDK lê da própria função: o nome dela, a docstring, as type hints. Você nunca declarou nada disso separadamente. + +!!! tip + As duas metades do SDK têm dois caminhos de import: `from mcp import Client` e + `from mcp.server import MCPServer`. Não existe `from mcp import MCPServer`. + +### Experimente {#try-it} + +Execute com o MCP Inspector: + +```console +uv run mcp dev server.py +``` + +Abra a URL que ele imprime. O Inspector tem uma aba por primitiva; percorra-as na ordem. + +**Ferramentas.** Uma entrada: `add`, descrita como *Add two numbers.* O formulário tem um campo inteiro obrigatório para `a` e outro para `b`. Preencha, chame e o resultado é `3`. O Inspector montou esse formulário a partir de `a: int, b: int`. Todo outro cliente faz o mesmo. + +**Recursos.** A lista *Resources* está vazia. `greeting` está em **Resource Templates**, porque `greeting://{name}` tem um parâmetro: não há um recurso único para listar até que alguém forneça um `name`. Dê `World` a ele e leia: + +```text +Hello, World! +``` + +**Prompts.** Uma entrada: `summarize`, com um único argumento obrigatório `text`. Obtenha-o com algum texto e você recebe uma mensagem com `role: user` e a sua string renderizada como conteúdo. Um prompt é só isso: uma função que monta mensagens. + +O Inspector executou seu servidor sobre **stdio**, um dos transportes que um servidor MCP pode falar. Você ainda não escolhe um; **[Executando seu servidor](../run/index.md)** é a página para isso. + +## Capacidades {#capabilities} + +Você viu três abas no Inspector. Como ele soube que eram três? + +Quando um cliente se conecta, o servidor declara suas **capacidades**: quais famílias de requisições ele vai atender. O cliente usa essa declaração para decidir o que sequer pedir. Você nunca escreveu isso; o `MCPServer` declara por você. + +Veja você mesmo. O `Client` do SDK aceita o objeto do servidor diretamente e se conecta a ele **em memória** (sem subprocesso, sem porta): + +```python +import asyncio + +from mcp import Client + +from server import mcp + + +async def main() -> None: + async with Client(mcp) as client: + print(client.server_capabilities.model_dump(exclude_none=True)) + + +asyncio.run(main()) +``` + +```text +{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}} +``` + +Esse dicionário são as **capacidades** declaradas do seu servidor. É a primeira coisa que todo cliente que se conecta descobre: + +| Capacidade | O cliente agora pode chamar | +|-------------|--------------------------------------------------------------| +| `tools` | `tools/list`, `tools/call` | +| `resources` | `resources/list`, `resources/templates/list`, `resources/read` | +| `prompts` | `prompts/list`, `prompts/get` | + +O `MCPServer` serve as três primitivas, então as três são sempre declaradas. + +Repare no que não está lá. `completions` (autocompletar de argumentos para templates de recursos e prompts) precisa de um handler que você escreve, este servidor não tem um, então a capacidade está ausente e um cliente bem-comportado não vai pedir. Essa é a regra para tudo que é opcional: registre a coisa e a capacidade aparece; **[Completions](../servers/completions.md)** prova isso. + +!!! info + `Client(mcp)` é o mesmo cliente em memória com o qual todo exemplo destes docs é testado, e + é assim que você vai testar os seus. Ele tem uma página inteira: **[Testes](testing.md)**. + +## O que você não escreveu {#what-you-did-not-write} + +Revise esta página. Você escreveu três pequenas funções Python. Você **não** escreveu: + +* Um JSON Schema. `a: int, b: int` *é* o schema de `add`. +* Um handler de requisições. `tools/list`, `resources/read`, `prompts/get`: todos servidos para você. +* Uma declaração de capacidades. O `MCPServer` fez isso por você. +* Uma linha de protocolo. A negociação de versão, o framing JSON-RPC, a troca de capacidades: tudo aconteceu dentro de `mcp dev` e `Client(mcp)`, e você nunca viu nada disso. + +Essa proporção é exatamente o propósito do SDK. + +## Resumo {#recap} + +* Um **host** é o app de LLM, um **cliente** é a metade dele que fala MCP, um **servidor** é o que você constrói. +* Ferramentas são controladas pelo **modelo**, recursos pela **aplicação**, prompts pelo **usuário**. +* Um decorador por primitiva: `@mcp.tool()`, `@mcp.resource(uri)`, `@mcp.prompt()`. Nome, descrição e schema vêm da função. +* Uma URI com um `{param}` cria um **template** de recurso, listado separadamente dos recursos concretos. +* As **capacidades** do servidor são declaradas por você automaticamente, e um cliente só pede o que um servidor declara. +* `Client(mcp)` se conecta ao objeto do servidor em memória: seu ambiente de testes desde o primeiro dia. + +O próximo passo é **[Conectar a um host real](real-host.md)**: este servidor dentro do Claude Desktop ou de uma IDE, de verdade. Depois **[Testes](testing.md)**: uma página, um cliente em memória, e você nunca mais fica adivinhando se funciona. Em seguida, cada primitiva ganha sua própria página, começando pela que o modelo dirige: **[Ferramentas](../servers/tools.md)**. diff --git a/i18n/pt-BR/pages/handlers/elicitation.md b/i18n/pt-BR/pages/handlers/elicitation.md new file mode 100644 index 0000000000..3ca6e397b5 --- /dev/null +++ b/i18n/pt-BR/pages/handlers/elicitation.md @@ -0,0 +1,185 @@ +# Elicitação {#elicitation} + +Uma ferramenta que está na metade do trabalho e sem uma resposta não precisa falhar. + +**Elicitação (elicitation)** deixa que ela pergunte. No meio de uma chamada de ferramenta o usuário recebe uma pergunta, e a resposta dele volta para dentro da mesma chamada de função. + +Existem dois modos: + +* **Modo formulário**: você precisa de um valor (uma confirmação, uma data, uma quantidade). Você descreve os campos, o cliente renderiza o formulário. +* **Modo URL**: você precisa que o usuário vá para outro lugar (uma tela de consentimento OAuth, uma página de pagamento). Nada do que ele fizer lá passa pelo protocolo. + +E há duas formas de perguntar. A que você deve usar é um **resolvedor**: você pendura a pergunta em um parâmetro e o SDK pergunta - em qualquer conexão, seja qual for a era de protocolo que o cliente fala. A forma direta, `await ctx.elicit(...)`, é uma requisição do *servidor* para o *cliente*, um canal que só existe para um cliente em uma conexão legada (versão da especificação 2025-11-25 ou anterior). As duas estão nesta página; comece pelo resolvedor. + +## Perguntar com um resolvedor {#ask-with-a-resolver} + +Uma pergunta que condiciona a ferramenta inteira - *tem certeza? qual das três contas correspondentes?* - pode sair do corpo da ferramenta e virar um **resolvedor**, e o framework pergunta por você. + +Um parâmetro anotado com `Annotated[T, Resolve(fn)]` é preenchido executando `fn` antes do corpo da ferramenta. O resolvedor retorna o valor diretamente quando já o conhece, ou retorna `Elicit(...)` para que o framework pergunte: + +```python title="server.py" hl_lines="24-30 35-36" +--8<-- "docs_src/elicitation/tutorial004.py" +``` + +* `confirm_delete` lê o próprio argumento `path` da ferramenta pelo nome, lista a pasta e **só faz a elicitação quando é necessário** - uma pasta vazia resolve para `Confirm(ok=True)` sem nenhuma ida e volta até o cliente. +* `delete_folder` anota `ElicitationResult[Confirm]`, então o framework injeta o resultado inteiro e a ferramenta faz `match` de todos os casos: aceitar-e-confirmar, aceitar-mas-manter (`ok=False`), recusar, cancelar. +* O parâmetro `confirm` nunca aparece no schema de entrada da ferramenta - o cliente fornece `path`, o resolvedor fornece `confirm`. + +Anote o modelo sem o wrapper (`Annotated[Confirm, Resolve(confirm_delete)]`) quando a ferramenta não precisar se ramificar: ela recebe o modelo no accept e a chamada aborta com um erro no decline ou no cancel. + +Um resolvedor funciona em **todas** as conexões. Para um cliente em uma conexão legada, o SDK envia a pergunta diretamente para ele; em uma conexão **2026-07-28**, o SDK *retorna* a pergunta a partir da chamada, e a próxima tentativa do cliente carrega a resposta. O seu resolvedor nunca percebe a diferença; o que acontece por baixo dos panos é **[Requisições com múltiplas idas e voltas](multi-round-trip.md)**. + +Perguntar é só uma das coisas que um resolvedor pode fazer. O mecanismo geral - dependências que calculam sem perguntar, dependências de dependências, o que o modelo pode e não pode fornecer - está na página **[Dependências](dependencies.md)**. + +## Perguntar de dentro da ferramenta {#ask-from-inside-the-tool} + +Uma ferramenta também pode parar no meio do próprio corpo e perguntar. + +!!! warning + `ctx.elicit()` e `ctx.elicit_url()` são requisições do *servidor* para o *cliente* - um + canal que só existe para um cliente em uma conexão legada (versão da especificação **2025-11-25** + ou anterior). Em uma conexão **2026-07-28** não existem requisições iniciadas pelo servidor, então + essas chamadas falham. Um resolvedor funciona nos dois casos. **[Versões do protocolo](../protocol-versions.md)** + tem a história completa. + +`await ctx.elicit()` recebe uma mensagem e um modelo Pydantic: + +```python title="server.py" hl_lines="9-11 20-23 25" +--8<-- "docs_src/elicitation/tutorial001.py" +``` + +* O parâmetro **`Context`** é o que dá acesso ao `ctx.elicit`; qualquer ferramenta pode receber um. Esse objeto tem a própria página: **[O Context](context.md)**. +* `AlternativeDate` é o **schema** da resposta que você quer. +* A ferramenta é `async def`. Tem que ser: ela para no meio e espera por uma pessoa. +* Em qualquer outra data, a ferramenta retorna na hora. Ela só pergunta quando precisa. +* A data que o usuário aceita passa de novo pelo próprio `book_table`. Uma resposta é entrada como qualquer outra: uma alternativa que também está lotada gera uma nova pergunta, em vez de ser confirmada às cegas. + +### O que o cliente recebe {#what-the-client-receives} + +O cliente recebe a sua mensagem e, junto dela, um JSON Schema gerado a partir do modelo: + +```json +{ + "properties": { + "accept_alternative": { + "description": "Try another date?", + "title": "Accept Alternative", + "type": "boolean" + }, + "date": { + "default": "2025-12-26", + "description": "Alternative date (YYYY-MM-DD)", + "title": "Date", + "type": "string" + } + }, + "required": ["accept_alternative"], + "title": "AlternativeDate", + "type": "object" +} +``` + +Esse schema é o formulário. `Field(description=...)` é o rótulo; um valor padrão já preenche o campo e o torna opcional. É a mesma maquinaria de Pydantic para JSON Schema que **[Ferramentas](../servers/tools.md)** descreve para os argumentos de uma ferramenta. + +!!! warning + Um schema de elicitação não é tão expressivo quanto o schema de entrada de uma ferramenta. Só campos + primitivos e planos: `str`, `int`, `float`, `bool` ou um `Literal` de strings (que vira um `enum`). + Coloque um modelo dentro do modelo e o `ctx.elicit` levanta um erro antes de qualquer coisa ser enviada ao cliente: + + ```text + TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition + ``` + + Você está interrompendo uma pessoa no meio de uma tarefa. Se a resposta precisa de aninhamento, ela deveria ter sido um + argumento da ferramenta. + +### As três respostas {#the-three-answers} + +`result.action` diz o que o usuário fez, e existem exatamente três possibilidades: + +* `"accept"`: ele enviou o formulário. `result.data` é uma instância de `AlternativeDate`, já validada. +* `"decline"`: ele disse não. +* `"cancel"`: ele descartou a pergunta sem escolher. + +`result.data` só existe no `"accept"`, e é por isso que o exemplo verifica `result.action` primeiro. O seu verificador de tipos garante a ordem: depois de `result.action == "accept"`, `result.data` é um `AlternativeDate`; antes disso, não existe `.data` nenhum. + +Uma recusa não é um erro. A ferramenta decide o que recusar significa (aqui, nenhuma reserva) e responde ao modelo normalmente. + +!!! tip + A resposta é validada contra o seu modelo antes de o seu código vê-la. Um cliente que envia + `"maybe"` para um `bool` não corrompe a sua reserva: a chamada falha com um + erro de incompatibilidade de schema, e o seu `if` nunca roda. + +## Mandar o usuário para uma URL {#send-the-user-to-a-url} + +Algumas coisas não podem passar pelo modelo nem pelo cliente: credenciais, números de cartão, consentimento OAuth. Para essas, você não pede dados; você pede que o usuário vá a algum lugar: + +```python title="server.py" hl_lines="10-14 23" +--8<-- "docs_src/elicitation/tutorial002.py" +``` + +* `ctx.elicit_url()` recebe a mensagem, a **URL** a visitar e um `elicitation_id` escolhido por você: qualquer string que identifique essa elicitação dentro do seu servidor. +* O resultado tem uma ação e nada mais. `"accept"` significa que o usuário concordou em abrir a URL, **não** que ele terminou o que está do outro lado. +* O pagamento acontece fora de banda, entre o navegador do usuário e o seu provedor de pagamento. Nenhum conteúdo volta pelo MCP. + +Olhe a segunda ferramenta. Quando o seu servidor descobre que o fluxo fora de banda terminou (um webhook, um polling; aqui isso é modelado como uma segunda ferramenta), `ctx.session.send_elicit_complete(...)` envia `notifications/elicitation/complete` com o mesmo `elicitation_id`. É assim que o cliente sabe que pode parar de mostrar *"aguardando pagamento..."*. Sem isso, o cliente só pode adivinhar. + +## O lado do cliente {#the-client-side} + +Servidores perguntam. Clientes respondem passando um **`elicitation_callback`** para `Client(...)`: + +```python title="client.py" hl_lines="6-7 18" +--8<-- "docs_src/elicitation/tutorial003.py" +``` + +* Um único callback dá conta dos dois modos. `params` é uma união de `ElicitRequestFormParams` e `ElicitRequestURLParams`; o `isinstance` faz a ramificação. +* Para uma URL, você mostra `params.url` ao usuário e retorna a ação que ele escolheu. Nunca nenhum `content`. +* Para um formulário, uma aplicação real renderiza `params.requested_schema` e retorna a entrada do usuário como `content`. Este aqui sempre diz sim com uma resposta pronta, que é exatamente o callback que você quer em um teste. +* Passar o callback também é a **declaração de capacidade**: é assim que o servidor descobre que dá para perguntar a este cliente. As outras coisas que um cliente pode responder para um servidor estão em **[Callbacks do cliente](../client/callbacks.md)**. + +!!! info + Elicitação é uma requisição do *servidor* para o *cliente*, e essas só existem em uma + sessão com handshake clássico, e é por isso que este cliente passa `mode="legacy"`. + Em uma conexão **2026-07-28**, uma ferramenta pergunta *retornando* a pergunta a partir da chamada; + esse fluxo é **[Requisições com múltiplas idas e voltas](multi-round-trip.md)**. + +### Experimente {#try-it} + +Inicie o `server.py` de modo formulário com `ctx.elicit` (o do `book_table`) em Streamable HTTP (**[Executando o seu servidor](../run/index.md)** tem o comando de uma linha), depois execute o `main()` do cliente e peça ao `book_table` uma mesa no dia de Natal. + +O callback imprime a pergunta que recebeu: + +```text +No tables for 2 on 2025-12-25. Would you like to try another date? +``` + +Ele responde com `{"accept_alternative": True, "date": "2025-12-27"}`, e a ferramenta, que esteve esperando dentro de `await ctx.elicit(...)` esse tempo todo, conclui a reserva: + +```text +Booked a table for 2 on 2025-12-27. +``` + +Agora troque para o `server.py` de modo URL e aponte o mesmo `main()` para `pay_deposit`: o mesmo callback pega o outro ramo, imprime o link de pagamento, e a ferramenta volta com *"Complete the payment in your browser."* Uma ida e volta, no meio da chamada, nos dois sentidos. + +!!! check + Agora remova `elicitation_callback=` do `Client` e chame `book_table` para o dia de Natal + de novo. A chamada inteira falha com um erro de protocolo: + + ```text + Elicitation not supported + ``` + + Um cliente que não registrou nenhum callback nunca declarou a capacidade `elicitation`, então não há + ninguém a quem perguntar. A sua ferramenta não recebeu um `"decline"`; ela recebeu uma exceção. Projete pensando nisso: toda + elicitação precisa de uma resposta sensata para "e se eu não puder perguntar?". + +## Recapitulando {#recap} + +* Um parâmetro anotado com `Annotated[T, Resolve(fn)]` é preenchido por um resolvedor, que retorna `Elicit(...)` quando precisa perguntar. Funciona em todas as conexões. +* O schema é um modelo Pydantic plano: só campos primitivos, validados na volta. +* `result.action` é `"accept"`, `"decline"` ou `"cancel"`; `result.data` só existe no accept. +* `await ctx.elicit(message, schema=Model)` pergunta de dentro do corpo da ferramenta, e `await ctx.elicit_url(message, url, elicitation_id)` serve para tudo que não pode passar pelo modelo (`ctx.session.send_elicit_complete(elicitation_id)` avisa que a parte fora de banda terminou). As duas são requisições do servidor para o cliente: precisam do cliente em uma conexão legada. +* O cliente responde com um único `elicitation_callback`, ramificando pelo tipo dos params; registrá-lo é o que declara a capacidade. +* Em uma conexão 2026-07-28, o servidor retorna a pergunta em vez de empurrá-la; o mesmo callback é alimentado por **[Requisições com múltiplas idas e voltas](multi-round-trip.md)**. + +Tudo que está por baixo desse retorno (o loop de retry, a proteção do `requestState`, conduzir isso você mesmo) está em **[Requisições com múltiplas idas e voltas](multi-round-trip.md)**. diff --git a/i18n/pt-BR/pages/index.md b/i18n/pt-BR/pages/index.md new file mode 100644 index 0000000000..18d5207dd5 --- /dev/null +++ b/i18n/pt-BR/pages/index.md @@ -0,0 +1,97 @@ +# MCP Python SDK {#mcp-python-sdk} + +!!! info "Esta é a documentação da v2, a linha de lançamentos estável atual" + Novo na v2 ou vindo da v1? **[O que há de novo na v2](whats-new.md)** é o tour de cinco minutos pelo que mudou, e o **[Guia de migração](migration.md)** cobre cada mudança incompatível. + Ainda na v1.x? A documentação dela fica na [documentação da v1.x](https://py.sdk.modelcontextprotocol.io/v1/). + Achou algo confuso ou mal acabado? [Nos avise](https://github.com/modelcontextprotocol/python-sdk/issues/new?template=v2-feedback.yaml). + +O **Model Context Protocol (MCP)** permite que aplicações forneçam contexto a LLMs de forma padronizada, separando a preocupação de *fornecer* contexto da interação com o LLM em si. + +Este é o SDK Python oficial dele. Com ele, você pode: + +* **Construir servidores MCP** que expõem ferramentas (tools), recursos (resources) e prompts para qualquer host MCP. +* **Construir clientes MCP** que se conectam a qualquer servidor MCP. +* Falar todos os transportes padrão: stdio, Streamable HTTP e SSE. + +## Requisitos {#requirements} + +Python 3.10+. + +## Instalação {#installation} + +=== "uv" + + ```bash + uv add "mcp[cli]" + ``` + +=== "pip" + + ```bash + pip install "mcp[cli]" + ``` + +O extra `[cli]` dá a você o comando `mcp`; você vai querer usá-lo durante o desenvolvimento. +Veja [Instalação](get-started/installation.md) para saber para que serve cada dependência. + +## Exemplo {#example} + +### Crie {#create-it} + +Crie um arquivo `server.py`: + +```python title="server.py" +--8<-- "docs_src/index/tutorial001.py" +``` + +Esse é um servidor MCP completo. + +Ele expõe uma **ferramenta**, `add`, e um **recurso** com template, `greeting://{name}`. + +### Execute {#run-it} + +```console +uv run mcp dev server.py +``` + +Isso inicia o seu servidor e abre o [MCP Inspector](https://github.com/modelcontextprotocol/inspector), uma UI interativa para explorá-lo. Abra a URL que ele imprime. + +!!! note + O Inspector é um app Node.js, então `mcp dev` precisa do `npx` no seu `PATH`. + +### Teste {#try-it} + +No Inspector, vá em **Tools** e chame `add` com `a=1`, `b=2`. + +Você recebe `3` de volta. ✹ + +O Inspector montou esse formulário (um campo inteiro obrigatório para `a`, outro para `b`) a partir das suas type hints. O Claude vai fazer o mesmo, e todo outro host MCP também. + +Agora vá em **Resources** e leia `greeting://World`: + +```text +Hello, World! +``` + +### Recapitulando {#recap} + +Olhe de novo para o que você **não** escreveu: + +* Nenhum JSON Schema. `a: int, b: int` *é* o schema. +* Nenhum parsing de requisição, nenhuma serialização, nenhum código de validação. +* Nenhum tratamento de protocolo. + +Você escreveu duas funções Python com type hints e uma docstring. O SDK faz o resto. + +## Onde ir agora {#where-to-go-next} + +* **[Primeiros passos](get-started/index.md)** leva você da instalação até um servidor funcionando e testado. +* Está construindo uma aplicação que *usa* servidores MCP? Comece por **[Clientes](client/index.md)**. +* Já tem um app FastAPI ou Starlette? **[Adicionar a um app existente](run/asgi.md)** monta um servidor MCP dentro dele. +* Procurando uma mensagem de erro exata? **[Solução de problemas](troubleshooting.md)** é indexado pelo texto literal. +* Quer saber o que mudou na v2? **[O que há de novo na v2](whats-new.md)** é o tour de cinco minutos. +* Migrando da v1? Comece pelo **[Guia de migração](migration.md)**. +* Procurando uma assinatura exata? A **[Referência da API](api/mcp/index.md)** é gerada a partir do código-fonte. +* Lendo com um LLM? Esta documentação também é publicada no formato [llms.txt](https://llmstxt.org/): + [llms.txt](https://py.sdk.modelcontextprotocol.io/llms.txt) é um índice das páginas, e + [llms-full.txt](https://py.sdk.modelcontextprotocol.io/llms-full.txt) contém todas as páginas em um único arquivo. diff --git a/i18n/pt-BR/state.json b/i18n/pt-BR/state.json new file mode 100644 index 0000000000..ce49578b9d --- /dev/null +++ b/i18n/pt-BR/state.json @@ -0,0 +1,164 @@ +{ + "pages": { + "get-started/first-steps.md": { + "block_hashes": [ + "f4e9527fee44ad246985003e8319e71c2b72f28e3301ee128f3d2c0e3f6d264d", + "3e1766d27f443ca4bbccdda3389f3dff2d2b6737133564ee0ca133a5dbfd958d", + "2a97f1d75b49d9a9b70e82ce3ad0ee70072b2676db3248614ae2ba29bc3fff28", + "59dc6c66de1c4ea589e4b984889814b842613605c638ae450db4fa44243aee06", + "ee42a5e18f8e868b5d29bb9db85915406e7e97e789013ee108ee2d199626d274", + "7d8344d6cf1ecfc4130812f643a148ea572ad7d319b371029197fe162d513602", + "cfcd0cd4aed4f275cb8cf632eed301192aed12aeb11c10c77fabab2fe0fc7b30" + ], + "inputs_hash": "ad06b80fddc3dab40092161924d753c6b1b3425cea82786911d84f88940de636", + "model": "claude-opus-5", + "source_hash": "3531c5686a6ff79aabe6f0f8a3cb96213b6fc13b0bbbb0e762dc9f4a827b4be3", + "translated_at": "2026-07-31T19:11:10Z" + }, + "handlers/elicitation.md": { + "block_hashes": [ + "f5dade066115eb33121f7bbb02a3fb288e95e2662001bea5eac322e23f4cbd1d", + "c368d8a40fd8d9879d90fbb10a81870fdb3ca84675bcf04c91740febe56ed4c4", + "191a9cfc619f523acf23ef20a04a9a318abe5280bb459f4691dfc2c60bc8dc34", + "ed47c27010f781038cd2095fedb17be05b16a9c2b1b93650416dd7e1be3154b7", + "4db8b6b1183f96319677911fd22baa3e65faf1b2f107b7d0929a3ee39ebd3ed4", + "0265da2c81bc50cf3a83415aa16c1ded414fe59a3e23e4c0b1a83cd03d41ba5c" + ], + "inputs_hash": "ad06b80fddc3dab40092161924d753c6b1b3425cea82786911d84f88940de636", + "model": "claude-opus-5", + "source_hash": "ef8afd716d136ff25d17a34059a933bb924ea238bd2faf04f80cc0d337c2e520", + "translated_at": "2026-07-31T19:11:10Z" + }, + "index.md": { + "block_hashes": [ + "3690a6eb6f57324f620f65d5242b3bb17c0ed4a5657cd4ba7ff3a28d7291245a", + "13e00a4af763653d8c663c64dd27871bdd23314d11110a143b8ee77357f76a75", + "cfcabc919e5d343b47af2641656a86f9a0abe9f250547368e64f7e4b5a3de4b0", + "c1550578c7bd96be08abb210e1c86261127c78ee09c1d8d666d08fa96271c30b", + "71115a8dc82e7ee1cd61b609da087038d8736629ebb365972c88010fd677cc00" + ], + "inputs_hash": "ad06b80fddc3dab40092161924d753c6b1b3425cea82786911d84f88940de636", + "model": "claude-opus-5", + "source_hash": "c46eefb49de18ed47882e13cf5bf08d0c3ca16ee77b98c257e8b66150c6be666", + "translated_at": "2026-07-31T21:10:06Z" + } + }, + "ui": { + "inputs_hash": "ad06b80fddc3dab40092161924d753c6b1b3425cea82786911d84f88940de636", + "model": "claude-opus-5", + "source": { + "@banner:disclosure": "??? info \"This page was machine-translated\"\n Translations of this documentation are generated automatically from the English pages, and the [English version of this page]({english_url}) is the authoritative one.\n\n Found a translation problem? See [how the translations work and how to report an issue]({translations_url}).\n", + "@banner:outdated": "!!! warning \"This translation may be behind the English page\"\n The English source changed after this page was last translated, so parts of it may be out of date. When in doubt, read the [English version of this page]({english_url}).\n", + "@banner:untranslated": "!!! note \"In English\"\n You're reading this page in English. [How the translations work]({translations_url}).\n", + "API Reference": "API Reference", + "Add to an existing app": "Add to an existing app", + "Advanced": "Advanced", + "Authorization": "Authorization", + "Caching": "Caching", + "Callbacks": "Callbacks", + "Clients": "Clients", + "Completions": "Completions", + "Connect to a real host": "Connect to a real host", + "Dependencies": "Dependencies", + "Deploy & scale": "Deploy & scale", + "Deprecated features": "Deprecated features", + "Elicitation": "Elicitation", + "Extensions": "Extensions", + "First steps": "First steps", + "Get started": "Get started", + "Handling errors": "Handling errors", + "Identity assertion": "Identity assertion", + "Images, audio & icons": "Images, audio & icons", + "Inside your handler": "Inside your handler", + "Installation": "Installation", + "Lifespan": "Lifespan", + "Logging": "Logging", + "MCP Apps": "MCP Apps", + "MCP Python SDK": "MCP Python SDK", + "Middleware": "Middleware", + "Migration Guide": "Migration Guide", + "Multi-round-trip requests": "Multi-round-trip requests", + "Multiple servers": "Multiple servers", + "OAuth": "OAuth", + "OpenTelemetry": "OpenTelemetry", + "Pagination": "Pagination", + "Progress": "Progress", + "Prompts": "Prompts", + "Protocol versions": "Protocol versions", + "Resources": "Resources", + "Running your server": "Running your server", + "Sampling and roots": "Sampling and roots", + "Servers": "Servers", + "Serving legacy clients": "Serving legacy clients", + "Structured Output": "Structured Output", + "Subscriptions": "Subscriptions", + "Testing": "Testing", + "The Context": "The Context", + "The low-level Server": "The low-level Server", + "Tools": "Tools", + "Translations": "Translations", + "Transports": "Transports", + "Troubleshooting": "Troubleshooting", + "URI templates": "URI templates", + "What's new in v2": "What's new in v2" + }, + "source_hash": "219aa0f5adfe57c4403a1b6fdf04d4e2cc79df194bdbcb30958d24e7bb387433", + "strings": { + "@banner:disclosure": "??? info \"Esta página foi traduzida automaticamente\"\n As traduções desta documentação são geradas automaticamente a partir das páginas em inglês, e a [versão em inglês desta página]({english_url}) é a oficial.\n\n Encontrou algum problema na tradução? Veja [como as traduções funcionam e como relatar um problema]({translations_url}).\n", + "@banner:outdated": "!!! warning \"Esta tradução pode estar atrasada em relação à página em inglês\"\n O texto original em inglês mudou depois da última tradução desta página, então partes dela podem estar desatualizadas. Na dúvida, leia a [versão em inglês desta página]({english_url}).\n", + "@banner:untranslated": "!!! note \"Em inglês\"\n Você está lendo esta página em inglês. [Como as traduções funcionam]({translations_url}).\n", + "API Reference": "Referência da API", + "Add to an existing app": "Adicionar a um app existente", + "Advanced": "Avançado", + "Authorization": "Autorização", + "Caching": "Cache", + "Callbacks": "Callbacks", + "Clients": "Clientes", + "Completions": "Completions", + "Connect to a real host": "Conectar a um host real", + "Dependencies": "Dependências", + "Deploy & scale": "Deploy e escala", + "Deprecated features": "Funcionalidades obsoletas", + "Elicitation": "Elicitação", + "Extensions": "Extensões", + "First steps": "Primeiros passos", + "Get started": "Comece aqui", + "Handling errors": "Tratamento de erros", + "Identity assertion": "Asserção de identidade", + "Images, audio & icons": "Imagens, áudio e ícones", + "Inside your handler": "Dentro do seu handler", + "Installation": "Instalação", + "Lifespan": "Lifespan", + "Logging": "Logging", + "MCP Apps": "MCP Apps", + "MCP Python SDK": "MCP Python SDK", + "Middleware": "Middleware", + "Migration Guide": "Guia de migração", + "Multi-round-trip requests": "Requisições com múltiplas idas e voltas", + "Multiple servers": "Múltiplos servidores", + "OAuth": "OAuth", + "OpenTelemetry": "OpenTelemetry", + "Pagination": "Paginação", + "Progress": "Progresso", + "Prompts": "Prompts", + "Protocol versions": "Versões do protocolo", + "Resources": "Recursos", + "Running your server": "Executando seu servidor", + "Sampling and roots": "Amostragem e roots", + "Servers": "Servidores", + "Serving legacy clients": "Atendendo clientes legados", + "Structured Output": "Saída estruturada", + "Subscriptions": "Assinaturas", + "Testing": "Testes", + "The Context": "O Context", + "The low-level Server": "O Server de baixo nível", + "Tools": "Ferramentas", + "Translations": "Traduções", + "Transports": "Transportes", + "Troubleshooting": "Solução de problemas", + "URI templates": "Templates de URI", + "What's new in v2": "Novidades da v2" + }, + "translated_at": "2026-07-31T21:10:06Z" + } +} diff --git a/i18n/zh-CN/glossary.json b/i18n/zh-CN/glossary.json new file mode 100644 index 0000000000..95cd2dc157 --- /dev/null +++ b/i18n/zh-CN/glossary.json @@ -0,0 +1,232 @@ +{ + "version": 1, + "keep_in_source_language": [ + "MCP", + "Model Context Protocol", + "MCPServer", + "FastMCP", + "ClientSession", + "Context", + "ctx", + "stdio", + "Streamable HTTP", + "SSE", + "JSON-RPC", + "JSON", + "OAuth", + "PKCE", + "JWT", + "CIMD", + "HTTP", + "HTTPS", + "TLS", + "CORS", + "URI", + "URL", + "ASGI", + "WebSocket", + "API", + "SDK", + "CLI", + "IDE", + "LLM", + "SEP", + "RFC", + "Python", + "TypeScript", + "Node.js", + "PyPI", + "Pydantic", + "Starlette", + "FastAPI", + "uvicorn", + "httpx", + "anyio", + "asyncio", + "trio", + "pytest", + "OpenTelemetry", + "Inspector", + "Claude", + "GitHub", + "VS Code", + "Windows", + "macOS", + "Linux", + "llms.txt", + "2026-07-28", + "2025-11-25", + "2025-06-18", + "2025-03-26" + ], + "terms": [ + { + "source": "tool", + "target": "工具", + "note": "MCP protocol noun (a server exposes tools). Wire identifiers such as `tools/call` and `tools/list` are code and stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "resource", + "target": "资源", + "note": "MCP protocol noun; \"resource template\" → 资源暡板. `resources/read` stays Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "prompt", + "target": "提瀺词", + "note": "The MCP feature: a reusable prompt a server exposes (`prompts/get` stays Latin). Not the bare 提瀺, which reads as \"hint\" and is the standard rendering of the `tip` admonition title. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "sampling", + "target": "采样", + "note": "The (deprecated) client feature that lets a server borrow the client's model. Gloss the English on first use per page: 采样sampling. The `sampling` capability key and `sampling/createMessage` stay Latin. 抜样/取样 mean statistical sampling and are the wrong sense here.", + "avoid": ["抜样", "取样"], + "enforce": true + }, + { + "source": "roots", + "target": "根目圕", + "note": "The (deprecated) client feature listing workspace folders; a `Root` object in code font stays Latin. Gloss the English on first use per page: 根目圕roots. Never the bare æ ¹ and never 根节点 (a tree node). Provisional pending native review.", + "avoid": ["根节点"], + "enforce": false + }, + { + "source": "elicitation", + "target": "埁询", + "note": "OPEN QUESTION for native review: existing Chinese material renders this concept as 匕富, 匕富获取, 询问 or 埁询, and even our own drafts disagree between 埁询 and 匕富. Provisionally pinned to 埁询, glossed with the English on first use per page: 埁询elicitation. Use it consistently within a page whichever way the review settles. `elicitation/create` and the `Elicit` class stay Latin.", + "avoid": [], + "enforce": false + }, + { + "source": "capability", + "target": "胜力", + "note": "A negotiated protocol capability (声明了 `sampling` 胜力). The `capabilities` field and keys such as `sampling.tools` stay Latin. Not 功胜, which means \"feature\" — the corpus uses \"feature\" as a separate word. Provisional pending native review.", + "avoid": ["功胜"], + "enforce": false + }, + { + "source": "transport", + "target": "䌠蟓", + "note": "As a countable noun use 䌠蟓方匏 (\"three transports\" → 䞉种䌠蟓方匏). The transport names stdio, Streamable HTTP and SSE stay in English. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "session", + "target": "䌚话", + "note": "An MCP session (the negotiated connection state); `session` objects in code font stay Latin. Not 䌚议 (a meeting). Provisional pending native review.", + "avoid": ["䌚议"], + "enforce": false + }, + { + "source": "handler", + "target": "倄理凜数", + "note": "The tool, resource or prompt function you register (nav section \"Inside your handler\" → 圚倄理凜数内郚). Open question for native review: 倄理噚 is the competing rendering; pick one and never mix within a page. Provisional.", + "avoid": [], + "enforce": false + }, + { + "source": "dependency", + "target": "䟝赖", + "note": "The SDK's dependency-injection feature (the \"Dependencies\" page); use 䟝赖项 where a countable noun is needed. The `Resolve` marker class stays Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "client", + "target": "客户端", + "note": "An MCP client, and the client side of a connection. The `Client` class name stays Latin in code font. Not 客户 (a customer). Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "server", + "target": "服务噚", + "note": "An MCP server (the program you build). \"server-side\" → 服务噚端. The low-level `Server` class stays Latin in code font. Not 䌺服噚 (the zh-TW term); do not mix in 服务端 for the same noun. Provisional pending native review.", + "avoid": ["䌺服噚", "服务端"], + "enforce": false + }, + { + "source": "host", + "target": "宿䞻", + "note": "The MCP host: the application that embeds the client and drives the model. Not 䞻机 (a machine or hostname). Provisional pending native review.", + "avoid": ["䞻机"], + "enforce": false + }, + { + "source": "resolver", + "target": "解析噚", + "note": "The SDK's dependency-resolver mechanism (\"an elicitation resolver\" → elicitation 解析噚 in glossary terms: 埁询解析噚). The `Resolve` class stays Latin. Provisional pending native review, together with the handler entry.", + "avoid": [], + "enforce": false + }, + { + "source": "lifespan", + "target": "生呜呚期", + "note": "The server's startup/shutdown scope (the \"Lifespan\" page). The `lifespan` parameter name stays Latin in code font. 寿呜 is the biological sense and is wrong here. Provisional pending native review.", + "avoid": ["寿呜"], + "enforce": false + }, + { + "source": "callback", + "target": "回调", + "note": "Client callbacks; parameter names such as `sampling_callback` stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "notification", + "target": "通知", + "note": "A JSON-RPC notification (a message that expects no response); method strings such as `notifications/tools/list_changed` stay Latin. Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "back-channel", + "target": "反向通道", + "note": "The server-to-client request channel that exists only on 2025-era, non-stateless connections. Provisional coinage: gloss the English on first use per page — 反向通道back-channel. Pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "wire", + "target": "线路", + "note": "The corpus's light metaphor for the transport stream (\"on the wire\" → 圚线路䞊; \"nothing changes on the wire\" → 线路䞊没有任䜕变化). Never 电线 (a physical cable). Provisional pending native review.", + "avoid": ["电线"], + "enforce": false + }, + { + "source": "deprecated", + "target": "已匃甚", + "note": "Also \"deprecation warning\" → 匃甚譊告 and \"X is deprecated\" → X 已匃甚. Pin the 匃甚 family throughout; do not switch to 废匃 or 淘汰 for the same concept. Provisional pending native review.", + "avoid": ["废匃", "淘汰"], + "enforce": false + }, + { + "source": "context", + "target": "䞊䞋文", + "note": "The generic lower-case word (\"provide context to LLMs\" → 䞺 LLM 提䟛䞊䞋文). The capitalised `Context` is the SDK object injected as `ctx`; both are on the keep-in-source list and stay in English in prose (\"The Context\" → Context). Provisional pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "multi-round-trip", + "target": "倚蜮埀返", + "note": "The 2026-07-28 request pattern (\"Multi-round-trip requests\" → 倚蜮埀返请求); \"round-trip\" alone → 埀返. Provisional coinage: gloss the English on first use per page — 倚蜮埀返multi-round-trip. Pending native review.", + "avoid": [], + "enforce": false + }, + { + "source": "you", + "target": "䜠", + "note": "The register rule from instructions.md made machine-checkable: 悚 must never appear on a page. Prefer dropping the pronoun; when one is needed it is 䜠.", + "avoid": ["悚"], + "enforce": true + } + ] +} diff --git a/i18n/zh-CN/instructions.md b/i18n/zh-CN/instructions.md new file mode 100644 index 0000000000..8b1991bb40 --- /dev/null +++ b/i18n/zh-CN/instructions.md @@ -0,0 +1,146 @@ +# Simplified Chinese (zh-CN) — translation instructions + +Target language: Simplified Chinese (简䜓䞭文), directory and URL code +`zh-CN`, page language tag `zh-Hans`. This file is sent verbatim with every +translation request for this language, on top of the shared translation rules +in `../general-prompt.md`. The termbase in `glossary.json` is sent +alongside it and wins any terminology conflict with this file. + +## 1. Register + +Write the casual-neutral written register that Chinese developer +documentation uses: plain, matter-of-fact, and even. + +- Address the reader as 䜠. Never use the honorific 悚, and never mix the + two. The rule holds in body prose, headings, admonition titles, table + cells and link text. +- Prefer no pronoun at all when the sentence stays clear — Chinese + instructions read naturally without a subject: "You can pass a schema" → + 可以䌠入䞀䞪暡匏. Reach for 䜠 only where the sentence would otherwise be + ambiguous about who acts. +- Steps and instructions are bare imperatives without a subject: + "Run the server" → 运行服务噚, not 请悚运行服务噚. A single 请 is fine where + it reads natural; a 请 in front of every step is not. +- The register is uniform across a page. A page that drifts between 䜠 and + 悚, or between plain and formal sentence endings, is wrong even when each + sentence is acceptable on its own. + +## 2. Voice + +Aim for the voice of an experienced Chinese-speaking engineer explaining a +library to a colleague: warm, direct, professional, compact. The English is +built on short declarative payoff sentences ("That's a complete MCP +server."); keep them short — 这就是䞀䞪完敎的 MCP 服务噚。 + +Do: + +- Follow Chinese word order and rhythm. Break one long English sentence + into two Chinese ones instead of mirroring its clause structure. +- Use concrete verbs (运行, 䌠入, 返回, 声明, 阻塞) rather than nominal chains. +- Keep the source's directness. Where the English says "don't", the + Chinese says 䞍芁, not a hedge like 也讞可以考虑避免. + +Avoid — these are the marks of a machine or customer-service translation: + +- 悚, and its whole register: 枩銚提瀺, 亲, 敬请, 感谢悚的耐心等埅. +- Formal padding: 进行  操䜜, 对  进行倄理, and 可胜像䞋面这样, where a plain + 是这样的 does the job. +- English-shaped Chinese: possessive chains (䜠的服务噚的工具的暡匏), 被 passives + where a topic-comment sentence is natural, and a translated connective + (然而, å› æ­€, 歀倖) at the start of every sentence. +- Marketing hype and internet slang: 神噚, 保姆级, 给力, 区倧到没朋友. + +## 3. Humour and idioms + +The English is friendly and dry rather than jokey: short payoff sentences, +a few stock phrases, the rare emoji. Carry the friendliness; recast the +idioms. + +- Never translate a pun, idiom or aside literally. Say what it means as a + short, natural Chinese sentence in the same register. If an aside carries + no information you may drop it — but never drop a technical caveat that + happens to be phrased lightly. +- Recurring English tags get fixed renderings: "**[X](
)** has the whole + story" / "The whole story is in **[X](
)**" → 诊见 **[X](
)**; + "That's the whole API." / "That's the whole protocol." → 敎䞪 API 就这些。 + / 敎䞪协议就是这样。; "That's it. It's just Python." → 就这样只是普通的 + Python。 +- Idioms take the plain meaning, not the picture: "Out of the box the app + answers **only** requests addressed to localhost." → 默讀情况䞋这䞪应甚 + **只**响应发埀 localhost 的请求。— not the literal 匀箱即甚地. +- Emoji: keep the source's rare, deliberately placed emoji exactly where + they are — two payoff lines end in ✹ ("You get `3` back. ✹"). Never + add new ones. +- Exclamation marks are rare in Chinese technical prose and the English + hardly uses them; do not add one to a plain payoff sentence. + +Worked examples (source → good / bad): + +- "You get `3` back. ✹" → good: 返回倌是 `3`。✚ / bad: 䜠䌚埗到3✚ + (missing Han–Latin spacing, added exclamation). +- "Give a parameter a default value and it stops being required. That's + it. It's just Python." → good: 给参数讟䞀䞪默讀倌它就䞍再是必填参数。 + 就这样只是普通的 Python。/ bad: 给䞀䞪参数䞀䞪默讀倌然后它就停止是必需的了。 + 就是它。它只是Python而已(English-shaped 它 chain, missing Han–Latin + spacing, added exclamation). + +## 4. Typography + +- Chinese prose takes full-width punctuation: 。、“” with ‘’ + nested inside “”, the dash —— and the ellipsis 

. Punctuation inside + code spans, code blocks, commands, URLs and quoted English text stays + half-width and untouched. +- Enumerations in prose use the enumeration comma 、: "a, b, and c" → + a、b 和 c, not ab和 c. +- Put one half-width space between Han characters and any run of Latin + letters or digits (䜿甚 Streamable HTTP 䌠蟓; 需芁 Python 3.10+); put no + space between a full-width punctuation mark and adjacent Latin text + (配眮奜 stdio。). Keep the spaces around Markdown markers (`**
**`, + links) exactly as the source has them. +- No italics in Chinese text. Where the source italicises a word for + emphasis, use **bold**; where the source italicises an example utterance + or a hypothetical question the user might see, wrap it in “” instead. + Keep bold on the same words the source bolds — a bolded negation + ("**not**" → "**䞍是**" / "**䞍䌚**") stays bold. +- Digits stay half-width Arabic numerals. Protocol revision strings such + as `2026-07-28` and `2025-11-25` are identifiers, copied byte-for-byte — + never 2026幎7月28日, never 2026/07/28. Other dates keep the source's format. +- Numbers and units: half-width digits with a space before a Latin unit + (10 MB); % and ° attach with no space; a Chinese unit needs no space + (5 秒). +- Do not hard-wrap a line between two Han characters (the renderer turns + the line break into a stray space). Keep the source's block layout and + wrap only where the source wraps or after full-width punctuation. + +## 5. Terminology pointer + +The termbase is `glossary.json` next to this file. It is injected into the +prompt separately and its renderings override anything written here. This +section only fixes the conventions the glossary assumes: + +- Terms listed under `keep_in_source_language`, and any other English word + left in Latin script, are copied exactly as spelled and always in the + singular, with no article and no English plural "s": "the URIs" → URI, + "children" → child. They are never transliterated or re-cased; the spacing + rule in §4 sets them off from the surrounding Han text. +- Everything in code font, plus API names, class, function and parameter + names, protocol method and message strings (`tools/call`, + `notifications/...`), header names, error codes, SEP numbers and product + names, stays in Latin script inline. A glossary term used as a code-font + identifier stays Latin even though its prose noun is translated: + "the `sampling` capability" → `sampling` 胜力. +- First-use gloss: a translated MCP concept the reader may need to map back + to the English specification carries the English in full-width parentheses + on its first occurrence on a page — 采样sampling — and appears alone + after that. Each glossary entry's note says whether the term takes the + gloss. +- One rendering per term per page: the glossary target, every time. Where an + entry's note marks the choice as open or provisional, still use the listed + target consistently rather than picking per sentence. + +## 6. Provisional note + +The register, voice and terminology decisions above are provisional, +pending review by native Chinese-speaking readers. To propose a change, edit +this file or `glossary.json` in a pull request; never edit the generated +pages under `pages/`, which the next translation run overwrites. diff --git a/i18n/zh-CN/pages/get-started/first-steps.md b/i18n/zh-CN/pages/get-started/first-steps.md new file mode 100644 index 0000000000..22dadd806a --- /dev/null +++ b/i18n/zh-CN/pages/get-started/first-steps.md @@ -0,0 +1,138 @@ +# 第䞀步 {#first-steps} + +**[銖页](../index.md)** 节奏埈快写䞀䞪服务噚、运行它、调甚䞀䞪工具。 + +这䞀页走埗慢䞀些把服务噚胜暎露的䞉样䞜西郜过䞀遍并䞔沿途给每样䞜西䞀䞪名字。 + +## 宿䞻、客户端和服务噚 {#host-client-and-server} + +从这里匀始每䞀页郜䌚出现的䞉䞪词 + +* **宿䞻**是 LLM 应甚Claude、IDE、某䞪 agent 运行时。它就是甚户圚对话的那䞪䞜西。 +* **客户端**䜏圚宿䞻里面莟莣讲 MCP。宿䞻每连接䞀䞪服务噚就运行䞀䞪客户端。 +* **服务噚**是䜠甚这䞪 SDK 构建的䞜西。它向客户端暎露胜力从䞍盎接和暡型对话。 + +䜠写的是服务噚。宿䞻是别人的产品。SDK 也给了䜠䞀䞪 `Client`甚它来测试䜠的服务噚本页后面就䌚出现。 + +## 䞉种原语 {#the-three-primitives} + +䞀䞪服务噚恰奜暎露䞉类䞜西。区分它们的是**由谁决定䜿甚它们** + +| 原语 | 由谁控制 | 它是什么 | 瀺䟋 | +|--------------|-----------|-----------------------------------------|-------------------------| +| **工具** | 暡型 | 暡型调甚来执行劚䜜的凜数 | 䞀次 API 调甚、䞀次数据库写入 | +| **资源** | 应甚 | 宿䞻加蜜进暡型䞊䞋文的数据 | 文件内容、API 响应 | +| **提瀺词** | 甚户 | 甚户按名称调甚的可倍甚消息暡板 | 䞀䞪斜杠呜什、䞀䞪菜单项 | + +“由谁控制”正是这种划分的党郚芁点。工具运行是因䞺**暡型**决定调甚它。资源被附加进来是因䞺**应甚**刀断暡型需芁它。提瀺词运行是因䞺**甚户**选了它。 + +!!! info + 劂果䜠写过 Web API倧郚分盎觉䜠已经有了**资源**盞圓于 `GET` + 只加蜜数据䞍改变任䜕䞜西**工具**盞圓于 `POST`它䌚做事并䞔可胜有 + 副䜜甚。**提瀺词**没有对应的 HTTP 类比它曎接近甚户按名称运行的䞀条保存奜的查询。 + +## 䞀䞪服务噚䞉者霐党 {#one-server-all-three} + +```python title="server.py" hl_lines="6 12 18" +--8<-- "docs_src/first_steps/tutorial001.py" +``` + +䞉䞪普通凜数䞉䞪装饰噚。每䞪装饰噚就是党郚的泚册工䜜 + +* `@mcp.tool()` 让 `add` 成䞺䞀䞪**工具**。 +* `@mcp.resource("greeting://{name}")` 让 `greeting` 成䞺䞀䞪**资源暡板**URI 里的 `{name}` 就是凜数的参数。 +* `@mcp.prompt()` 让 `summarize` 成䞺䞀䞪**提瀺词**。它返回的字笊䞲䌚变成䞀条甚户消息。 + +其䜙的䞀切名称、描述、参数暡匏SDK 郜从凜数本身读取它的名字、它的文档字笊䞲、它的类型标泚。䜠从来䞍甚单独声明这些。 + +!!! tip + SDK 的䞀半有䞀条富入路埄`from mcp import Client` 和 + `from mcp.server import MCPServer`。䞍存圚 `from mcp import MCPServer`。 + +### 试䞀试 {#try-it} + +甹 MCP Inspector 运行它 + +```console +uv run mcp dev server.py +``` + +打匀它打印出的 URL。Inspector 每种原语各有䞀䞪标筟页按顺序逐䞪看。 + +**工具。** 只有䞀项`add`描述是“Add two numbers.”。衚单里有䞀䞪必填的敎数字段 `a`还有䞀䞪 `b`。填奜、调甚结果是 `3`。这䞪衚单是 Inspector 根据 `a: int, b: int` 生成的。其他客户端也䞀样。 + +**资源。** **Resources** 列衚是空的。`greeting` 圚 **Resource Templates** 䞋面因䞺 `greeting://{name}` 垊了参数圚有人提䟛 `name` 之前没有䞀䞪具䜓的资源可以列出。给它 `World` 然后读取 + +```text +Hello, World! +``` + +**提瀺词。** 只有䞀项`summarize`垊䞀䞪必填参数 `text`。䌠入䞀些文本获取它䜠䌚收到䞀条消息`role: user`内容是枲染后的字笊䞲。提瀺词就是这么回事䞀䞪构建消息的凜数。 + +Inspector 通过 **stdio** 运行了䜠的服务噚这是 MCP 服务噚可以讲的䌠蟓方匏之䞀。现圚还䞍甚选**[运行䜠的服务噚](../run/index.md)** 那䞀页䞓闚讲这䞪。 + +## 胜力 {#capabilities} + +䜠圚 Inspector 里看到了䞉䞪标筟页。它怎么知道有䞉䞪 + +客户端连接䞊来时服务噚䌚声明它的**胜力**它䌚响应哪几类请求。客户端根据这仜声明来决定该问什么。这段声明䜠从来没写过`MCPServer` 替䜠声明了。 + +自己看看。SDK 的 `Client` 盎接接受服务噚对象并**圚内存䞭**连接它没有子进皋也没有端口 + +```python +import asyncio + +from mcp import Client + +from server import mcp + + +async def main() -> None: + async with Client(mcp) as client: + print(client.server_capabilities.model_dump(exclude_none=True)) + + +asyncio.run(main()) +``` + +```text +{'prompts': {'list_changed': True}, 'resources': {'subscribe': True, 'list_changed': True}, 'tools': {'list_changed': True}} +``` + +那䞪字兞就是䜠的服务噚声明的**胜力**。它是每䞪连䞊来的客户端最先了解到的䞜西 + +| 胜力 | 客户端现圚可以调甚 | +|-------------|------------------------------------------------------------| +| `tools` | `tools/list`、`tools/call` | +| `resources` | `resources/list`、`resources/templates/list`、`resources/read` | +| `prompts` | `prompts/list`、`prompts/get` | + +`MCPServer` 提䟛党郚䞉种原语所以䞉者总是郜䌚被声明。 + +泚意没出现的䞜西。`completions`资源暡板和提瀺词的参数自劚补党需芁䜠自己写䞀䞪倄理凜数这䞪服务噚没有所以这项胜力䞍存圚行䞺良奜的客户端也䞍䌚去问。所有可选项郜是这䞪规则泚册了䞜西胜力就出现**[补党](../servers/completions.md)** 䌚证明这䞀点。 + +!!! info + `Client(mcp)` 就是这仜文档䞭每䞪瀺䟋甚来测试的那䞪内存内客户端䜠也䌚甚它来测试自己的 + 服务噚。它有䞓闚的䞀页**[测试](testing.md)**。 + +## 䜠没有写的䞜西 {#what-you-did-not-write} + +回倎看看这䞀页。䜠写了䞉䞪小小的 Python 凜数。䜠**没有**写 + +* JSON Schema。`a: int, b: int` **就是** `add` 的暡匏。 +* 请求倄理凜数。`tools/list`、`resources/read`、`prompts/get`党郜替䜠倄理奜了。 +* 胜力声明。`MCPServer` 替䜠生成了。 +* 䞀行协议代码。版本协商、JSON-RPC 垧封装、胜力亀换党郜发生圚 `mcp dev` 和 `Client(mcp)` 内郚䜠从倎到尟郜没看见。 + +这䞪比䟋就是 SDK 的党郚意义。 + +## 回顟 {#recap} + +* **宿䞻**是 LLM 应甚**客户端**是它讲 MCP 的那䞀半**服务噚**是䜠构建的䞜西。 +* 工具由**暡型**控制资源由**应甚**控制提瀺词由**甚户**控制。 +* 每种原语䞀䞪装饰噚`@mcp.tool()`、`@mcp.resource(uri)`、`@mcp.prompt()`。名称、描述和暡匏郜来自凜数本身。 +* 垊 `{param}` 的 URI 构成资源**暡板**它䞎具䜓资源分匀列出。 +* 服务噚的**胜力**是替䜠声明的客户端只䌚去问服务噚声明过的䞜西。 +* `Client(mcp)` 圚内存䞭连接服务噚对象从第䞀倩起就是䜠的测试工具。 + +䞋䞀步是 **[连接到真实宿䞻](real-host.md)**把这䞪服务噚真正攟进 Claude Desktop 或者 IDE 里。然后是 **[测试](testing.md)**䞀页内容、䞀䞪内存内客户端从歀䞍甚靠猜它胜䞍胜跑。再埀后每种原语各有䞀页从暡型驱劚的那䞪匀始**[工具](../servers/tools.md)**。 diff --git a/i18n/zh-CN/pages/handlers/elicitation.md b/i18n/zh-CN/pages/handlers/elicitation.md new file mode 100644 index 0000000000..1edabf1fa6 --- /dev/null +++ b/i18n/zh-CN/pages/handlers/elicitation.md @@ -0,0 +1,180 @@ +# 埁询 {#elicitation} + +工具干到䞀半、猺䞀䞪答案并䞍䞀定就埗倱莥。 + +**埁询elicitation**让它可以发问。圚䞀次工具调甚的䞭途甚户收到䞀䞪问题而他们的答案䌚回到同䞀次凜数调甚里。 + +有䞀种暡匏 + +* **衚单暡匏**䜠需芁䞀䞪倌䞀次确讀、䞀䞪日期、䞀䞪数量。䜠描述字段客户端枲染衚单。 +* **URL 暡匏**䜠需芁甚户去别的地方OAuth 同意页面、支付页面。他们圚那里做的任䜕事郜䞍经过协议。 + +发问也有䞀种方匏。銖选是**解析噚**把问题挂圚䞀䞪参数䞊由 SDK 去问——圚任䜕连接䞊郜可以䞍管客户端诎的是哪䞪协议幎代。盎接的方匏 `await ctx.elicit(...)` 是䞀䞪从**服务噚**发埀**客户端**的请求而这䞪通道只有圚客户端倄于旧匏连接规范版本 2025-11-25 或曎早时才存圚。本页䞀种郜䌚讲先从解析噚匀始。 + +## 甚解析噚发问 {#ask-with-a-resolver} + +决定敎䞪工具胜吊继续的问题——“确定吗䞉䞪匹配的莊户里选哪䞪”——可以从工具䜓里提出来攟进䞀䞪**解析噚**由框架替䜠发问。 + +标泚䞺 `Annotated[T, Resolve(fn)]` 的参数䌚圚执行工具䜓之前先运行 `fn` 来填充。解析噚已经知道倌时就盎接返回倌吊则返回 `Elicit(...)` 让框架去问 + +```python title="server.py" hl_lines="24-30 35-36" +--8<-- "docs_src/elicitation/tutorial004.py" +``` + +* `confirm_delete` 按名字读取工具自己的 `path` 参数列出文件倹内容**只圚必须时才埁询**——空文件倹䌚解析䞺 `Confirm(ok=True)`䞍需芁䞎客户端埀返。 +* `delete_folder` 标泚的是 `ElicitationResult[Confirm]`于是框架泚入完敎的结果工具甚 `match` 倄理每䞀种情况接受并确讀、接受䜆保留`ok=False`、拒绝、取消。 +* `confirm` 参数氞远䞍䌚出现圚工具的蟓入暡匏里——`path` 由客户端提䟛`confirm` 由解析噚提䟛。 + +劂果工具䞍需芁分支倄理就改䞺标泚未包装的暡型`Annotated[Confirm, Resolve(confirm_delete)]`接受时它拿到暡型拒绝或取消时调甚以错误䞭止。 + +解析噚圚**每䞀种**连接䞊郜胜甚。对于旧匏连接䞊的客户端SDK 盎接把问题发给它圚 **2026-07-28** 连接䞊SDK 把问题从调甚䞭**返回**出来客户端的䞋䞀次尝试垊䞊答案。䜠的解析噚完党察觉䞍到区别底层发生的事情诊见 **[倚蜮埀返请求multi-round-trip](multi-round-trip.md)**。 + +发问只是解析噚胜做的事情之䞀。曎通甚的机制——䞍发问就胜算出结果的䟝赖、䟝赖的䟝赖、暡型胜提䟛和䞍胜提䟛什么——圚 **[䟝赖](dependencies.md)** 页面。 + +## 圚工具内郚发问 {#ask-from-inside-the-tool} + +工具也可以圚自己的凜数䜓䞭途停䞋来发问。 + +!!! warning + `ctx.elicit()` 和 `ctx.elicit_url()` 是从**服务噚**发埀**客户端**的请求——这䞪通道只有圚客户端倄于旧匏连接规范版本 **2025-11-25** + 或曎早时才存圚。圚 **2026-07-28** 连接䞊䞍存圚服务噚发起的请求所以 + 这些调甚䌚倱莥。解析噚圚䞀种情况䞋郜胜甚。诊见 **[协议版本](../protocol-versions.md)**。 + +`await ctx.elicit()` 接收䞀条消息和䞀䞪 Pydantic 暡型 + +```python title="server.py" hl_lines="9-11 20-23 25" +--8<-- "docs_src/elicitation/tutorial001.py" +``` + +* **`Context`** 参数就是 `ctx.elicit` 的来源任䜕工具郜可以接收䞀䞪。这䞪对象有自己的页面**[Context](context.md)**。 +* `AlternativeDate` 是䜠想芁的答案的**暡匏**。 +* 这䞪工具是 `async def`。它必须是因䞺它䌚圚䞭途停䞋来等䞀䞪人。 +* 其他任䜕日期工具郜䌚立刻返回。只有必须问的时候它才问。 +* 甚户接受的日期䌚通过 `book_table` 自身回到流皋里。答案和其他蟓入没有区别劂果倇选日期也订满了䌚再问䞀次而䞍是盲目确讀。 + +### 客户端收到什么 {#what-the-client-receives} + +客户端收到䜠的消息以及旁蟹䞀仜由暡型生成的 JSON Schema + +```json +{ + "properties": { + "accept_alternative": { + "description": "Try another date?", + "title": "Accept Alternative", + "type": "boolean" + }, + "date": { + "default": "2025-12-26", + "description": "Alternative date (YYYY-MM-DD)", + "title": "Date", + "type": "string" + } + }, + "required": ["accept_alternative"], + "title": "AlternativeDate", + "type": "object" +} +``` + +这仜暡匏就是衚单。`Field(description=...)` 是标筟默讀倌䌚预填蟓入框并让该字段变䞺可选。这和 **[工具](../servers/tools.md)** 里描述的工具参数甚的是同䞀套 Pydantic 蜬 JSON Schema 的机制。 + +!!! warning + 埁询的暡匏没有工具蟓入暡匏那么有衚现力。只支持扁平的原始类型字段 + `str`、`int`、`float`、`bool`或者字笊䞲的 `Literal`它䌚变成 `enum`。 + 圚暡型里再嵌䞀䞪暡型`ctx.elicit` 䌚圚任䜕内容发送给客户端之前就抛出匂垞 + + ```text + TypeError: Elicitation schema field 'address' rendered as {'$ref': '#/$defs/Address'}, which is not a valid PrimitiveSchemaDefinition + ``` + + 䜠是圚打断䞀䞪正圚做事的人。劂果答案需芁嵌套那它本来就应该是工具的参数。 + +### 䞉种答案 {#the-three-answers} + +`result.action` 告诉䜠甚户做了什么而可胜性正奜只有䞉种 + +* `"accept"`甚户提亀了衚单。`result.data` 是䞀䞪 `AlternativeDate` 实䟋已经通过校验。 +* `"decline"`甚户诎了䞍。 +* `"cancel"`甚户没有选择就关掉了这䞪问题。 + +`result.data` 只圚 `"accept"` 时存圚所以瀺䟋里先检查 `result.action`。类型检查噚䌚区制这䞪顺序圚 `result.action == "accept"` 之后`result.data` 是 `AlternativeDate`圚这之前根本没有 `.data`。 + +拒绝䞍是错误。工具自己决定拒绝意味着什么这里是䞍预订然后正垞回倍暡型。 + +!!! tip + 答案圚䜠的代码看到它之前就已经按䜠的暡型校验过了。客户端给䞀䞪 `bool` 字段发 + `"maybe"`䞍䌚匄坏䜠的预订调甚䌚以暡匏䞍匹配的错误倱莥䜠的 `if` 根本䞍䌚执行。 + +## 把甚户匕到䞀䞪 URL {#send-the-user-to-a-url} + +有些䞜西绝䞍胜经过暡型或客户端凭据、卡号、OAuth 授权同意。对这些䜠芁的䞍是数据而是让甚户去某䞪地方 + +```python title="server.py" hl_lines="10-14 23" +--8<-- "docs_src/elicitation/tutorial002.py" +``` + +* `ctx.elicit_url()` 接收消息、芁访问的 **URL**以及䞀䞪由䜠指定的 `elicitation_id`任意胜圚䜠的服务噚内标识这次埁询的字笊䞲。 +* 结果里只有䞀䞪 action没有别的。`"accept"` 衚瀺甚户同意打匀这䞪 URL**䞍是**衚瀺他们完成了及䞀端的事情。 +* 支付发生圚垊倖圚甚户的浏览噚和䜠的支付服务商之闎。没有任䜕内容䌚通过 MCP 回来。 + +看看第二䞪工具。圓䜠的服务噚埗知垊倖流皋已经结束通过 webhook、蜮询这里甚第二䞪工具来暡拟`ctx.session.send_elicit_complete(...)` 䌚垊䞊同䞀䞪 `elicitation_id` 发送 `notifications/elicitation/complete`。客户端就是靠这䞪知道可以䞍甚再星瀺“正圚等埅支付  ”了。没有它客户端只胜靠猜。 + +## 客户端这䞀䟧 {#the-client-side} + +服务噚发问。客户端通过向 `Client(...)` 䌠入 **`elicitation_callback`** 来回答 + +```python title="client.py" hl_lines="6-7 18" +--8<-- "docs_src/elicitation/tutorial003.py" +``` + +* 䞀䞪回调同时倄理䞀种暡匏。`params` 是 `ElicitRequestFormParams` 和 `ElicitRequestURLParams` 的联合类型甚 `isinstance` 来分支。 +* 对于 URL把 `params.url` 展瀺给甚户然后返回他们选择的 action。绝䞍返回任䜕 `content`。 +* 对于衚单真实的应甚䌚枲染 `params.requested_schema`并把甚户的蟓入䜜䞺 `content` 返回。这里的实现总是甚䞀䞪写死的答案诎“是”而这正是测试里想芁的回调。 +* 䌠入回调同时也是**胜力声明**服务噚就是这样知道可以向这䞪客户端发问的。客户端还胜䞺服务噚回答哪些事情见 **[客户端回调](../client/callbacks.md)**。 + +!!! info + 埁询是从**服务噚**发埀**客户端**的请求而这类请求只存圚于经兞握手的䌚话䞭 + 所以这䞪客户端䌠了 `mode="legacy"`。 + 圚 **2026-07-28** 连接䞊工具改䞺通过调甚的**返回倌**来发问 + 那䞪流皋见 **[倚蜮埀返请求](multi-round-trip.md)**。 + +### 试䞀试 {#try-it} + +甹 Streamable HTTP 启劚衚单暡匏的 `ctx.elicit` 版 `server.py`就是垊 `book_table` 的那䞪䞀行呜什见 **[运行服务噚](../run/index.md)**然后运行客户端的 `main()`向 `book_table` 预订圣诞节圓倩。 + +回调䌚打印它收到的问题 + +```text +No tables for 2 on 2025-12-25. Would you like to try another date? +``` + +它回答 `{"accept_alternative": True, "date": "2025-12-27"}`而䞀盎卡圚 `await ctx.elicit(...)` 里等埅的工具完成了预订 + +```text +Booked a table for 2 on 2025-12-27. +``` + +现圚换成 URL 暡匏的 `server.py`把同䞀䞪 `main()` 指向 `pay_deposit`同䞀䞪回调走及䞀䞪分支打印支付铟接工具返回“Complete the payment in your browser.”。䞀次埀返发生圚调甚䞭途双向郜走通了。 + +!!! check + 现圚把 `Client` 里的 `elicitation_callback=` 去掉再䞺圣诞节圓倩调甚䞀次 `book_table`。 + 敎䞪调甚䌚以协议错误倱莥 + + ```text + Elicitation not supported + ``` + + 没有泚册回调的客户端从来没有声明 `elicitation` 胜力所以没有人可以问。䜠的工具拿到的 + 䞍是 `"decline"`而是䞀䞪匂垞。讟计时芁考虑这䞀点每䞀次埁询郜需芁对“劂果问䞍了怎么办”有䞀䞪合理的答案。 + +## 回顟 {#recap} + +* 标泚䞺 `Annotated[T, Resolve(fn)]` 的参数由解析噚填充解析噚圚必须发问时返回 `Elicit(...)`。它圚每䞀种连接䞊郜胜甚。 +* 暡匏是䞀䞪扁平的 Pydantic 暡型只允讞原始类型字段返回时䌚校验。 +* `result.action` 是 `"accept"`、`"decline"` 或 `"cancel"``result.data` 只圚 accept 时存圚。 +* `await ctx.elicit(message, schema=Model)` 圚工具䜓内郚发问`await ctx.elicit_url(message, url, elicitation_id)` 甚于所有䞍胜经过暡型的䞜西`ctx.session.send_elicit_complete(elicitation_id)` 衚瀺垊倖的那郚分已经完成。䞀者郜是服务噚发埀客户端的请求需芁客户端倄于旧匏连接䞊。 +* 客户端甚䞀䞪 `elicitation_callback` 来回答按 params 类型分支泚册它就等于声明了这䞪胜力。 +* 圚 2026-07-28 连接䞊服务噚䞍是掚送问题而是返回问题同䞀䞪回调由 **[倚蜮埀返请求](multi-round-trip.md)** 来驱劚。 + +这䞪返回背后的䞀切重试埪环、保技 `requestState`、自己驱劚它郜圚 **[倚蜮埀返请求](multi-round-trip.md)**。 diff --git a/i18n/zh-CN/pages/index.md b/i18n/zh-CN/pages/index.md new file mode 100644 index 0000000000..9f59f0d360 --- /dev/null +++ b/i18n/zh-CN/pages/index.md @@ -0,0 +1,97 @@ +# MCP Python SDK {#mcp-python-sdk} + +!!! info "本文档对应 v2即圓前的皳定发垃线" + 第䞀次接觊 v2或者从 v1 过来**[v2 有什么新变化](whats-new.md)** 甚五分钟垊䜠了解改劚**[迁移指南](migration.md)** 芆盖了每䞀倄砎坏性变曎。 + 仍圚䜿甚 v1.x它的文档圚 [v1.x 文档](https://py.sdk.modelcontextprotocol.io/v1/)。 + 哪里䞍顺手或者看䞍明癜[告诉我们](https://github.com/modelcontextprotocol/python-sdk/issues/new?template=v2-feedback.yaml)。 + +**Model Context Protocol (MCP)** 让应甚以标准化的方匏䞺 LLM 提䟛䞊䞋文把**提䟛**䞊䞋文这件事和䞎 LLM 的亀互本身分匀。 + +这是它的官方 Python SDK。甚它可以 + +* **构建 MCP 服务噚**向任意 MCP 宿䞻暎露工具、资源和提瀺词。 +* **构建 MCP 客户端**连接任意 MCP 服务噚。 +* 䜿甚所有标准䌠蟓方匏stdio、Streamable HTTP 和 SSE。 + +## 环境芁求 {#requirements} + +Python 3.10+。 + +## 安装 {#installation} + +=== "uv" + + ```bash + uv add "mcp[cli]" + ``` + +=== "pip" + + ```bash + pip install "mcp[cli]" + ``` + +`[cli]` 附加组件䌚提䟛 `mcp` 呜什匀发时䌚甚到它。 +每䞪䟝赖项的甚途见 [安装](get-started/installation.md)。 + +## 瀺䟋 {#example} + +### 创建 {#create-it} + +创建文件 `server.py` + +```python title="server.py" +--8<-- "docs_src/index/tutorial001.py" +``` + +这就是䞀䞪完敎的 MCP 服务噚。 + +它暎露了䞀䞪**工具** `add`以及䞀䞪暡板化的**资源** `greeting://{name}`。 + +### 运行 {#run-it} + +```console +uv run mcp dev server.py +``` + +这䌚启劚服务噚并打匀 [MCP Inspector](https://github.com/modelcontextprotocol/inspector)——䞀䞪甚来劚手试验的亀互匏界面。打匀它打印出来的 URL。 + +!!! note + Inspector 是䞀䞪 Node.js 应甚所以 `mcp dev` 需芁 `PATH` 䞭有 `npx`。 + +### 试䞀试 {#try-it} + +圚 Inspector 里进入 **Tools**甚 `a=1`、`b=2` 调甚 `add`。 + +返回倌是 `3`。✚ + +Inspector 根据䜠的类型提瀺生成了那䞪衚单`a` 是䞀䞪必填的敎数字段`b` 也是。Claude 以及其他所有 MCP 宿䞻同样劂歀。 + +现圚进入 **Resources**读取 `greeting://World` + +```text +Hello, World! +``` + +### 回顟 {#recap} + +再看䞀県䜠**没有**写的䞜西 + +* 没有 JSON Schema。`a: int, b: int` **就是**暡匏。 +* 没有请求解析没有序列化没有校验代码。 +* 完党没有协议倄理。 + +䜠写的是䞀䞪垊类型提瀺和文档字笊䞲的 Python 凜数。剩䞋的亀给 SDK。 + +## 接䞋来看什么 {#where-to-go-next} + +* **[快速匀始](get-started/index.md)** 垊䜠从安装走到䞀䞪可运行、已测试的服务噚。 +* 芁构建䞀䞪**䜿甚** MCP 服务噚的应甚从 **[客户端](client/index.md)** 匀始。 +* 已经有 FastAPI 或 Starlette 应甚了**[接入已有应甚](run/asgi.md)** 讲劂䜕圚其䞭挂蜜 MCP 服务噚。 +* 圚查某条具䜓的错误信息**[疑隟排查](troubleshooting.md)** 按原文逐字猖排。 +* 想知道 v2 有哪些改劚**[v2 有什么新变化](whats-new.md)** 甚五分钟讲完。 +* 从 v1 迁移先看 **[迁移指南](migration.md)**。 +* 圚扟某䞪确切的筟名**[API 参考](api/mcp/index.md)** 由源码生成。 +* 甹 LLM 阅读本文档同时以 [llms.txt](https://llmstxt.org/) 栌匏发垃 + [llms.txt](https://py.sdk.modelcontextprotocol.io/llms.txt) 是页面玢匕 + [llms-full.txt](https://py.sdk.modelcontextprotocol.io/llms-full.txt) 把所有页面攟圚䞀䞪文件里。 diff --git a/i18n/zh-CN/state.json b/i18n/zh-CN/state.json new file mode 100644 index 0000000000..1a13488049 --- /dev/null +++ b/i18n/zh-CN/state.json @@ -0,0 +1,164 @@ +{ + "pages": { + "get-started/first-steps.md": { + "block_hashes": [ + "f4e9527fee44ad246985003e8319e71c2b72f28e3301ee128f3d2c0e3f6d264d", + "3e1766d27f443ca4bbccdda3389f3dff2d2b6737133564ee0ca133a5dbfd958d", + "2a97f1d75b49d9a9b70e82ce3ad0ee70072b2676db3248614ae2ba29bc3fff28", + "59dc6c66de1c4ea589e4b984889814b842613605c638ae450db4fa44243aee06", + "ee42a5e18f8e868b5d29bb9db85915406e7e97e789013ee108ee2d199626d274", + "7d8344d6cf1ecfc4130812f643a148ea572ad7d319b371029197fe162d513602", + "cfcd0cd4aed4f275cb8cf632eed301192aed12aeb11c10c77fabab2fe0fc7b30" + ], + "inputs_hash": "af25ee0f3cb8086ea78e60ae50a8ded6f0ae6a01884d398e6153fb66d7e4e8a8", + "model": "claude-opus-5", + "source_hash": "3531c5686a6ff79aabe6f0f8a3cb96213b6fc13b0bbbb0e762dc9f4a827b4be3", + "translated_at": "2026-07-31T20:08:09Z" + }, + "handlers/elicitation.md": { + "block_hashes": [ + "f5dade066115eb33121f7bbb02a3fb288e95e2662001bea5eac322e23f4cbd1d", + "c368d8a40fd8d9879d90fbb10a81870fdb3ca84675bcf04c91740febe56ed4c4", + "191a9cfc619f523acf23ef20a04a9a318abe5280bb459f4691dfc2c60bc8dc34", + "ed47c27010f781038cd2095fedb17be05b16a9c2b1b93650416dd7e1be3154b7", + "4db8b6b1183f96319677911fd22baa3e65faf1b2f107b7d0929a3ee39ebd3ed4", + "0265da2c81bc50cf3a83415aa16c1ded414fe59a3e23e4c0b1a83cd03d41ba5c" + ], + "inputs_hash": "af25ee0f3cb8086ea78e60ae50a8ded6f0ae6a01884d398e6153fb66d7e4e8a8", + "model": "claude-opus-5", + "source_hash": "ef8afd716d136ff25d17a34059a933bb924ea238bd2faf04f80cc0d337c2e520", + "translated_at": "2026-07-31T19:11:10Z" + }, + "index.md": { + "block_hashes": [ + "3690a6eb6f57324f620f65d5242b3bb17c0ed4a5657cd4ba7ff3a28d7291245a", + "13e00a4af763653d8c663c64dd27871bdd23314d11110a143b8ee77357f76a75", + "cfcabc919e5d343b47af2641656a86f9a0abe9f250547368e64f7e4b5a3de4b0", + "c1550578c7bd96be08abb210e1c86261127c78ee09c1d8d666d08fa96271c30b", + "71115a8dc82e7ee1cd61b609da087038d8736629ebb365972c88010fd677cc00" + ], + "inputs_hash": "af25ee0f3cb8086ea78e60ae50a8ded6f0ae6a01884d398e6153fb66d7e4e8a8", + "model": "claude-opus-5", + "source_hash": "c46eefb49de18ed47882e13cf5bf08d0c3ca16ee77b98c257e8b66150c6be666", + "translated_at": "2026-07-31T21:09:40Z" + } + }, + "ui": { + "inputs_hash": "af25ee0f3cb8086ea78e60ae50a8ded6f0ae6a01884d398e6153fb66d7e4e8a8", + "model": "claude-opus-5", + "source": { + "@banner:disclosure": "??? info \"This page was machine-translated\"\n Translations of this documentation are generated automatically from the English pages, and the [English version of this page]({english_url}) is the authoritative one.\n\n Found a translation problem? See [how the translations work and how to report an issue]({translations_url}).\n", + "@banner:outdated": "!!! warning \"This translation may be behind the English page\"\n The English source changed after this page was last translated, so parts of it may be out of date. When in doubt, read the [English version of this page]({english_url}).\n", + "@banner:untranslated": "!!! note \"In English\"\n You're reading this page in English. [How the translations work]({translations_url}).\n", + "API Reference": "API Reference", + "Add to an existing app": "Add to an existing app", + "Advanced": "Advanced", + "Authorization": "Authorization", + "Caching": "Caching", + "Callbacks": "Callbacks", + "Clients": "Clients", + "Completions": "Completions", + "Connect to a real host": "Connect to a real host", + "Dependencies": "Dependencies", + "Deploy & scale": "Deploy & scale", + "Deprecated features": "Deprecated features", + "Elicitation": "Elicitation", + "Extensions": "Extensions", + "First steps": "First steps", + "Get started": "Get started", + "Handling errors": "Handling errors", + "Identity assertion": "Identity assertion", + "Images, audio & icons": "Images, audio & icons", + "Inside your handler": "Inside your handler", + "Installation": "Installation", + "Lifespan": "Lifespan", + "Logging": "Logging", + "MCP Apps": "MCP Apps", + "MCP Python SDK": "MCP Python SDK", + "Middleware": "Middleware", + "Migration Guide": "Migration Guide", + "Multi-round-trip requests": "Multi-round-trip requests", + "Multiple servers": "Multiple servers", + "OAuth": "OAuth", + "OpenTelemetry": "OpenTelemetry", + "Pagination": "Pagination", + "Progress": "Progress", + "Prompts": "Prompts", + "Protocol versions": "Protocol versions", + "Resources": "Resources", + "Running your server": "Running your server", + "Sampling and roots": "Sampling and roots", + "Servers": "Servers", + "Serving legacy clients": "Serving legacy clients", + "Structured Output": "Structured Output", + "Subscriptions": "Subscriptions", + "Testing": "Testing", + "The Context": "The Context", + "The low-level Server": "The low-level Server", + "Tools": "Tools", + "Translations": "Translations", + "Transports": "Transports", + "Troubleshooting": "Troubleshooting", + "URI templates": "URI templates", + "What's new in v2": "What's new in v2" + }, + "source_hash": "219aa0f5adfe57c4403a1b6fdf04d4e2cc79df194bdbcb30958d24e7bb387433", + "strings": { + "@banner:disclosure": "??? info \"本页由机噚翻译生成\"\n 本文档的译文由英文页面自劚生成以[本页的英文版]({english_url})䞺准。\n\n 发现翻译问题请参阅[翻译的工䜜方匏以及劂䜕反銈问题]({translations_url})。\n", + "@banner:outdated": "!!! warning \"本译文可胜萜后于英文页面\"\n 本页最后䞀次翻译之后英文原文已有改劚因歀郚分内容可胜已过时。劂有疑问请阅读[本页的英文版]({english_url})。\n", + "@banner:untranslated": "!!! note \"英文页面\"\n 䜠正圚阅读本页的英文版。[翻译的工䜜方匏]({translations_url})。\n", + "API Reference": "API 参考", + "Add to an existing app": "集成到现有应甚", + "Advanced": "进阶", + "Authorization": "授权", + "Caching": "猓存", + "Callbacks": "回调", + "Clients": "客户端", + "Completions": "补党", + "Connect to a real host": "连接真实宿䞻", + "Dependencies": "䟝赖", + "Deploy & scale": "郚眲䞎扩展", + "Deprecated features": "已匃甚的特性", + "Elicitation": "埁询", + "Extensions": "扩展", + "First steps": "第䞀步", + "Get started": "快速䞊手", + "Handling errors": "错误倄理", + "Identity assertion": "身仜断蚀", + "Images, audio & icons": "囟像、音频䞎囟标", + "Inside your handler": "圚倄理凜数内郚", + "Installation": "安装", + "Lifespan": "生呜呚期", + "Logging": "日志", + "MCP Apps": "MCP Apps", + "MCP Python SDK": "MCP Python SDK", + "Middleware": "äž­é—Žä»¶", + "Migration Guide": "迁移指南", + "Multi-round-trip requests": "倚蜮埀返请求", + "Multiple servers": "倚服务噚", + "OAuth": "OAuth", + "OpenTelemetry": "OpenTelemetry", + "Pagination": "分页", + "Progress": "进床", + "Prompts": "提瀺词", + "Protocol versions": "协议版本", + "Resources": "资源", + "Running your server": "运行服务噚", + "Sampling and roots": "采样䞎根目圕", + "Servers": "服务噚", + "Serving legacy clients": "服务旧版客户端", + "Structured Output": "结构化蟓出", + "Subscriptions": "订阅", + "Testing": "测试", + "The Context": "Context", + "The low-level Server": "䜎层 Server", + "Tools": "工具", + "Translations": "翻译", + "Transports": "䌠蟓方匏", + "Troubleshooting": "问题排查", + "URI templates": "URI 暡板", + "What's new in v2": "v2 新特性" + }, + "translated_at": "2026-07-31T21:09:40Z" + } +} diff --git a/mkdocs.yml b/mkdocs.yml index 06b293f876..f215e692ed 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -68,6 +68,7 @@ nav: - Extensions: advanced/extensions.md - MCP Apps: advanced/apps.md - Troubleshooting: troubleshooting.md + - Translations: translations.md - Migration Guide: migration.md - API Reference: api/ diff --git a/pyproject.toml b/pyproject.toml index 3c814106d1..c3cf30819e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,6 +102,9 @@ docs = [ # griffe's successor distribution (same author) and still imports as # `griffe`; the old `griffe` distribution is the incompatible 1.x line. "griffelib==2.1.0", + # The docs translation tool (scripts/docs/translations.py) calls the Claude API in + # its `translate` command; the rest of the pipeline is offline. + "anthropic>=0.120.2", ] codegen = ["datamodel-code-generator==0.57.0"] @@ -175,6 +178,7 @@ executionEnvironments = [ { root = "tests", extraPaths = [ ".", "examples", + "scripts/docs", ], reportUnusedFunction = false, reportPrivateUsage = false }, { root = "examples/stories", extraPaths = [ "examples", @@ -242,6 +246,9 @@ strict-no-cover = { git = "https://github.com/pydantic/strict-no-cover" } [tool.pytest.ini_options] log_cli = true +# tests/test_docs_i18n.py imports the docs translation tool, a top-level +# module under scripts/docs/. +pythonpath = ["scripts/docs"] xfail_strict = true markers = [ "requirement(id): links a test to the entry in tests/interaction/_requirements.py it exercises", diff --git a/scripts/docs/build.sh b/scripts/docs/build.sh index 8f545761bc..63faf083f2 100755 --- a/scripts/docs/build.sh +++ b/scripts/docs/build.sh @@ -1,15 +1,31 @@ #!/usr/bin/env bash # -# Build the v2 documentation site for this checkout into `site/`. +# Build the v2 documentation site for this checkout into `site/`: the English +# site at the root plus one machine-translated site per language listed in +# i18n/languages.yml, under `site//`. # -# Zensical runs no MkDocs plugins or hooks, so the build is three steps: -# materialise the API reference pages and the concrete config, build the -# site strictly (plus the order-independence and cross-reference checks -# Zensical doesn't do itself), then generate llms.txt and the per-page -# markdown renditions. This script is the single owner of that recipe, dependency -# sync included — CI (shared.yml, docs-preview.yml) and scripts/build-docs.sh -# all call it. The toolchain detection in docs-preview.yml and build-docs.sh -# keys on this file's path and expects the site under site/. +# Zensical runs no MkDocs plugins or hooks, so the English build is three +# steps: materialise the API reference pages and the concrete config, build +# the site strictly (plus the order-independence and cross-reference checks +# Zensical doesn't do itself), then generate llms.txt and the per-page markdown +# renditions. A language site is the lighter recipe against the docs tree the +# translation tool stages (English pages with translations overlaid and status +# notices stamped in); it carries no API reference of its own — its nav entry and +# prose links point at the English one. This script is the single owner of the +# recipe, dependency sync included — CI (shared.yml, docs-preview.yml) and +# scripts/build-docs.sh all call it. The toolchain detection in +# docs-preview.yml and build-docs.sh keys on this file's path and expects the +# site under site/. +# +# Environment: +# DOCS_LANGUAGES=en-only build only the English site (fast local loop) +# DOCS_LANGUAGES=ja,ko build only these language sites after English +# DOCS_SITE_URL= the URL the site is served from; defaults to +# mkdocs.yml's site_url. A PR preview passes its +# own host so every absolute link the build bakes +# (language switcher, banner links, links into the +# English API reference) resolves on the host the +# reader is browsing. # # Usage: # scripts/docs/build.sh @@ -27,9 +43,15 @@ uv sync --frozen --group docs # pages re-render silently drops cross-references to cache-hit pages, and # HTML for since-deleted pages lingers in site/. Build cold so the output # (and the checks below) are deterministic. -rm -rf .cache site +rm -rf .cache site .build/i18n + +# The language sites this build produces (comma-separated), decided once so +# every site's switcher lists exactly the sites that exist. +languages="${DOCS_LANGUAGES-$(uv run --frozen --no-sync python scripts/docs/translations.py languages | paste -sd, -)}" +[[ "$languages" == "en-only" ]] && languages="" +site_url="${DOCS_SITE_URL:-}" -uv run --frozen --no-sync python scripts/docs/build_config.py +uv run --frozen --no-sync python scripts/docs/build_config.py --languages "$languages" ${site_url:+--site-url "$site_url"} uv run --frozen --no-sync zensical build -f mkdocs.gen.yml --strict # The build above renders pages in one arbitrary (filesystem-dependent) @@ -44,3 +66,16 @@ uv run --frozen --no-sync python scripts/docs/check_render_order.py uv run --frozen --no-sync python scripts/docs/check_crossrefs.py --site-dir site uv run --frozen --no-sync python scripts/docs/llms_txt.py --site-dir site + +# The English site must already be in site/ before this loop: `zensical build` +# deletes anything foreign in its site_dir, so the English build (site_dir +# site/) would wipe every site//, while a language build (site_dir +# site//) leaves its siblings and parent alone. +for lang in ${languages//,/ }; do + echo "=== Building language site: ${lang} ===" + uv run --frozen --no-sync python scripts/docs/translations.py stage --lang "$lang" ${site_url:+--site-url "$site_url"} + uv run --frozen --no-sync python scripts/docs/build_config.py --lang "$lang" --languages "$languages" \ + ${site_url:+--site-url "$site_url"} + rm -rf .cache + uv run --frozen --no-sync zensical build -f "mkdocs.${lang}.gen.yml" --strict +done diff --git a/scripts/docs/build_config.py b/scripts/docs/build_config.py index daba648344..24c792f70f 100644 --- a/scripts/docs/build_config.py +++ b/scripts/docs/build_config.py @@ -6,12 +6,21 @@ gen_ref_pages) and writes `mkdocs.gen.yml` with the real API nav spliced in — that generated file is what `zensical build`/`serve` consumes. +With `--lang ` it writes `mkdocs..gen.yml` for a translated +language site instead: built from the staged tree the translation tool made +(`.build/i18n//docs`, see scripts/docs/translations.py) into `site//`, +with the sidebar labels translated and no API reference of its own — its API +entry links the English reference. Every config carries the same language +switcher (`extra.alternate`) listing the English site and each language site +this build produces (`--languages`, default: all enabled). + Usage: - python scripts/docs/build_config.py + python scripts/docs/build_config.py [--site-url URL] [--languages a,b] [--lang CODE] """ from __future__ import annotations +import argparse import posixpath import re from pathlib import Path @@ -19,6 +28,7 @@ # Both scripts live in this directory, which Python puts on sys.path[0] when # `build_config.py` is run directly (its documented invocation). import gen_ref_pages +import translations import yaml ROOT = Path(__file__).parent.parent.parent @@ -67,24 +77,69 @@ def _validate_nav(nav: list, docs_dir: Path) -> None: raise SystemExit(f"build_config: pages under docs/ that no nav entry reaches: {orphaned}") -def build_config() -> None: - config = yaml.safe_load((ROOT / "mkdocs.yml").read_text(encoding="utf-8")) +def _relabel(nav: list, labels: dict[str, str]) -> list: + """The nav with each translated sidebar label swapped in (page paths and unmapped labels untouched).""" + out = [] + for entry in nav: + if isinstance(entry, str): + out.append(entry) + continue + ((label, value),) = entry.items() + out.append({labels.get(label, label): _relabel(value, labels) if isinstance(value, list) else value}) + return out - api_nav = gen_ref_pages.generate() - if not api_nav: - raise SystemExit("build_config: gen_ref_pages produced no API pages — did the src/ layout move?") - for entry in config["nav"]: - if isinstance(entry, dict) and "API Reference" in entry: - entry["API Reference"] = api_nav - break - else: - raise SystemExit("build_config: no 'API Reference' entry found in mkdocs.yml nav") - _validate_nav(config["nav"], ROOT / "docs") +def build_config(lang: str | None = None, site_url: str | None = None, codes: list[str] | None = None) -> Path: + """Write the concrete config for the English site, or with `lang` for that language site. - output = ROOT / "mkdocs.gen.yml" + `site_url` is the URL the site is served from (mkdocs.yml's by default; a + PR preview passes its own host) and `codes` the language sites this build + produces, which is exactly what the language switcher offers. + """ + config = yaml.safe_load((ROOT / "mkdocs.yml").read_text(encoding="utf-8")) + registry = translations.load_registry() + base = (site_url or config["site_url"]).rstrip("/") + switched = registry.languages if codes is None else [registry.language(code) for code in codes] + if switched: # the same switcher on every site, listing exactly the language sites this build produces + config.setdefault("extra", {})["alternate"] = [ + {"name": "English", "link": f"{base}/", "lang": "en"}, + *({"name": la.name, "link": f"{base}/{la.code}/", "lang": la.hreflang} for la in switched), + ] + api = next((entry for entry in config["nav"] if isinstance(entry, dict) and "API Reference" in entry), None) + if api is None: + raise SystemExit("build_config: no 'API Reference' entry found in mkdocs.yml nav") + + if lang is None: + api_nav = gen_ref_pages.generate() + if not api_nav: + raise SystemExit("build_config: gen_ref_pages produced no API pages — did the src/ layout move?") + api["API Reference"] = api_nav + docs = ROOT / "docs" + config["site_url"] = f"{base}/" + output = ROOT / "mkdocs.gen.yml" + else: + # A language site carries no API reference of its own: its nav entry + # (and its prose links, rewritten at staging) point at the English one. + api["API Reference"] = f"{base}/api/mcp/" + config["plugins"] = [p for p in config["plugins"] if not (isinstance(p, dict) and "mkdocstrings" in p)] + config["nav"] = _relabel(config["nav"], translations.load_state(lang)["ui"].get("strings", {})) + config["theme"]["language"] = registry.language(lang).theme_language + docs = translations.staged_docs(lang) + config["docs_dir"] = f".build/i18n/{lang}/docs" + config["site_dir"] = f"site/{lang}" + config["site_url"] = f"{base}/{lang}/" + output = ROOT / f"mkdocs.{lang}.gen.yml" + + _validate_nav(config["nav"], docs) output.write_text(yaml.safe_dump(config, sort_keys=False, allow_unicode=True), encoding="utf-8") + return output if __name__ == "__main__": - build_config() + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--lang", metavar="CODE", help="build the config for this language site") + parser.add_argument("--site-url", metavar="URL", help="URL the site is served from (default: mkdocs.yml)") + parser.add_argument("--languages", metavar="a,b", help="language sites this build produces (default: all)") + args = parser.parse_args() + codes = None if args.languages is None else [c.strip() for c in args.languages.split(",") if c.strip()] + print(f"wrote {build_config(args.lang, args.site_url, codes).relative_to(ROOT)}") diff --git a/scripts/docs/translations.py b/scripts/docs/translations.py new file mode 100644 index 0000000000..772c2d5276 --- /dev/null +++ b/scripts/docs/translations.py @@ -0,0 +1,995 @@ +"""Machine-translate the documentation and stage the translated sites for the build. + +English under `docs/` is the source of truth. Each language listed in +`i18n/languages.yml` gets generated pages under `i18n//pages/`, driven +by that language's human-written `instructions.md` and `glossary.json` (see +`i18n/README.md`). Corrections go into those inputs, never into the generated +pages. + +Usage: + python scripts/docs/translations.py languages # enabled codes, one per line + python scripts/docs/translations.py status [--lang ja] # what is missing/outdated + python scripts/docs/translations.py translate --lang ja [--pages a.md ...] [--fresh] [--dry-run] [--no-verify] + python scripts/docs/translations.py stage --lang ja # assemble the docs tree the language site builds from + +`translate` calls the model (set `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN`); +every other command is offline. +""" + +from __future__ import annotations + +import argparse +import hashlib +import html +import json +import os +import posixpath +import re +import shutil +import sys +import unicodedata +from collections import Counter +from collections.abc import Callable, Iterable +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +import anthropic +import yaml +from llms_txt import page_url + +ROOT = Path(__file__).resolve().parent.parent.parent +DOCS = ROOT / "docs" +I18N = ROOT / "i18n" +ATTEMPTS = 3 + + +def sha256(text: str) -> str: + return hashlib.sha256(text.encode("utf-8")).hexdigest() + + +# --------------------------------------------------------------------------- +# Registry (i18n/languages.yml) and a language's prompt inputs +# --------------------------------------------------------------------------- + + +@dataclass(frozen=True) +class Language: + code: str + name: str + theme_language: str + hreflang: str + + +@dataclass(frozen=True) +class Registry: + languages: list[Language] + exclude_pages: list[str] + models: dict[str, str] # {"translate": ..., "verify": ...} + banners: dict[str, str] # kind -> English notice with {english_url}/{translations_url} placeholders + + def language(self, code: str) -> Language: + for language in self.languages: + if language.code == code: + return language + raise SystemExit(f"i18n: unknown language code {code!r} (see i18n/languages.yml)") + + +def load_registry() -> Registry: + """The language registry, English banner sources and model ids from `i18n/languages.yml`.""" + data = yaml.safe_load((I18N / "languages.yml").read_text(encoding="utf-8")) + languages = [ + Language( + item["code"], item["name"], item.get("theme_language", item["code"]), item.get("hreflang", item["code"]) + ) + for item in data["languages"] + ] + return Registry(languages, data.get("exclude_pages", []), data["models"], data["banners"]) + + +@dataclass(frozen=True) +class Inputs: + """The human-written prompt inputs of one language; `hash` fingerprints all of them.""" + + language: Language + general_prompt: str + instructions: str + glossary: dict[str, Any] + hash: str + + +def load_inputs(language: Language) -> Inputs: + general = (I18N / "general-prompt.md").read_text(encoding="utf-8") + instructions = (I18N / language.code / "instructions.md").read_text(encoding="utf-8") + glossary = (I18N / language.code / "glossary.json").read_text(encoding="utf-8") + return Inputs(language, general, instructions, json.loads(glossary), sha256(general + instructions + glossary)) + + +# --------------------------------------------------------------------------- +# Pages and sidebar labels, both read from mkdocs.yml +# --------------------------------------------------------------------------- + + +def mkdocs_config() -> dict[str, Any]: + return yaml.safe_load((ROOT / "mkdocs.yml").read_text(encoding="utf-8")) + + +def _nav_entries(nav: list[Any]) -> Iterable[tuple[str | None, str]]: + """Every `(label, page)` of the nav in order; a bare page has no label, a section title has no page.""" + for entry in nav: + if isinstance(entry, str): + yield None, entry + continue + label, value = next(iter(entry.items())) + if isinstance(value, list): + yield label, "" + yield from _nav_entries(value) + else: + yield label, value + + +def translatable_pages(nav: list[Any], exclude: list[str]) -> list[str]: + """The prose pages each language should carry, in nav order (the API reference stays English).""" + excluded = tuple(pattern.removesuffix("**") for pattern in ["api/", *exclude]) + return [page for _, page in _nav_entries(nav) if page.endswith(".md") and not page.startswith(excluded)] + + +def nav_labels(nav: list[Any]) -> list[str]: + """The sidebar labels (section titles and `Label: page.md` entries) in nav order, deduplicated.""" + return list(dict.fromkeys(label for label, _ in _nav_entries(nav) if label)) + + +def read_page(root: Path, page: str) -> str: + return (root / page).read_text(encoding="utf-8") + + +# --------------------------------------------------------------------------- +# Markdown structure: fences, headings and their anchors, links, ##-sections +# --------------------------------------------------------------------------- + +_FENCE = re.compile(r"^[ \t]*(?P`{3,}|~{3,})(?P[^\n]*)$") +_HEADING = re.compile(r"^(?P#{1,6})[ \t]+(?P.*?)[ \t]*$") +_ANCHOR = re.compile(r"[ \t]+\{[ \t]*#(?P[^}\s]+)[^}]*\}[ \t]*$") +_CODE_SPAN = re.compile(r"(?.+?)(?!?)\[(?P