docs: refresh translations, and translate pages in parallel (#3458)

This commit is contained in:
Max 2026-09-07 13:35:53 +01:00 • committed by GitHub
commit fd66270926
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 4821 additions and 2277 deletions

View file

@ -1,6 +1,6 @@
---
translation:
sections: [1062ef792791488a, 4be2b831547184a9, 374b049e770385f2, b72f6947089e6de0, b172c9db7831bb31, 70b9ece244ca1b0c, cba78e052898c3f6, f06bdb541cb0b469, fb82d526320b7cc3]
sections: [1062ef792791488a, 4be2b831547184a9, 374b049e770385f2, b72f6947089e6de0, b172c9db7831bb31, 10394c6f16601638, cba78e052898c3f6, f06bdb541cb0b469, 6dc898ccc5a903f9]
tool: 1
---
# Додавання до наявного застосунку {#add-to-an-existing-app}
@ -99,7 +99,7 @@ uvicorn server:app
--8<-- "docs_src/asgi/tutorial004.py"
```
Тепер клієнти під'єднуються до `/notes`, а не до `/notes/mcp`.
Тепер клієнти під'єднуються до `/notes/`, а не до `/notes/mcp`.
## CORS для браузерних клієнтів {#cors-for-browser-clients}
@ -142,4 +142,4 @@ uvicorn server:app
* Браузерним клієнтам потрібен CORS: `allow_headers` для заголовків запиту `Mcp-*`, `expose_headers=["Mcp-Session-Id"]` для відповіді.
* `@mcp.custom_route()` додає звичайні HTTP-кінцеві точки без автентифікації поруч із `/mcp`.
Щойно сервер стане досяжним за справжньою URL-адресою, **[Клієнт](../client/index.md)** під'єднається до нього за цією URL-адресою, а не через об'єкт сервера.
Щойно сервер стане досяжним за справжньою URL-адресою, **[Клієнт](../client/index.md)** під'єднається до нього за цією URL-адресою.

View file

@ -1,60 +1,64 @@
---
translation:
sections: [d62c13457fc4a534, 80e73abaca6e0652, d1dc4c54cd00ec9c, 14ad3bc7904036bb, 5225f127bc1b9c77, fe1626fdd5aad1da, 4556cb7ea1a04a31]
sections: [d62c13457fc4a534, 80e73abaca6e0652, 128a492d18295f64, 14ad3bc7904036bb, 54a6697833fcc1d9, fe1626fdd5aad1da, 811d083c1da8bcf6]
tool: 1
---
# Авторизація {#authorization}
Через Streamable HTTP ваш MCP-сервер — це звичайний вебсервіс, і захищати його слід так само, як будь-який інший вебсервіс: bearer-токенами OAuth 2.1.
Через Streamable HTTP ваш MCP-сервер — це звичайний вебсервіс, і захищати його треба так само, як будь-який вебсервіс: bearer-токенами OAuth 2.1.
У термінах OAuth ваш сервер — це **сервер ресурсів** (resource server). Він ніколи нікого не автентифікує й ніколи не видає токенів. Він робить одне: дивиться на заголовок `Authorization` у кожному запиті й вирішує, чи придатний токен у ньому.
У термінах OAuth ваш сервер — це **сервер ресурсів**. Він нікого не автентифікує і ніколи не видає токенів. Він робить одне: дивиться на заголовок `Authorization` кожного запиту й вирішує, чи дійсний токен у ньому.
Ця сторінка — про серверний бік. Клієнт, який знаходить ваш сервер авторизації й отримує токен, описано на сторінці **[Клієнти OAuth](../client/oauth-clients.md)**.
Ця сторінка — про серверну частину. Клієнт, який знаходить ваш сервер авторизації й отримує токен, описаний на сторінці **[OAuth-клієнти](../client/oauth-clients.md)**.
## Три сторони {#the-three-parties}
* **Сервер авторизації** автентифікує людей і видає токени доступу. Ви його не пишете. Це ваш постачальник ідентичності (Auth0, Keycloak, Entra, ваш власний).
* **Сервер авторизації** автентифікує людей і видає токени доступу. Його ви не пишете. Це ваш постачальник ідентичності (Auth0, Keycloak, Entra, власний).
* **Сервер ресурсів** — це ваш MCP-сервер. Він перевіряє токен у кожному запиті.
* **Клієнт** з'ясовує, якому серверу авторизації ви довіряєте, отримує від нього токен і надсилає його вам як `Authorization: Bearer <token>`.
Оце й увесь трикутник. Усе на цій сторінці стосується середнього пункту.
Оце й увесь трикутник. Усе на цій сторінці — про середній пункт.
## Верифікатор токенів {#a-token-verifier}
SDK не має власної думки про те, який вигляд має дійсний токен. Це визначаєте ви, реалізувавши **`TokenVerifier`**:
SDK не має власної думки про те, який токен вважати дійсним. Це визначаєте ви, реалізуючи **`TokenVerifier`**:
```python title="server.py" hl_lines="12-14 19-24"
```python title="server.py" hl_lines="14-16 21-27"
--8<-- "docs_src/authorization/tutorial001.py"
```
* `TokenVerifier` — це протокол з одним асинхронним методом. `verify_token` отримує сирий токен із заголовка `Authorization` і повертає **`AccessToken`**, якщо він дійсний, або `None`, якщо ні. Більше нічого реалізовувати не треба.
* Цей верифікатор шукає токен у таблиці. Справжній перевіряє підпис JWT або звертається до кінцевої точки інтроспекції токенів сервера авторизації. Цей код — ваш; SDK лише викликає його.
* `token_verifier=` і `auth=` завжди йдуть разом. Передайте одне без іншого — і `MCPServer(...)` викине `ValueError` ще до того, як обслужить хоч один запит.
* `TokenVerifier` — це протокол з одним асинхронним методом. `verify_token` отримує сирий токен із заголовка `Authorization` і повертає **`AccessToken`**, якщо токен дійсний, або `None`, якщо ні. Більше нічого реалізовувати не потрібно.
* Цей шукає токен у таблиці; кожен запис фіксує ресурс, для якого токен видано. Справжній перевіряє підпис JWT або викликає кінцеву точку інтроспекції токенів сервера авторизації і повідомляє, для кого видано токен (його `aud`), у `AccessToken.resource`. Цей код — ваш; SDK його лише викликає.
* `token_verifier=` і `auth=` завжди йдуть у парі. Передайте одне без іншого — і `MCPServer(...)` викине `ValueError`, ще не обслуживши жодного запиту.
`AuthSettings` — це публічне обличчя вашого сервера ресурсів:
`AuthSettings` — публічне обличчя вашого сервера ресурсів:
* `issuer_url`: сервер авторизації, що видає ваші токени.
* `resource_server_url`: публічний URL цієї MCP-кінцевої точки. Він указує, для *якого* ресурсу призначено токен, і саме тут розміщено документ виявлення.
* `resource_server_url`: публічна URL-адреса цієї кінцевої точки MCP. Вона вказує, для *якого* ресурсу призначено токен, і саме за нею лежить документ виявлення.
* `required_scopes`: кожен токен мусить містити їх усі.
* `validate_token_resource`: відхиляти будь-який токен, чий `AccessToken.resource` не дорівнює `resource_server_url`. Якщо залишити його незаданим, коли `resource_server_url` задано, з'являється попередження (`MCPDeprecationWarning`), а поведінка відповідає `False`; у 3.0 типовим значенням для серверів ресурсів стане `True`.
* Увімкніть його, якщо ваш сервер авторизації прив'язує токени до `resource`, який запросив клієнт, — а MCP-клієнти завжди його надсилають. `resource_server_url` має бути точно тією URL-адресою, до якої під'єднуються клієнти.
* Залиште вимкненим, якщо ваш сервер авторизації використовує власні ідентифікатори аудиторії (ідентифікатор API в Auth0, ідентифікатор застосунку в Entra), і натомість перевіряйте `aud` у своєму верифікаторі, повертаючи `None` для токена, що не призначений цьому серверу.
* Якщо `aud` — список, покладіть у `resource` той елемент, що дорівнює `resource_server_url`.
!!! tip
`examples/servers/simple-auth/` у репозиторії SDK містить `IntrospectionTokenVerifier`, який звертається
до кінцевої точки [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) справжнього сервера авторизації. Саме таку форму мають більшість продакшн-верифікаторів.
У `examples/servers/simple-auth/` у репозиторії SDK є `IntrospectionTokenVerifier`, який викликає
кінцеву точку [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) справжнього сервера авторизації. Саме таку форму має більшість верифікаторів у реальних розгортаннях.
## Що з'являється через HTTP {#what-you-get-over-http}
## Що ви отримуєте через HTTP {#what-you-get-over-http}
Авторизація живе в HTTP-заголовках, тож існує лише на HTTP-транспортах. Запустіть сервер на тому, який розгортаєте: `mcp.run(transport="streamable-http")` розміщує його на `http://127.0.0.1:8000/mcp`, а решта — на сторінці **[Запуск сервера](index.md)**. Тепер застосунок має два маршрути:
Авторизація живе в HTTP-заголовках, тож існує лише на HTTP-транспортах. Запускайте її на тому, який розгортаєте: `mcp.run(transport="streamable-http")` піднімає сервер на `http://127.0.0.1:8000/mcp`, а решту описано на сторінці **[Запуск сервера](index.md)**. Тепер застосунок має два маршрути:
```text
/mcp
/.well-known/oauth-protected-resource/mcp
```
Ви зареєстрували один інструмент. Другий маршрут належить SDK.
Ви зареєстрували один інструмент. Другий маршрут — від SDK.
### Виявлення {#discovery}
Зробіть `GET` на цей well-known-шлях — і отримаєте **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) Protected Resource Metadata**, побудовані безпосередньо з вашого `AuthSettings`:
Зробіть `GET` на цей well-known шлях — і отримаєте **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) Protected Resource Metadata**, побудовані прямо з ваших `AuthSettings`:
```json
{
@ -65,7 +69,7 @@ SDK не має власної думки про те, який вигляд м
}
```
Саме завдяки цьому документу клієнт, який ніколи не чув про ваш сервер, знаходить дорогу: він читає `authorization_servers` і йде туди по токен. Ви не написали з нього жодного рядка.
Саме завдяки цьому документу клієнт, який ніколи не чув про ваш сервер, знаходить шлях усередину: він читає `authorization_servers` і йде туди по токен. Ви не написали з нього жодного рядка.
!!! check
Зверніться до `/mcp` без токена (або з таким, для якого ваш верифікатор повернув `None`) — і запит
@ -78,26 +82,26 @@ SDK не має власної думки про те, який вигляд м
{"error": "invalid_token", "error_description": "Authentication required"}
```
Нічого не було розібрано, і жоден інструмент не виконався. А вказівник `resource_metadata` у `WWW-Authenticate` —
саме те, що робить виявлення автоматичним: 401 -> документ метаданих -> сервер авторизації -> токен -> повторна спроба.
Нічого не розібрано, жоден інструмент не виконувався. А вказівник `resource_metadata` у `WWW-Authenticate` —
це те, що робить виявлення автоматичним: 401 -> документ метаданих -> сервер авторизації -> токен -> повторна спроба.
!!! warning
Ніщо з цього не захищає `stdio`. Канал не має заголовка `Authorization`, тож `token_verifier` там ніколи
не викликається. Межа безпеки `stdio`-сервера — це процес, який його запустив. Те саме
стосується `Client(mcp)` у пам'яті, який ви використовуєте в тестах: він під'єднується безпосередньо до об'єкта сервера
й оминає HTTP-рівень, разом з авторизацією.
Нічого з цього не захищає `stdio`. Канал (pipe) не має заголовка `Authorization`, тож `token_verifier` там ніколи
не викликається. Межа безпеки `stdio`-сервера — процес, який його запустив. Те саме
стосується `Client(mcp)` у пам'яті, який використовують у тестах: він під'єднується безпосередньо до об'єкта сервера
й оминає HTTP-рівень разом з авторизацією.
## Ідентичність того, хто викликає {#the-callers-identity}
Усередині будь-якого обробника **`get_access_token()`** — це `AccessToken`, який ваш верифікатор повернув для поточного запиту:
```python title="server.py" hl_lines="4 32-35"
```python title="server.py" hl_lines="4 35-38"
--8<-- "docs_src/authorization/tutorial002.py"
```
* Це працює в інструментах, ресурсах і промптах, і нічого нікуди передавати не треба: middleware авторизації зберігає його в контекстній змінній для кожного запиту.
* Повертається **той самий об'єкт, який побудував ваш верифікатор**: `client_id`, `scopes`, `subject`, `expires_at` і будь-які додаткові `claims`, які ви додали. Це й є зачіпка для правил на рівні окремих інструментів: прочитайте scopes і відмовте.
* Поза автентифікованим HTTP-запитом він повертає `None`. У пам'яті й через `stdio` це завжди `None`.
* Це працює в інструментах, ресурсах і промптах, і нічого не треба передавати вручну: middleware авторизації зберігає його в контекстній змінній для кожного запиту.
* Повертається **той самий об'єкт, що його побудував ваш верифікатор**: `client_id`, `scopes`, `subject`, `expires_at` і будь-які додаткові `claims`, які ви додали. Це і є зачіпка для правил на рівні окремих інструментів: прочитайте scopes і відмовте.
* Поза автентифікованим HTTP-запитом функція повертає `None`. У пам'яті та через `stdio` це завжди `None`.
Викличте `whoami` з `Authorization: Bearer alice-token` — і модель прочитає:
@ -109,22 +113,22 @@ alice (scopes: notes:read)
SDK дає вам половину сервера ресурсів: перевірити, оголосити, відмовити. Він не дає сторінки входу, екрана згоди чи токена.
Щоб побачити всі три сторони в русі, запустіть `examples/servers/simple-auth/` з репозиторію SDK (невеликий сервер авторизації та сервер ресурсів, налаштований точно як на цій сторінці), а потім спрямуйте на нього `examples/clients/simple-auth-client/`, щоб пройти повний шлях виявлення й отримання токена.
Щоб побачити всі три сторони в русі, запустіть `examples/servers/simple-auth/` з репозиторію SDK (невеликий сервер авторизації та сервер ресурсів, налаштований точно як на цій сторінці), а потім спрямуйте на нього `examples/clients/simple-auth-client/` — і пройдете весь шлях від виявлення до токена.
!!! info
Є другий аргумент конструктора, `auth_server_provider=`, який вбудовує повноцінний сервер
авторизації всередину вашого MCP-сервера. Він з'явився ще до розділення AS/RS, навколо якого
побудовано специфікацію авторизації MCP. Новим серверам не слід до нього вдаватися.
Є ще другий аргумент конструктора, `auth_server_provider=`, який вбудовує повноцінний сервер авторизації
всередину вашого MCP-сервера. Він з'явився раніше за розділення AS/RS, навколо якого побудована специфікація
авторизації MCP. У нових серверах його використовувати не слід.
Сервер авторизації також може прийняти підписане твердження корпоративного постачальника ідентичності замість того, щоб користувач проходив екран згоди, і SDK підтримує обидва боки цього обміну. Про цей grant і клієнта, що його пред'являє, — на сторінці **[Твердження ідентичності](../client/identity-assertion.md)**.
Сервер авторизації може також прийняти підписане твердження від корпоративного постачальника ідентичності замість того, щоб користувач проклацував екран згоди, і SDK підтримує обидві сторони цього обміну. Цей грант і клієнт, що його пред'являє, описані на сторінці **[Твердження ідентичності](../client/identity-assertion.md)**.
## Підсумки {#recap}
* Через Streamable HTTP ваш сервер — це **сервер ресурсів** OAuth 2.1: він перевіряє токени й ніколи їх не видає.
* `TokenVerifier` — це вся поверхня інтеграції: один асинхронний метод, на вході токен, на виході `AccessToken | None`.
* `token_verifier=` і `auth=AuthSettings(issuer_url=..., resource_server_url=..., required_scopes=[...])` завжди йдуть разом.
* Через Streamable HTTP ваш сервер — **сервер ресурсів** OAuth 2.1: він перевіряє токени й ніколи їх не видає.
* `TokenVerifier` — уся поверхня інтеграції: один асинхронний метод, токен на вході, `AccessToken | None` на виході.
* `token_verifier=` і `auth=AuthSettings(issuer_url=..., resource_server_url=..., required_scopes=[...])` завжди йдуть у парі.
* SDK публікує [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728) Protected Resource Metadata за адресою `/.well-known/oauth-protected-resource/...` і відповідає на неавтентифіковані запити кодом 401, чий заголовок `WWW-Authenticate` вказує на них. Оце й уся історія виявлення.
* `get_access_token()` у будь-якому обробнику — це той, хто викликає.
* Авторизація — справа HTTP. `stdio` та клієнт у пам'яті ніколи її не бачать.
* Авторизація — справа HTTP. `stdio` і тестовий клієнт у пам'яті ніколи її не бачать.
Клієнтська половина (виявлення вашого сервера авторизації й отримання токена за вас) — на сторінці **[Клієнти OAuth](../client/oauth-clients.md)**. А клієнт, який *стверджує* ідентичність замість того, щоб запитувати її в користувача, — на сторінці **[Твердження ідентичності](../client/identity-assertion.md)**.
Клієнтська половина (виявлення сервера авторизації й отримання токена за вас) — на сторінці **[OAuth-клієнти](../client/oauth-clients.md)**. А клієнт, який *стверджує* ідентичність замість того, щоб просити її в користувача, — на сторінці **[Твердження ідентичності](../client/identity-assertion.md)**.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [28221886b198784f, f88ea1f1614f3a1d, ce926d686730b6d0, 3be24f8ad8bb5ab9, 3fad24032b2224ff, f25a7f860e579ecb, e758745df6fb7b0a]
sections: [28221886b198784f, f88ea1f1614f3a1d, 2e76f5cb9df15042, ce926d686730b6d0, 3be24f8ad8bb5ab9, 3fad24032b2224ff, f25a7f860e579ecb, 697b01d95080880d]
tool: 1
---
# Розгортання та масштабування {#deploy-scale}
@ -47,6 +47,22 @@ translation:
розгорнутий сервер, який відхиляє кожне з'єднання, — це список дозволених Host, доки не доведено протилежне.
**[Усунення несправностей](../troubleshooting.md)** теж починається звідси.
## За проксі, що завершує TLS {#behind-a-tls-terminating-proxy}
Якщо TLS завершується на проксі (ingress, балансувальник навантаження, Caddy, nginx), а uvicorn за ним обслуговує звичайний HTTP, скажіть uvicorn довіряти заголовкам `X-Forwarded-*` від проксі:
```console
uvicorn server:app --proxy-headers --forwarded-allow-ips='<proxy address>'
```
Без цього застосунок вважає, що його обслуговують через `http://`, і будь-яке перенаправлення, яке він видає (зазвичай це `/mcp` → `/mcp/`), веде на `http://…`. Python-клієнт відмовляється переходити з HTTPS-ендпоінта на звичайний HTTP і прямо про це каже:
```text
MCPError: Redirect to http://mcp.example.com/mcp/ not followed: it would downgrade this HTTPS endpoint to plain HTTP.
```
Тимчасовий обхід на боці клієнта — вказати точний URL, який обслуговує сервер (`https://mcp.example.com/mcp/`, зі скісною рискою наприкінці), щоб перенаправлення не було взагалі. Справжнє виправлення — прапорець вище. `FORWARDED_ALLOW_IPS` — той самий параметр у вигляді змінної середовища; `*` довіряє кожному проміжному вузлу, що правильно лише тоді, коли до uvicorn не може дістатися ніхто, крім проксі.
## Робочі процеси, і для кого потрібні липкі сесії {#workers-and-who-has-to-be-sticky}
Щойно ім'я хоста відповідає, поставте за ним більше ніж один робочий процес. Для цього в SDK немає жодного перемикача; Starlette-застосунок масштабують так само, як і будь-який ASGI-застосунок, — передають об'єкт чомусь, що вміє створювати дочірні процеси:
@ -170,6 +186,7 @@ python -c "import secrets; print(secrets.token_hex(32))"
## Підсумки {#recap}
* За замовчуванням застосунок відповідає лише на запити, адресовані localhost. `transport_security=TransportSecuritySettings(allowed_hosts=[...], allowed_origins=[...])` — це ворота виходу в світ: доки його не передати, кожен запит за справжнім іменем хоста — це `421`, а причина є лише в лозі сервера.
* За проксі, що завершує TLS, запускайте uvicorn з `--proxy-headers --forwarded-allow-ips=...`, інакше його перенаправлення ведуть на `http://`, і клієнт їх відхиляє.
* На 2026-07-28 немає сесії і нічого, до чого балансувальник навантаження мав би прив'язуватися. `stateless_http=True` — перемикач лише для старого покоління, бо сучасний запит маршрутизується й отримує відповідь ще до того, як цей прапорець узагалі прочитають.
* Типовий ключ `requestState` — це `os.urandom(32)`, випущений окремо в кожному процесі. Багатораундова повторна спроба, що потрапила до іншого робочого процесу, збоїть з `-32602` *«Invalid or expired requestState»*.
* Виправлення — `RequestStateSecurity(keys=[...])` **і** те саме ім'я сервера на кожному екземплярі. Ім'я — типове твердження про аудиторію токена. Ті самі ключі, те саме ім'я.

