docs: publish translated docs in twelve languages and the tool that maintains them (#3280)

This commit is contained in:
Max 2026-08-14 17:07:34 +01:00 • committed by GitHub
commit 52ad0a8876
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 89737 additions and 54 deletions

View file

@ -0,0 +1,145 @@
---
translation:
sections: [0d6c05bcbf836bf3, 59a7b14eeefc68c1, 7114d8d6daba203f, e8bbb56a98ba7bc9, 5138010f6159901c, f78da7c7c363d4c6, 220a939cab348686]
tool: 1
---
# Первые шаги {#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` **шаблоном ресурса**: `{name}` в URI — это параметр функции.
* `@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 по одной вкладке на каждый примитив; пройдитесь по ним по порядку.
**Tools.** Одна запись: `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.** Одна запись: `summarize` с единственным обязательным аргументом `text`. Получите его с каким-нибудь текстом — придёт одно сообщение с `role: user` и вашей готовой строкой в качестве содержимого. Вот и всё, что такое промпт: функция, которая собирает сообщения.
Inspector запустил ваш сервер через **stdio** — один из транспортов, на которых может говорить MCP-сервер. Выбирать транспорт пока не нужно; этому посвящена страница **[Запуск сервера](../run/index.md)**.
## Возможности {#capabilities}
В Inspector вы видели три вкладки. Откуда он узнал, что их три?
Когда клиент подключается, сервер объявляет свои **возможности**: на какие семейства запросов он будет отвечать. По этому объявлению клиент решает, о чём вообще имеет смысл спрашивать. Вы его не писали; `MCPServer` объявляет его за вас.
Посмотрите сами. Класс `Client` из SDK принимает объект сервера напрямую и подключается к нему **в памяти** (без подпроцесса, без порта):
```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()`. Имя, описание и схема берутся из функции.
* URI с `{param}` создаёт **шаблон** ресурса, который отображается отдельно от конкретных ресурсов.
* **Возможности** сервера объявляются за вас, а клиент спрашивает только о том, что сервер объявил.
* `Client(mcp)` подключается к объекту сервера в памяти: ваш тестовый стенд с первого дня.
Дальше — **[Подключение к настоящему хосту](real-host.md)**: этот же сервер внутри Claude Desktop или IDE, по-настоящему. Затем **[Тестирование](testing.md)**: одна страница, один клиент в памяти — и больше не придётся гадать, работает ли оно. После этого каждому примитиву отведена своя страница, начиная с того, которым управляет модель: **[Инструменты](../servers/tools.md)**.

View file

@ -0,0 +1,56 @@
---
translation:
sections: [ed4a756b4c53c585, 97e2fb315b7fe398, 4d04f1c6f4bf6c1d, 577d73078fc62baf]
tool: 1
---
# Начало работы {#get-started}
Впервые работаете с MCP или с этим SDK? Начните здесь. Эти страницы проведут вас от нуля до работающего, протестированного сервера: [установите SDK](installation.md), соберите
[первый сервер](first-steps.md), [подключите его к настоящему хосту](real-host.md) и
[протестируйте](testing.md) с помощью клиента в памяти.
## Запуск кода {#run-the-code}
Все блоки кода можно копировать и использовать как есть: это полные, рабочие файлы.
Чтобы повторять шаги по ходу чтения, вставьте блок в файл `server.py` и откройте его в MCP Inspector:
```console
uv run mcp dev server.py
```
**Настоятельно рекомендуем** писать (или копировать) код, править его и запускать локально. Именно работа в собственном редакторе показывает суть: как мало приходится писать, как помогает автодополнение, как проверка типов ловит ошибки ещё до запуска.
## Гадать не придётся {#you-will-not-be-guessing}
Каждый пример в этой документации — полный файл в каталоге [`docs_src/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/docs_src) в репозитории самого SDK, и каждый из них прогоняется набором тестов SDK через **клиент в памяти**:
```python
import pytest
from mcp import Client
from server import mcp
@pytest.mark.anyio
async def test_add() -> None:
async with Client(mcp) as client:
result = await client.call_tool("add", {"a": 1, "b": 2})
assert result.structured_content == {"result": 3}
```
Ни подпроцесса, ни порта, ни транспорта. `Client(mcp)` подключается к объекту сервера напрямую.
Если изменение в SDK ломает пример на одной из этих страниц, CI краснеет раньше, чем страница. Код, который вы здесь читаете, — это код, который выполняется.
Вы и сами воспользуетесь этим приёмом на странице [Тестирование](testing.md): свои серверы тестируют точно так же.
## Куда дальше {#where-to-go-next}
Как только сервер запущен, остальная документация — это справочник, а не курс.
Каждая страница самодостаточна, так что переходите сразу к нужному:
* Что сервер предоставляет (инструменты, ресурсы, промпты) — **[Серверы](../servers/index.md)**.
* Что доступно внутри регистрируемых функций — **[Внутри обработчика](../handlers/index.md)**.
* Как донести сервер до клиентов (stdio, HTTP, существующее FastAPI-приложение) — **[Запуск сервера](../run/index.md)**.
* Как построить другую сторону — приложение, которое *использует* MCP-серверы, — **[Клиенты](../client/index.md)**.

View file

@ -0,0 +1,48 @@
---
translation:
sections: [6e2f9bab94d5ed36, 8cf653388f69e28b, 6fd9ea2f65de0df6]
tool: 1
---
# Установка {#installation}
Python SDK опубликован на PyPI как [`mcp`](https://pypi.org/project/mcp/). Для работы нужен **Python 3.10+**.
Эта документация описывает **v2** — текущую стабильную линейку выпусков:
=== "uv"
```bash
uv add "mcp[cli]"
```
=== "pip"
```bash
pip install "mcp[cli]"
```
!!! note "Переходите с v1?"
v2 — мажорная версия с несовместимыми изменениями; все они описаны в
**[руководстве по миграции](../migration.md)**. Если ваш *пакет* зависит от `mcp` и ещё не готов
к переходу, сохраните верхнюю границу `<2` (например, `mcp>=1.28,<2`), чтобы при разрешении
зависимостей без фиксированных версий оставаться на линейке 1.x.
## Что устанавливается {#what-gets-installed}
Чтобы пользоваться SDK, всё это знать не обязательно, но если интересно, зачем нужна каждая зависимость:
* `mcp-types`: все типы протокола (запросы, результаты, блоки содержимого) в виде отдельного пакета, версии которого выходят синхронно с SDK. Код, зависящий от `mcp`, импортирует его через псевдоним `mcp.types` (каждый `from mcp.types import ...` в этой документации); импортируйте `mcp_types` напрямую только в проекте, который устанавливает `mcp-types` без SDK.
* [`anyio`](https://anyio.readthedocs.io/): асинхронная среда выполнения. Весь SDK написан поверх anyio, поэтому работает как на `asyncio`, так и на `trio`.
* [`pydantic`](https://docs.pydantic.dev/): основа всех моделей `mcp.types`, а также вся генерация схем и валидация.
* [`httpx2`](https://pypi.org/project/httpx2/): HTTP-клиент, на котором построены *клиентские* транспорты Streamable HTTP и SSE, со встроенной поддержкой server-sent events.
* [`starlette`](https://www.starlette.io/), [`uvicorn`](https://www.uvicorn.org/), [`sse-starlette`](https://pypi.org/project/sse-starlette/) и [`python-multipart`](https://pypi.org/project/python-multipart/): *серверные* HTTP-транспорты.
* [`jsonschema`](https://pypi.org/project/jsonschema/): проверяет структурированный вывод инструмента на соответствие объявленной выходной схеме.
* [`pyjwt[crypto]`](https://pyjwt.readthedocs.io/): работа с OAuth-токенами для авторизации.
* [`opentelemetry-api`](https://opentelemetry-python.readthedocs.io/): только лёгкий API, поэтому middleware трассировки в SDK ничего не стоит, пока вы сами не установите SDK и экспортёр OpenTelemetry.
* [`typing-extensions`](https://typing-extensions.readthedocs.io/) и [`typing-inspection`](https://pypi.org/project/typing-inspection/): современные возможности типизации на Python 3.10.
* [`pywin32`](https://pypi.org/project/pywin32/): только для Windows, используется для управления подпроцессами `stdio`.
## Дополнительные компоненты {#optional-extras}
* `mcp[cli]` добавляет [`typer`](https://typer.tiangolo.com/) и [`python-dotenv`](https://pypi.org/project/python-dotenv/) для утилиты командной строки `mcp` (`mcp dev`, `mcp run`, `mcp install`). Во время разработки она пригодится; в развёрнутом сервере может и не понадобиться.
* `mcp[rich]` добавляет [`rich`](https://rich.readthedocs.io/) для более красивых логов сервера.

View file

@ -0,0 +1,186 @@
---
translation:
sections: [3c4f2f06b4e978b6, 22520eecae3d1961, f4e1709db18d635a, 2eb57992049671d9, 1ba83e9af37cc1b4, 4822586344b08d9e, 1c93afef72478992, b6b448f9eddd51dc, fe55370fd931815b]
tool: 1
---
# Подключение к настоящему хосту {#connect-to-a-real-host}
**Хост** — это приложение, внутри которого в итоге оказывается ваш сервер: Claude Desktop, Claude Code, IDE. Именно с хостом говорит пользователь. Внутри него MCP-**клиент** запускает ваш сервер как дочерний процесс и общается с ним через stdin и stdout этого процесса.
А значит, подключение к хосту сводится к одному действию: сообщить ему **команду, которая запускает сервер**. Всё на этой странице (две команды CLI, три JSON-файла) — это разные места, куда кладётся одна и та же команда.
## Один сервер, любой хост {#one-server-every-host}
```python title="server.py" hl_lines="3 33-34"
--8<-- "docs_src/real_host/tutorial001.py"
```
Два инструмента и ресурс, один файл. Три вещи в этом файле важны для каждого хоста ниже:
* `mcp.run()` без аргументов запускает **stdio**-сервер: он блокируется, читает сообщения протокола из stdin и пишет их в stdout. Это тот транспорт, на котором говорят все хосты на этой странице. Хост запускает ваш файл как дочерний процесс и владеет обоими каналами, поэтому подключение всегда сводится к «вот команда». Порт выбирать не нужно, и ничто на нём не слушает.
* `run()` стоит под `if __name__ == "__main__":`. Всё, что ниже, **импортирует** этот файл, а не выполняет его, так что незащищённый `run()` запускал бы сервер в тот момент, когда что-нибудь загружает модуль.
* Объект сервера — глобальная переменная уровня модуля с именем `mcp`. Это имя ищет `mcp run` (`server` и `app` тоже подходят). Назовёте иначе — придётся указать имя явно: `mcp run server.py:bookshop`.
Это последняя строка Python на этой странице. Дальше — только настройка хостов.
## Команда запуска {#the-launch-command}
Каждый хост ниже получает одну и ту же команду:
```bash
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
Одна команда для всех, потому что `uv run --with` прямо на месте разворачивает SDK в свежем окружении: она работает из любого каталога, не требует проекта и не требует активировать виртуальное окружение. Здесь это важнее, чем где бы то ни было, потому что хост запускает сервер из *своего* рабочего каталога с почти пустым окружением, а не из вашей оболочки.
Это же команда, которую `mcp install` записывает за вас в конфигурацию Claude Desktop (ниже), так что набранное вручную и сгенерированное инструментом совпадают, если не считать точной фиксации версии, которую добавляет инструмент.
!!! tip "Если хост не находит `uv`"
Хост порождает ваш сервер с минимальным `PATH`, и `uv` в нём может не оказаться. Замените
голое `uv` абсолютным путём из `which uv` (macOS/Linux) или `where uv` (Windows). Именно
это и записывает `mcp install`.
!!! note "Эта страница — про локальный сценарий"
Всё здесь запускает сервер на той же машине, где находится хост: хост запускает ваш
файл через stdio. Это в точности то, что нужно для личного инструмента или инструмента
на одну машину. Чтобы отдать сервер людям, у которых *нет* вашего файла, раздают
**URL**, а не команду: тот же объект `mcp`, обслуживаемый по Streamable HTTP.
**[Запуск сервера](../run/index.md)** сводит это решение в одну таблицу, а
**[Развёртывание и масштабирование](../run/deploy.md)** — дорога оттуда к настоящему
имени хоста.
А хост — это всего лишь приложение с MCP-клиентом внутри, так что роль хоста может
сыграть ваш собственный код на Python: **[Клиентские транспорты](../client/transports.md)**
запускают этот же файл как подпроцесс через `stdio_client(...)`, а
**[Тестирование](testing.md)** подключается к нему в памяти вообще без процесса.
## Claude Desktop {#claude-desktop}
Единственный хост, который SDK умеет настроить за вас:
```bash
uv run mcp install server.py
```
Вот и всё. `mcp install` импортирует файл, чтобы прочитать имя сервера, находит файл конфигурации Claude Desktop и записывает в него команду запуска. Попутно она превращает ваш путь в абсолютный, чтобы этого не пришлось делать вам.
Никакой магии здесь нет. Вот запись, которую она создаёт:
```json
{
"mcpServers": {
"Bookshop": {
"command": "/absolute/path/to/uv",
"args": [
"run",
"--frozen",
"--with",
"mcp[cli]==2.0.0",
"mcp",
"run",
"/absolute/path/to/server.py"
]
}
}
}
```
Это команда запуска из раздела выше с тремя добавлениями: абсолютный путь к `uv`, `--frozen`, чтобы `uv` никогда не переписывал lock-файл, рядом с которым случайно окажется, и точная фиксация установленной у вас версии `mcp`. Запись попадает в `claude_desktop_config.json`, который лежит здесь:
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
Этот файл можно написать и вручную. `mcp install` существует затем, чтобы вы при этом не допустили классической ошибки (относительного пути).
Полностью завершите Claude Desktop (а не просто закройте окно) и откройте заново.
!!! warning
`mcp install` завершается ошибкой `Claude app not found`, если *каталога* конфигурации Claude
Desktop ещё не существует. Установите Claude Desktop и запустите его один раз: именно это
создаёт каталог.
!!! tip
Claude Desktop запускает сервер в собственном процессе, так что переменных окружения вашей
оболочки там нет. `uv run mcp install server.py -v API_KEY=abc123` (или `-f .env`) записывает
их в поле `env` записи. `--name` переопределяет имя записи; по умолчанию берётся `name`
сервера.
## Claude Code {#claude-code}
Редактировать никакой файл не нужно. Зарегистрируйте сервер через CLI `claude`; всё после `--` — это команда запуска.
```bash
claude mcp add bookshop -- uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
Выполните `/mcp` внутри сессии Claude Code, чтобы убедиться, что `bookshop` подключён и его инструменты перечислены.
## Cursor {#cursor}
Создайте `.cursor/mcp.json` в корне проекта.
```json
{
"mcpServers": {
"bookshop": {
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
}
}
}
```
Те же `command` и `args` под тем же ключом `mcpServers`, что использует Claude Desktop. Сервер появляется в настройках MCP Cursor с обоими инструментами в списке.
## VS Code {#vs-code}
Создайте `.vscode/mcp.json` в корне проекта.
```json
{
"servers": {
"bookshop": {
"type": "stdio",
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/absolute/path/to/server.py"]
}
}
}
```
Два отличия от файла Cursor, и только эти два: ключ-обёртка — `servers`, а не `mcpServers`, и каждая запись объявляет свой `type`. Подтвердите запрос о доверии, после чего **MCP: List Servers** в палитре команд покажет запущенный `bookshop`.
!!! note
Нужен VS Code 1.99 или новее с расширением **GitHub Copilot**, в котором выполнен вход
(достаточно Copilot Free), и Copilot Chat должен быть в режиме **Agent**, потому что никакой
другой режим не вызывает инструменты.
## Сервер не появляется {#it-doesnt-show-up}
Прежде чем трогать конфигурацию какого-либо хоста, выполните команду запуска сами:
```bash
uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py
```
Ничего не печатается, и команда не завершается. Эта тишина правильна: stdio-сервер ждёт, пока хост первым заговорит через stdin (`Ctrl-C`, чтобы остановить). Настоящая ошибка — это трассировка или немедленный выход, и теперь её можно прочитать, а не угадывать через хост.
Когда эта команда спокойно ждёт, остаётся почти всегда одно из трёх:
* **Относительный путь.** Хост запускает сервер из *своего* рабочего каталога, а не из того, где вы его регистрировали. `server.py` там, где нужен `/absolute/path/to/server.py`, — самая распространённая ошибка. Если хост не находит и `uv`, этот путь тоже должен быть абсолютным.
* **Хост всё ещё работает со старой конфигурацией.** Хосты читают конфигурацию при запуске. Claude Desktop в особенности нужно *полностью завершить* (а не просто закрыть окно) и открыть заново, прежде чем правка в `claude_desktop_config.json` вступит в силу.
* **Что-то попало в stdout вне перенаправляемого окна.** На stdio stdout *и есть* протокол. SDK на время обслуживания перенаправляет сброшенный посторонний вывод в stderr, но вывод, сброшенный в stdout до этого (скрипт-обёртка с echo, `print()` при импорте в небуферизованном процессе), или буферизованный `print()`, слитый при завершении интерпретатора, отдаёт хосту испорченное сообщение, и тот разрывает соединение. Пишите логи с конфигурацией `logging` по умолчанию, чей stderr-обработчик сбрасывает каждую запись; пользовательские обработчики тоже должны избегать stdout. Подробнее — на странице **[Логирование](../handlers/logging.md)**.
Claude Desktop ведёт лог для каждого сервера: `mcp-server-<NAME>.log` — это stderr вашего сервера, рядом с `mcp.log` для подключений, в `~/Library/Logs/Claude` на macOS и `%APPDATA%\Claude\logs` на Windows.
Всё, что выходит за рамки этих трёх случаев, — на странице **[Устранение неполадок](../troubleshooting.md)**.
## Итоги {#recap}
* **Хост** (Claude Desktop, IDE) содержит MCP-клиент, который запускает ваш сервер как дочерний процесс через stdio. Подключиться — значит дать ему одну команду запуска.
* Эта команда — `uv run --with "mcp[cli]" mcp run /absolute/path/to/server.py`: не нужно активировать venv, работает из любого каталога.
* **Claude Desktop** — единственный хост, который `mcp install` настраивает за вас. Она записывает ту же команду (плюс абсолютный путь к `uv`, `--frozen` и точную фиксацию установленной у вас версии) в `claude_desktop_config.json`, чтобы этого никогда не пришлось делать вам.
* **Claude Code** — это `claude mcp add bookshop -- <launch command>`. **Cursor** — `.cursor/mcp.json` под ключом `mcpServers`. **VS Code** — `.vscode/mcp.json` под ключом `servers`, каждая запись с `type`.
* Везде абсолютные пути, перезапуск хоста после правки его конфигурации, и ничто, кроме SDK, не должно писать в stdout.
Каждый хост на этой странице подключился к одному и тому же файлу одной и той же командой. То, что этот файл может *предоставлять*, — остальная документация: **[Инструменты](../servers/tools.md)**, **[Ресурсы](../servers/resources.md)** и все транспорты, кроме stdio, на странице **[Запуск сервера](../run/index.md)**.

View file

@ -0,0 +1,116 @@
---
translation:
sections: ['4926721070127497', c52a1de2b6b32f40, 2e410b412c25f314, 627195f7159e24ef]
tool: 1
---
# Тестирование {#testing}
В Python SDK есть класс `Client` со **встроенным in-memory транспортом**: передайте ему объект сервера, и он подключится к нему напрямую.
Ни подпроцесса. Ни порта. Вообще никакого транспорта. Та же идея, что и `TestClient` в FastAPI.
## Базовое использование {#basic-usage}
Предположим, есть простой сервер с одним инструментом:
```python title="server.py"
--8<-- "docs_src/testing/tutorial001.py"
```
Чтобы запустить тест ниже, понадобятся две дополнительные зависимости (для разработки):
=== "uv"
```bash
uv add --dev pytest inline-snapshot
```
=== "pip"
```bash
pip install pytest inline-snapshot
```
!!! info
Эта документация предполагает, что вы уже знакомы с [`pytest`](https://docs.pytest.org/en/stable/).
[`inline-snapshot`](https://15r10nk.github.io/inline-snapshot/latest/) — то, с помощью чего
тест ниже проверяет весь объект результата одной строкой. Библиотека записывает вывод теста
в виде литерала `snapshot(...)`, который вы видите. Если не хотите её использовать, уберите
импорт и проверяйте нужные поля (`result.content[0].text == "3"`), как в любом другом тесте.
Теперь сам тест:
```python title="test_server.py"
import pytest
from inline_snapshot import snapshot
from mcp import Client
from mcp.types import CallToolResult, TextContent
from server import mcp
@pytest.fixture
def anyio_backend(): # (1)!
return "asyncio"
@pytest.fixture
async def client(): # (2)!
async with Client(mcp, raise_exceptions=True) as c:
yield c
@pytest.mark.anyio
async def test_call_add_tool(client: Client):
result = await client.call_tool("add", {"a": 1, "b": 2})
# Drop the server identity stamp in `_meta`; it is not what this test is about.
result.meta = None
assert result == snapshot(
CallToolResult(
content=[TextContent(type="text", text="3")],
structured_content={"result": 3},
)
)
```
1. Если используете `trio`, верните вместо этого `"trio"`. Подробности — в [документации anyio](https://anyio.readthedocs.io/en/stable/testing.html#specifying-the-backends-to-run-on).
2. Фикстура отдаёт подключённый клиент. Каждый тест, принимающий `client`, получает свежее in-memory подключение к тому же серверу.
Готово! Теперь можно расширять тесты, чтобы покрыть больше сценариев.
## Зачем `raise_exceptions=True`? {#why-raise_exceptionstrue}
Пойти не так могут две разные вещи, и этот флаг касается только одной из них.
Исключение внутри одного из **ваших инструментов** — не сбой протокола. Оно превращается в
обычный результат с `is_error=True`, и модель читает сообщение. `raise_exceptions` этого не
меняет: с ним или без него `call_tool` возвращает один и тот же результат с `is_error=True`.
Этому посвящена целая страница: **[Обработка ошибок](../servers/handling-errors.md)**.
Сбой **вне** тела инструмента — другое дело. На подключении, которое даёт `Client(mcp)`, сервер
очищает его до обобщённого `"Internal server error"`, прежде чем оно дойдёт до клиента. Детали
неожиданного падения никогда не должны утекать к удалённому вызывающему. В тесте это ровно то,
чего вы *не* хотите, и именно это меняет `raise_exceptions=True`: тест видит настоящее сообщение
вместо очищенного.
В тестах оставляйте его включённым. В продакшен-коде он не имеет смысла.
## Внутри процесса по умолчанию {#in-process-by-default}
!!! note
`Client(mcp)` подключается внутри процесса и по умолчанию **не привязан к поколению
протокола**: он опрашивает сервер и выбирает подходящий путь протокола. Зафиксируйте
`mode="legacy"`, если тест проверяет семантику, специфичную для подключений старого
поколения (push-сообщения сэмплирования (sampling) или элицитации (elicitation),
`message_handler`), и уберите там `raise_exceptions=True`: подключение старого поколения
вообще ничего не очищает, а флаг повторно выбрасывает сбой внутри задачи сервера, а не в
вашем тесте.
Именно благодаря этой одной строке документация может обещать, что её примеры работают: каждый
файл с примером прогоняется собственным набором тестов SDK, и почти все — ровно через этот
клиент. Вы пользуетесь тем же инструментом, которым SDK проверяет сам себя.
У вас есть работающий, протестированный сервер. Как поместить его в настоящее приложение
(Claude Desktop, IDE) — на странице **[Подключение к настоящему хосту](real-host.md)**; все
остальные способы его запустить — в разделе **[Запуск сервера](../run/index.md)**.