# Жизненный цикл stdio: конфигурация клиента и выбор транспорта

Рабочая лаборатория использует локальный дочерний процесс. В этом уроке проверим его конфигурацию, повторим настоящее согласование stdio и разберём три неверные записи подключения. Отдельно классифицируем пример HTTPS-адреса без обращения к нему. Результат — понятная граница между проверенным локальным запуском и будущим удалённым сервером.

## Перед началом

Нужны файлы первого урока, `.venv` с SDK 1.30.0, созданная `configure_client.py` конфигурация `.mcp.json` и общий `lab_session.py` из [контракта](/uroki/chto-takoe-mcp/). Если конфигурация ещё не создана, выполните один раз:

```powershell
.\.venv\Scripts\python.exe configure_client.py
```

Повторный запуск намеренно откажется перезаписывать существующую конфигурацию. Не удаляйте чужую .mcp.json ради упражнения; работайте в новой учебной папке из первого урока.

## 1. Посмотрите на рабочую запись

```powershell
Get-Content -LiteralPath .mcp.json
```

У label-lab должны быть type stdio, абсолютный command вашего `.venv\Scripts\python.exe` и args с label_server.py. В stdio клиент запускает этот процесс и обменивается JSON-RPC через stdin/stdout. Начало обмена — initialize, ответ с согласованной версией, затем notifications/initialized. Только после этого получаем список инструментов.

stdout не является местом для логов: каждый посторонний print может повредить поток сообщений. stderr отделён и пригоден для диагностики. Завершение контекста клиента закрывает транспорт и дочерний процесс; постоянный веб-сервис в этом режиме не требуется.

## 2. Сохраните проверку конфигурации

Создайте `check_transport.py`:

```python
"""Classify a configuration; exercise HTTP syntax without a network call."""
import asyncio
import json
from pathlib import Path
from urllib.parse import urlsplit
from lab_session import lab_session


def classify(configuration: dict) -> str:
    has_command = bool(configuration.get("command"))
    has_url = bool(configuration.get("url"))
    if has_command == has_url:
        raise ValueError("Provide exactly one of command or url")
    if has_command:
        if configuration.get("type", "stdio") != "stdio":
            raise ValueError("A command uses stdio in this exercise")
        return "stdio"
    url = urlsplit(configuration["url"])
    if url.scheme != "https" or not url.hostname or url.username or url.password:
        raise ValueError("Use an HTTPS endpoint without credentials in its URL")
    return "streamable-http"


async def main():
    root = Path(__file__).resolve().parent
    configuration = json.loads((root / ".mcp.json").read_text(encoding="utf-8"))["mcpServers"]["label-lab"]
    assert classify(configuration) == "stdio"
    async with lab_session() as (session, initialized):
        assert initialized.protocolVersion == "2025-11-25"
        assert (await session.list_tools()).tools[0].name == "normalize_label"
    hypothetical_http = {"url": "https://example.invalid/mcp"}
    assert classify(hypothetical_http) == "streamable-http"
    bad_configurations = [{}, {"command": "python", "url": "https://example.invalid/mcp"}, {"url": "http://example.invalid/mcp"}]
    errors = []
    for bad in bad_configurations:
        try:
            classify(bad)
        except ValueError as error:
            errors.append(str(error))
        else:
            raise AssertionError("Expected a configuration error")
    print("PASS: real stdio handshake; HTTP configuration only; three invalid configurations")
    print(json.dumps({"local": {"transport": "stdio", "protocol": initialized.protocolVersion, "messages": ["initialize", "notifications/initialized", "tools/list"]}, "http_exercise": {"transport": classify(hypothetical_http), "endpoint": hypothetical_http["url"], "server_implemented": False, "authorization_implemented": False, "network_called": False}, "configuration_errors": errors}, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    asyncio.run(main())
```

Функция classify — узкая проверка этого упражнения, не полная схема всех конфигураций редактора. Она требует ровно одно из command и url, для команды принимает stdio, для учебного HTTP-адреса требует HTTPS без учётных данных внутри URL. Она не подтверждает существование сервера, поддержку транспорта, безопасность или наличие авторизации.

## 3. Запустите и отделите факты от предположений

```powershell
.\.venv\Scripts\python.exe check_transport.py
```

Ожидаемая первая строка:

```text
PASS: real stdio handshake; HTTP configuration only; three invalid configurations
```

В JSON `local.protocol` должен быть 2025-11-25. В http_exercise три поля — server_implemented, authorization_implemented и network_called — будут false. Адрес example.invalid специально нерабочий: это упражнение на конфигурацию, не endpoint для подключения.

Пустая запись, одновременно command и url, а также учебный http:// адрес дадут ValueError. Если чтение .mcp.json завершилось FileNotFoundError, выполните генератор из первого урока в той же лаборатории. Если появился KeyError, сравните переносимый верхний ключ mcpServers с ключом servers у .vscode/mcp.json: эти файлы имеют разные схемы.

## Что понадобится для настоящего HTTP

Streamable HTTP использует отдельный MCP endpoint и HTTP-запросы. Удалённый сервер требует решения по аутентификации и авторизации, проверке Origin, токенам, разрешениям и жизненному циклу соединений. В протоколе 2025-11-25 сервер обязан проверять Origin при его наличии, а локальный HTTP-сервер следует привязывать к localhost. Наличие HTTPS само по себе не даёт права читать или менять чужие данные.

Включение HTTP одной заменой строки транспорта не реализует OAuth или права на инструменты. Авторизация HTTP описана отдельным разделом спецификации; stdio обычно получает необходимые параметры окружения от host. Наш сервер не работает с секретами, файловыми ресурсами или операциями записи, поэтому этот урок не выдаёт его за готовый удалённый сервис.

## Проверьте себя

В копии теста добавьте URL с username и password: classify должен отклонить его. Добавьте stdio-команду с type http: она тоже должна отклониться. Верните исходные данные и получите PASS. Для будущего HTTP-проекта составьте отдельно список: endpoint, проверка Origin, идентификация клиента, разрешение tool, отсутствие секретов в логах, тест отказа доступа. Эти пункты здесь не реализованы и не принимаются за результат упражнения.

## Версия и источники

Упражнение закреплено за Python 3.11 и `mcp==1.30.0`, SDK v1, протоколом 2025-11-25. SDK v2 требует отдельной миграции. Примеры используют синтетические подписи, без внешнего API и модели. Документация сверена 8 октября 2026 года.

Проверены локальный stdio initialize/tools/list и чистая логика трёх ошибок конфигурации. HTTP endpoint, OAuth, Origin на работающем сервере и UI клиента не запускались.

Источники: [транспорты 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports), [жизненный цикл](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle), [HTTP-авторизация](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization), [форматы VS Code](https://code.visualstudio.com/docs/agents/reference/mcp-configuration).