View file

@ -1,6 +1,6 @@
---
translation:
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 2fd7cf825e6d2b2c, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
sections: [fea8d769ff9edeba, ce8e2ad42f29ef71, 0d705efb19cf99c2, 9fd2357154a5b7e7, 9adc400e8c88e854, 318893ad8e2e9924, 6b63ab96b34476c0]
tool: 1
---
# Запуск сервера {#running-your-server}
@ -75,6 +75,11 @@ Inspector робить рівно те саме, що й справжній хо
* `max_request_body_size`: найбільший прийнятний розмір тіла запиту в байтах. За замовчуванням 4 МіБ; більші запити
отримують HTTP 413 ще до розбору чи створення сесії. Збільшуйте його лише тоді, коли легітимні MCP-повідомлення
перевищують цей розмір.
* `session_idle_timeout`: скільки секунд сесія старого покоління може простоювати без жодного запиту в роботі,
перш ніж сервер її закриє. За замовчуванням 1800. `None` вимикає це. Див.
[Час життя сесії та обмеження](legacy-clients.md#session-lifetime-and-limits).
* `max_sessions`: скільки сесій старого покоління один процес утримує одночасно. За замовчуванням 10 000. `None`
знімає обмеження. Описано в тому самому розділі.
* `event_store`, `retry_interval`, `transport_security`: відновлюваність і захист від DNS-rebinding. Вони можуть зачекати, доки ви не розгорнете сервер деінде, крім localhost; `transport_security` описано на сторінці **[Розгортання та масштабування](deploy.md)**.
!!! warning

View file

@ -1,6 +1,6 @@
---
translation:
sections: [3d1663c18edc824c, d4fd37009a13f03d, af9f398a5a8b679a, 470c2dd144294d69, 8e45827e6d24e8c8, 91dfd0ce98ebb03c]
sections: [3d1663c18edc824c, 90956965ae6a1ca1, af9f398a5a8b679a, 5ce83b1f9d88da62, 0d9b5d13fffc94e5, 8e45827e6d24e8c8, 91dfd0ce98ebb03c]
tool: 1
---
# Обслуговування клієнтів старого покоління {#serving-legacy-clients}
@ -21,15 +21,25 @@ SDK маршрутизує кожен запит за його заголовк
## Один обробник, обидва покоління {#one-handler-both-eras}
Ось інструмент, якому треба дещо запитати в користувача, і клієнти обох поколінь, що його викликають:
Ось інструмент, якому треба дещо запитати в користувача:
```python title="server.py" hl_lines="24 37-38"
```python title="server.py" hl_lines="21"
--8<-- "docs_src/legacy_clients/tutorial001.py"
```
`reserve` потребує однієї речі, якої модель не надала: скільки примірників. Через `Annotated[..., Resolve(ask_quantity)]` інструмент це й оголошує (докладніше — на сторінці **[Залежності](../handlers/dependencies.md)**). Ніщо в `reserve` не називає версію, не перевіряє можливість і не розгалужується.
Обидва клієнти відкриті **одночасно**, на тому самому об'єкті `mcp`. `mode="legacy"` виконує рукостискання `initialize` — саме те з'єднання, яке відкриває клієнт до 2026 року. Другий клієнт бере значення за замовчуванням і потрапляє на `2026-07-28`.
Запустіть його через HTTP — і ось клієнти обох поколінь, що його викликають:
```console
uv run mcp run server.py --transport streamable-http
```
```python title="client.py" hl_lines="14-15"
--8<-- "docs_src/legacy_clients/tutorial001_client.py"
```
Обидва клієнти відкриті **одночасно**, до того самого сервера, що працює. `mode="legacy"` виконує рукостискання `initialize` — саме те з'єднання, яке відкриває клієнт до 2026 року. Другий клієнт бере значення за замовчуванням і потрапляє на `2026-07-28`. Запустіть `python client.py` з другого термінала:
```text
2025-11-25 {'result': "Reserved 2 of 'Dune'."}
@ -62,6 +72,41 @@ SDK маршрутизує кожен запит за його заголовк
надсилання пропущених SSE-подій клієнту, що перепід'єднується до *тієї самої* сесії), а
не сховище сесій. Він ніколи не робить сесію досяжною з іншого процесу.
## Час життя сесій і обмеження {#session-lifetime-and-limits}
Сесія старого покоління не живе вічно, і один процес не тримає їх необмежену кількість.
Цим керують два налаштування. Обидва — іменовані аргументи `run()`, `streamable_http_app()`
і `Server.streamable_http_app()`. Сучасні з'єднання (`2026-07-28`) і `stateless_http=True`
сесій не мають, тож жодне з налаштувань їх не стосується.
| Налаштування | За замовчуванням | Що робить | Що бачить клієнт | Як вимкнути |
|---|---|---|---|---|
| `session_idle_timeout` | `1800` (30 хв) | Закриває сесію, у якій стільки часу нічого не було в роботі. | `404 Session not found`. Доведеться знову виконати `initialize`. | `None` |
| `max_sessions` | `10_000` | Відмовляється відкривати сесію понад цю кількість. Наявні сесії не зачіпаються, і нічого не витісняється. | `503 Too many open sessions` з кодом JSON-RPC `-32603`. | `None` |
Що вважається «в роботі»:
* Відкритий потік `GET`. Клієнти SDK тримають один відкритим, тож сесія під'єднаного
клієнта ніколи не спливає.
* Запит, на який досі відповідають. Виклик інструмента, що триває довше за тайм-аут, не
переривається, а відлік починається лише після його завершення.
* Більше нічого. Між запитами годинник іде. Будь-який запит у сесії перезапускає його,
зокрема й `ping`. Щойно сесія спливла, ніщо її не оживить.
Клієнт, що завершує свою сесію через `DELETE`, звільняє її негайно. Так само й клієнт, чий
початковий запит відхилено.
```python
mcp.run(transport="streamable-http", session_idle_timeout=None, max_sessions=50_000)
```
Обидві події потрапляють у лог сервера. Спливання — це `Session <id> idle timeout` на рівні
`INFO`. Відмова у відкритті — `Refusing to open a new session: <n> sessions are already open`
на рівні `WARNING`.
Обмеження діють на процес. Із чотирма робочими процесами стеля — учетверо `max_sessions`,
і кожен робочий процес сам завершує свої сесії за тайм-аутом.
## Єдиний перемикач: `stateless_http` {#the-one-knob-stateless_http}
Якщо липкість — ціна, яку ви платити не готові, змінити можна рівно одну річ.
@ -87,8 +132,8 @@ SDK маршрутизує кожен запит за його заголовк
!!! check
Зробіть неправильно. `reserve` — той самий інструмент, що щойно обслужив обох клієнтів.
Розгорніть його зі `stateless_http=True`, під'єднайте тих самих двох клієнтів через HTTP
і викличте його з кожного.
Розгорніть його зі `stateless_http=True`, під'єднайте тих самих двох клієнтів і викличте
його з кожного.
Сучасний клієнт, як і раніше, отримує `Reserved 2 of 'Dune'.` Сучасна гілка не змінилася.