IT · Model Context Protocol

Міграція MCP на Streamable HTTP

Від stdio до мережевого transport і зручного логування

Урок 11

План заняття

  1. 01 Повторення Streamable HTTP
  2. 02 Перехід на Streamable HTTP
  3. 03 Логування

Блок 1

Повторення Streamable HTTP

Освіжимо transport перед міграцією qauto

Повторення Streamable HTTP

Навіщо повторюємо

Досі qauto MCP server працював через stdio: Cursor запускав процес і передавав credentials у env.

Перед міграцією освіжимо, що таке Streamable HTTP і чим він відрізняється від локального subprocess.

Повторення Streamable HTTP

Що це і навіщо

Transport layer визначає, як JSON-RPC доходить між Host і Server. Формат повідомлень той самий — змінюється лише доставка.

Streamable HTTP — мережевий transport: сервер як окремий процес з HTTP endpoint. Один процес — багато клієнтів. У stdio кожне зʼєднання — окремий subprocess.

Повторення Streamable HTTP

Обмеження

1. Host сам стартує процес

Cursor/Claude запускають uv run python -m qauto_mcp і тримають stdin/stdout. Немає окремого сервера: закрив IDE — MCP помер. Не підключиш другий клієнт до того самого процесу.

2. Одне з'єднання = один subprocess

Кожен Host піднімає свій процес. Inspector, Cursor і Claude Desktop — три копії сервера, три сесії, три логіни в QAuto.

3. stdout зайнятий протоколом

У stdin/stdout ходить JSON-RPC. Туди не можна писати логи — зламаєш протокол. Діагностика лише в stderr, і то обережно.

4. Секрети тільки через env процесу

Працює, бо Host інжектить QAUTO_EMAIL / QAUTO_PASSWORD при spawn. Для HTTP Host процес не запускає, тому той самий блок env у mcp.json уже нічого не дає.

5. Немає нормального remote

stdio — локальна труба. Не вийде покласти MCP на сервер/Docker і підключитись по URL. Для цього якраз Streamable HTTP.

Повторення Streamable HTTP

Як це працює

Раніше в MCP був окремий transport HTTP+SSE з двома endpoint'ами. Streamable HTTP замінив його: тепер один endpoint, наприклад /mcp, і через нього йде все.

Повторення Streamable HTTP

stdio: command + env

Host сам стартує процес і інжектить credentials у env:

{
  "mcpServers": {
    "qauto-course": {
      "command": "uv",
      "args": ["run", "main.py"],
      "env": {
        "QAUTO_EMAIL": "user@example.com",
        "QAUTO_PASSWORD": "YourPass1"
      }
    }
  }
}

Повторення Streamable HTTP

HTTP: url + headers

Host не запускає subprocess — ті самі ключі йдуть у headers:

{
  "mcpServers": {
    "qauto-course": {
      "url": "http://127.0.0.1:8000/mcp",
      "headers": {
        "QAUTO_EMAIL": "user@example.com",
        "QAUTO_PASSWORD": "YourPass1"
      }
    }
  }
}

Повторення Streamable HTTP

stdio vs Streamable HTTP

  • Desktop / локально без окремого процесу → stdio
  • Окремий процес, Inspector, кілька клієнтів → Streamable HTTP
  • Production remote → Streamable HTTP + auth

Блок 2

Логування

Два рівні: transport (FastMCP) і додаток (tools + httpx)

Логування

Два рівні логування

Після міграції на Streamable HTTP логування — не одне ціле. Це два окремі шари.

Рівень 1 — MCP / transport

Framework: старт HTTP, обраний transport, вхідні запити на /mcp. На stdio stdout зайнятий протоколом. На HTTP задаємо log_level у конструкторі.

mcp = FastMCP(
    ...,
    log_level=os.getenv("MCP_LOG_LEVEL", "INFO"),
)

Рівень 2 — додаток

Ваш код: виклики tools і HTTP до QAuto API. Цього log_level FastMCP не дає — потрібен Python logging.

LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
logger.info("tool → %s", name)

Логування

Що логує log_level

Логує

Старт і зупинку HTTP-сервера, обраний transport (streamable-http), вхідні MCP-запити на endpoint /mcp (JSON-RPC: initialize, tools/call).

Не логує

API-запити qauto через httpx у tools. Якщо tool викликав get_cars, у stderr від log_level буде лише факт tools/call — без запиту на /api/cars.

Логування

Рівні log_level

Рівень — фільтр «від і вище». Локально — DEBUG / INFO, production — WARNING / ERROR.

DEBUG

Максимум деталей: bind, transport, кожен запит на /mcp.

log_level="DEBUG"
# Processing request of type CallToolRequest
# Streamable HTTP session manager started

INFO

Робочий режим: сервер стартував і слухає endpoint.

log_level="INFO"
# Uvicorn running on http://127.0.0.1:8000

WARNING

Лише попередження framework, без шуму INFO.

log_level="WARNING"
# Stateless HTTP is currently experimental

ERROR

Помилки сервера / transport, коли запит не обробився.

log_level="ERROR"
# Error processing request from 127.0.0.1

CRITICAL

Сервер не може продовжувати роботу.

log_level="CRITICAL"
# Failed to bind 127.0.0.1:8000

Логування

Налаштування log_level

Задаємо один раз у конструкторі — поруч із host/port:

mcp = FastMCP(
    name="qauto-course",
    host=MCP_HOST,
    port=MCP_PORT,
    streamable_http_path=MCP_HTTP_PATH,
    log_level=os.getenv("MCP_LOG_LEVEL", "INFO"),
)

Логування

logging (Python)

log_level покриває лише transport. Щоб бачити, що робить qauto server — виклики tools, запити до QAuto API, час відповіді — потрібен окремий шар: logging у Python + hooks httpx + wrapper для tools.

Фінал заняття

Підсумки

На цій лекції ми освіжили Streamable HTTP, перевели qauto MCP server зі stdio на мережевий transport і розібрали, як credentials переїжджають з env у headers клієнтського конфігу.

Також розвели два рівні логування: log_level FastMCP (transport) і logging додатку (tools + httpx до QAuto) з маскуванням секретів.

Ключова ідея: host, port і streamable_http_path — у конструкторі FastMCP; у run() лишається вибір transport. Далі — контейнеризація MCP server.