docs: refresh translations, and translate pages in parallel (#3458)
This commit is contained in:
parent
08a3bc8eaf
commit
fd66270926
1 changed files with 4821 additions and 2277 deletions
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
translation:
|
||||
sections: [2efaecdef109a5c5, fcacd3e66b8635a4, 25323d737dcf0261, 8a6e351ec756904d, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 323ef84f6b4bebde, 30fd31be74169d9a, 656943c6cb567218, c2dc3b1007d2e987, 7cf5386b997d04e9, 0b59feed8384456e, 0cba47bae78d04eb, e4355f4c7cf4fb2e]
|
||||
sections: [3d58228e81b99543, 170514ce901c4139, 17d61fad0a50d62b, 8a6e351ec756904d, 137454d469c867f5, 6392596bd6df54f0, 41126fa9c4fe432f, 480b6d7897e30ab4, d83bb682e708dde0, ebbed3449c499db4, 525cdf1755e29d4c, 30fd31be74169d9a, d2e88333d4f7841f, c2dc3b1007d2e987, d6eabf60cc366341, f798e815252852c2, 0cba47bae78d04eb, 2c218ba829abf74e]
|
||||
tool: 1
|
||||
---
|
||||
# Устранение неполадок {#troubleshooting}
|
||||
|
|
@ -13,6 +13,12 @@ translation:
|
|||
--8<-- "docs_src/troubleshooting/tutorial001.py"
|
||||
```
|
||||
|
||||
Эти записи обращаются к нему по адресу `http://localhost:8000/mcp`, так что оставьте его запущенным по HTTP:
|
||||
|
||||
```console
|
||||
uv run mcp run server.py --transport streamable-http
|
||||
```
|
||||
|
||||
Ошибки, которые цитирует эта страница, настоящие: собственный набор тестов SDK воспроизводит каждую из них.
|
||||
|
||||
## `ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)` {#exceptiongroup-unhandled-errors-in-a-taskgroup-1-sub-exception}
|
||||
|
|
@ -23,7 +29,7 @@ translation:
|
|||
|
||||
```python
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
async with Client("http://localhost:8000/mcp") as client:
|
||||
await client.read_resource("weather://Atlantis")
|
||||
```
|
||||
|
||||
|
|
@ -49,7 +55,7 @@ async def main() -> None:
|
|||
|
||||
```python
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
async with Client("http://localhost:8000/mcp") as client:
|
||||
try:
|
||||
await client.read_resource("weather://Atlantis")
|
||||
except MCPError as e:
|
||||
|
|
@ -67,7 +73,7 @@ async def main() -> None:
|
|||
|
||||
```python
|
||||
async def main() -> None:
|
||||
client = Client(mcp)
|
||||
client = Client("http://localhost:8000/mcp")
|
||||
tools = await client.list_tools() # RuntimeError
|
||||
```
|
||||
|
||||
|
|
@ -75,7 +81,7 @@ async def main() -> None:
|
|||
|
||||
```python
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
async with Client("http://localhost:8000/mcp") as client:
|
||||
tools = await client.list_tools()
|
||||
```
|
||||
|
||||
|
|
@ -251,7 +257,7 @@ app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app())], lifespan=lif
|
|||
|
||||
## `MCPError: Session not found` {#mcperror-session-not-found}
|
||||
|
||||
Сервер не узнаёт `Mcp-Session-Id`, который отправил клиент, — почти всегда потому, что сервер **перезапустился** (или вас направили на другой экземпляр). Сессии живут в памяти одного этого процесса.
|
||||
Сервер не узнаёт `Mcp-Session-Id`, который отправил клиент. Либо сервер **перезапустился** (или вас направили на другой экземпляр), либо сессия **истекла**, потому что в течение `session_idle_timeout` — по умолчанию 30 минут — не выполнялось ни одного запроса. См. раздел [Время жизни сессии и ограничения](run/legacy-clients.md#session-lifetime-and-limits). Сессии живут в памяти одного этого процесса.
|
||||
|
||||
Искать ошибку в сервере незачем. HTTP-ответ — `404`, тело которого — *настоящий* JSON-RPC, поэтому, в отличие от `421` выше, `Client` на Python показывает его дословно:
|
||||
|
||||
|
|
@ -261,9 +267,9 @@ app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app())], lifespan=lif
|
|||
|
||||
Исправление — переподключиться: выйти из блока `async with Client(...)` и войти в новый, который согласует свежую сессию. Для долгоживущего клиента это означает перехватывать `MCPError` вокруг вызовов и переподключаться по этому сообщению, а не повторять попытки внутри мёртвой сессии.
|
||||
|
||||
Если это происходит *без* перезапуска, значит, у вас больше одного воркера без закрепления сессий за ними: каждый воркер держит собственную таблицу сессий, поэтому запрос, направленный не на тот воркер, оказывается здесь. Эта история и два её решения (маршрутизация с привязкой сессий или `stateless_http=True`) — на страницах **[Развёртывание и масштабирование](run/deploy.md)** и **[Обслуживание клиентов старого поколения](run/legacy-clients.md)**.
|
||||
Если это происходит *без* перезапуска и клиент не молчал так долго, значит, у вас больше одного воркера без закрепления сессий за ними: каждый воркер держит собственную таблицу сессий, поэтому запрос, направленный не на тот воркер, оказывается здесь. Эта история и два её решения (маршрутизация с привязкой сессий или `stateless_http=True`) — на страницах **[Развёртывание и масштабирование](run/deploy.md)** и **[Обслуживание клиентов старого поколения](run/legacy-clients.md)**.
|
||||
|
||||
Для оператора сервера соответствующая строка лога — `Rejected request with unknown or expired session ID: <id>`. Она пишется на уровне `INFO`, поэтому при обычном пороге `WARNING` её не видно. Видеть её пачками сразу после развёртывания — нормально: все подключённые клиенты переподключаются.
|
||||
Для оператора сервера соответствующая строка лога — `Rejected request with unknown or expired session ID: <id>`. Она пишется на уровне `INFO`, поэтому при обычном пороге `WARNING` её не видно. Видеть её пачками сразу после развёртывания — нормально: все подключённые клиенты переподключаются. Если же сессия истекла, этой строке предшествует `Session <id> idle timeout`, тоже на уровне `INFO`.
|
||||
|
||||
## `MCPError: Method not found` {#mcperror-method-not-found}
|
||||
|
||||
|
|
@ -275,7 +281,13 @@ app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app())], lifespan=lif
|
|||
|
||||
Сервер хочет что-то спросить у пользователя, а этот клиент никогда не говорил, что его можно спрашивать.
|
||||
|
||||
Резолвер элицитации (elicitation) отказывает заранее, если подключённый клиент не объявил элицитацию через формы, и `e.error.data` называет ровно то, чего не хватает:
|
||||
Этот Bistro перед бронированием задаёт вопрос через резолвер:
|
||||
|
||||
```python title="server.py" hl_lines="15-17 21"
|
||||
--8<-- "docs_src/troubleshooting/tutorial007.py"
|
||||
```
|
||||
|
||||
Запустите его вместо сервера Weather и вызовите `book_table` из клиента, который не передал `elicitation_callback`. Резолвер отказывает заранее, потому что подключённый клиент так и не объявил элицитацию (elicitation) через формы, а `e.error.data` называет ровно то, чего не хватает:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -289,7 +301,7 @@ app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app())], lifespan=lif
|
|||
|
||||
```python
|
||||
async def main() -> None:
|
||||
async with Client(mcp, elicitation_callback=handle_elicitation) as client:
|
||||
async with Client("http://localhost:8000/mcp", elicitation_callback=handle_elicitation) as client:
|
||||
result = await client.call_tool("book_table", {"date": "Friday"})
|
||||
```
|
||||
|
||||
|
|
@ -314,14 +326,14 @@ async def main() -> None:
|
|||
|
||||
Обработчик попытался обратиться к клиенту посреди запроса на подключении, где у вызова нет канала, способного донести запрос от сервера. В такое положение вызов ставят три конфигурации сервера.
|
||||
|
||||
**Подключение `2026-07-28`: любой транспорт, всегда.** В современном протоколе вообще нет запросов, инициируемых сервером, поэтому сервер отказывает ещё до того, как что-либо отправлено. `ctx.elicit()` внутри инструмента — классический способ с этим столкнуться (в самом первом тесте в памяти, поскольку `Client(server)` согласовывает `2026-07-28`, не спрашивая), и передача `elicitation_callback=` ничего не меняет: никакой запрос до клиента не доходит, так что отвечать ему не на что:
|
||||
**Подключение `2026-07-28`: любой транспорт, всегда.** В современном протоколе вообще нет запросов, инициируемых сервером, поэтому сервер отказывает ещё до того, как что-либо отправлено. `ctx.elicit()` внутри инструмента — классический способ с этим столкнуться, обычно в самом первом **[тесте](get-started/testing.md)** этого инструмента в памяти, ведь `Client(mcp)` согласовывает `2026-07-28`, даже если его об этом не просили. Передача `elicitation_callback=` ничего не меняет: никакой запрос до клиента не доходит, так что отвечать ему не на что:
|
||||
|
||||
```python title="server.py" hl_lines="16"
|
||||
--8<-- "docs_src/troubleshooting/tutorial006.py"
|
||||
```
|
||||
|
||||
```python
|
||||
async def main() -> None:
|
||||
async def test_book_table() -> None:
|
||||
async with Client(mcp) as client:
|
||||
await client.call_tool("book_table", {"date": "Friday"})
|
||||
```
|
||||
|
|
@ -363,7 +375,7 @@ mcp.shared.exceptions.MCPError: Cannot send 'elicitation/create': this transport
|
|||
|
||||
```python
|
||||
async def main() -> None:
|
||||
async with Client(mcp) as client:
|
||||
async with Client("http://localhost:8000/mcp") as client:
|
||||
await client.call_tool("forecast", {"city": "London"}, request_state="round-1-from-worker-a")
|
||||
```
|
||||
|
||||
|
|
@ -416,7 +428,7 @@ mcp = MCPServer("Weather", request_state_security=RequestStateSecurity(keys=[key
|
|||
* `Tool already exists:` в логе сервера — единственный признак того, что два одноимённых инструмента схлопнулись в один.
|
||||
* Один 421, три написания: `Server returned an error response` (`Client` на Python), `421 Misdirected Request` / `Invalid Host header` (всё остальное), `Invalid Host header: <host>` (лог сервера). Исправление: `transport_security=TransportSecuritySettings(allowed_hosts=[...])`.
|
||||
* `Task group is not initialized` -> смонтированное приложение, жизненный цикл хоста которого так и не вошёл в `mcp.session_manager.run()`.
|
||||
* `Session not found` -> сервер перезапустился; переподключитесь.
|
||||
* `Session not found` -> сервер перезапустился или сессия истекла (`session_idle_timeout`); переподключитесь.
|
||||
* `Cannot send 'elicitation/create': ... no back-channel ...` -> `ctx.elicit()` нужен канал от сервера к клиенту: у подключения `2026-07-28` его не бывает никогда, `stateless_http=True` отнимает его у подключений старого поколения, а `json_response=True` отнимает канал, привязанный к запросу. Используйте резолвер (клиенту старого поколения к тому же нужен сервер, который сохраняет канал). Соседнее `Method not found` — это запрос метода, которого нет в ревизии протокола другой стороны.
|
||||
* `Client did not declare the form elicitation capability ...` и `Elicitation not supported` -> у клиента не хватает `elicitation_callback=`.
|
||||
* `Invalid or expired requestState` никогда не говорит по сети, почему. Лог сервера говорит; `unknown key` означает, что `RequestStateSecurity(keys=[...])` нужно сделать общим для всех воркеров.
|
||||
|
|
|
|||
Loading…
Reference in a new issue