AmigaОбучение ИИ
Модуль 2 · Что такое MCP · урок 3 из 14

Архитектура: хост, клиент, сервер

10 мин чтения▶ есть видеоверсия

В прошлом уроке мы договорились о словах: хост, клиент, сервер. Теперь посмотрим, что между ними на самом деле летает. Это пригодится, когда сервер «не подключается» и нужно понять, где обрыв, — а ещё чтобы не бояться логов инспектора, который мы откроем через несколько уроков.

Ещё раз о трёх ролях

Хост — это приложение: Claude Code, приложение Claude, VS Code. Он создаёт по одному клиенту на каждый подключённый сервер, и каждый клиент держит выделенное соединение со своим сервером. Если в Claude Code подключены серверы трекера, GitLab и файловой системы, внутри работают три клиента, и они ничего не знают друг о друге.

Сервер — программа, которая отвечает на запросы клиента. Где она запущена, неважно: локальный сервер стартует как дочерний процесс хоста прямо на вашем ноутбуке, удалённый живёт где-то в облаке и обслуживает много клиентов сразу. С точки зрения протокола это один и тот же сервер.

Два слоя: данные и транспорт

Протокол делится на два слоя, и это разделение стоит запомнить.

Слой данных описывает, что говорят друг другу клиент и сервер: формат сообщений, набор методов, примитивы (инструменты, ресурсы, промпты), уведомления. Он один для всех.

Транспортный слой описывает, как доставить сообщение: через какой канал, как разделять сообщения, как авторизоваться. Транспортов два:

  • stdio — клиент запускает сервер как дочерний процесс и пишет ему в стандартный ввод, а читает из стандартного вывода. Никакой сети, никаких портов. Так работают почти все локальные серверы.
  • Streamable HTTP — клиент отправляет сообщения HTTP-запросами POST, а сервер может отвечать потоком (Server-Sent Events). Так работают удалённые серверы: авторизация обычными заголовками или OAuth.

Один и тот же сервер можно запустить в любом транспорте, не меняя логику. В нашем учебном проекте будет stdio, а Streamable HTTP подробно разбирает курс «MCP: продвинутые темы».

Из stdio следует правило, которое ломает больше серверов, чем любая другая ошибка: сервер на stdio не должен ничего печатать в стандартный вывод. Вывод — это канал протокола. Один print("отладка") — и клиент получает мусор вместо JSON. Логи пишите в стандартный поток ошибок, через модуль logging.

JSON-RPC: язык слоя данных

Все сообщения — это JSON-RPC 2.0, простой формат вызова удалённых методов. Есть три вида сообщений: запрос (с id, ждёт ответа), ответ (с тем же id) и уведомление (без id, ответа не ждёт).

Вот как клиент спрашивает у сервера список инструментов:

json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {}
}

И что сервер отвечает (сокращённо):

json
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "read_doc",
        "description": "Прочитать документ проекта целиком.",
        "inputSchema": {
          "type": "object",
          "properties": {
            "name": { "type": "string", "description": "Имя файла, например brief.md" }
          },
          "required": ["name"]
        }
      }
    ]
  }
}

Обратите внимание на inputSchema. Это обычная JSON Schema, и именно её хост покажет модели, чтобы та знала, с какими аргументами вызывать инструмент. Когда мы будем писать сервер на Python, эту схему за нас соберёт SDK из аннотаций типов.

Вызов инструмента — метод tools/call:

json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "read_doc",
    "arguments": { "name": "brief.md" }
  }
}

Ответ содержит массив content — список блоков (текст, картинка, встроенный ресурс), которые хост передаст модели как результат.

Названия методов устроены единообразно: tools/list и tools/call, resources/list и resources/read, prompts/list и prompts/get. Увидев в логе resources/read, вы уже знаете, что происходит.

Как клиент узнаёт, что умеет сервер

Прежде чем что-то вызывать, клиенту нужно понять, какие примитивы поддерживает сервер: только инструменты или ещё ресурсы и промпты, умеет ли он присылать уведомления. Это называется обнаружением возможностей (capabilities).

В актуальной версии протокола (2026-07-28) каждый запрос несёт в поле _meta версию протокола, имя клиента и его возможности, а клиент может отправить запрос server/discover, чтобы получить от сервера его имя, поддерживаемые версии и список возможностей. В более ранних версиях это делалось рукопожатием initialize в начале соединения. Python SDK скрывает разницу: и в том, и в другом случае после подключения у клиента есть готовый объект с возможностями сервера.

Для нашего сервера ответ будет выглядеть примерно так:

json
{
  "capabilities": {
    "tools": { "listChanged": true },
    "resources": { "subscribe": true, "listChanged": true },
    "prompts": { "listChanged": true }
  }
}

listChanged: true означает, что сервер умеет сообщать, когда его список инструментов (или ресурсов, промптов) изменился.

Уведомления

Иногда серверу нужно что-то сказать без запроса: «список инструментов изменился», «ресурс обновился», «задача выполнена на сорок процентов». Для этого есть уведомления — сообщения без id. Хост, получив notifications/tools/list_changed, заново запрашивает tools/list и обновляет то, что видит модель.

Уведомления не гарантированы: если соединение переподключилось, часть может потеряться, поэтому хосты всё равно периодически перечитывают списки. В актуальной версии протокола клиент подписывается на нужные типы уведомлений явно, запросом subscriptions/listen. Подробнее — в уроке «Уведомления: логи и прогресс» продвинутого курса.

Собираем картину

Тестировщик пишет в Claude Code: «Прочитай бриф и скажи, какие риски не учтены». Что происходит:

  1. При старте Claude Code запустил наш сервер как дочерний процесс (транспорт stdio) и запросил его возможности и список инструментов.
  2. Модель получила описание read_doc вместе с запросом пользователя и решила вызвать инструмент с аргументом brief.md.
  3. Хост спросил у пользователя подтверждение, затем клиент отправил tools/call через стандартный ввод процесса.
  4. Сервер вернул content с текстом брифа, клиент передал его модели как результат вызова.
  5. Модель написала ответ.

Слой данных здесь — методы и JSON, транспорт — стандартный ввод-вывод процесса. Поменяйте транспорт на HTTP, и шаги останутся теми же.

Попробуйте сами

10–15 мин на рабочем месте
  1. Напишите от руки JSON-RPC-запрос tools/call для инструмента «создать задачу в трекере» с полями «заголовок» и «приоритет». Потом — ответ сервера с одним текстовым блоком. Сверьте с примерами выше.
  2. Возьмите любой MCP-сервер, который уже подключён у вас в Claude Code, и по его настройкам определите транспорт: если в конфигурации есть command, это stdio; если url — Streamable HTTP.
  3. Подумайте, какой транспорт нужен серверу «документы проекта», если документы лежат у каждого на ноутбуке, и какой — если они в общей вики компании.

Коротко

  • Хост создаёт по клиенту на каждый сервер; клиенты друг о друге не знают.
  • Два слоя: слой данных (JSON-RPC, методы, примитивы) и транспорт (stdio или Streamable HTTP).
  • stdio: сервер — дочерний процесс, общение через стандартный ввод-вывод. Ничего не печатать в stdout.
  • Методы называются единообразно: tools/list, tools/call, resources/read, prompts/get.
  • Клиент узнаёт возможности сервера при подключении; уведомления сообщают об изменениях.

Видеоверсия

Сценарий озвучки · 437 слов, ≈ 3 мин

В прошлый раз мы назвали три роли: хост, клиент и сервер. Сегодня посмотрим, что между ними на самом деле летает. Это пригодится, когда сервер «не подключается» и нужно понять, где обрыв.

Напомню роли. Хост — приложение: Claude Code, приложение Claude, редактор. На каждый подключённый сервер хост создаёт отдельного клиента, и клиенты ничего не знают друг о друге. Сервер — программа, которая отвечает на запросы. Где она запущена — на вашем ноутбуке или в облаке — для протокола неважно.

Протокол делится на два слоя. Слой данных описывает, что именно говорят друг другу клиент и сервер: формат сообщений, набор методов, примитивы. Транспортный слой описывает, как доставить сообщение. Транспортов два. Первый — стандартный ввод-вывод: клиент запускает сервер как дочерний процесс и пишет ему в стандартный ввод, а читает из стандартного вывода. Никакой сети. Так работают почти все локальные серверы. Второй — Streamable HTTP: сообщения летят обычными эйч-ти-ти-пи-запросами, а сервер может отвечать потоком. Так работают удалённые серверы.

Из первого транспорта следует правило, которое ломает больше серверов, чем любая другая ошибка. Сервер на стандартном вводе-выводе не должен ничего печатать в стандартный вывод. Вывод — это канал протокола. Одна отладочная печать — и клиент получает мусор вместо джейсона. Логи пишите в поток ошибок.

Теперь слой данных. Все сообщения — это JSON-RPC, простой формат вызова удалённых методов. Запрос с номером, ответ с тем же номером, и уведомление без номера, которое ответа не ждёт. Названия методов устроены единообразно: «tools list» — дай список инструментов, «tools call» — вызови инструмент, «resources read» — прочитай ресурс, «prompts get» — дай промпт. В ответе на список инструментов сервер присылает для каждого имя, описание и схему аргументов. Именно эту схему хост покажет модели, чтобы та знала, как вызывать инструмент.

Прежде чем что-то вызывать, клиент выясняет, что умеет сервер: только инструменты, или ещё ресурсы и промпты, умеет ли присылать уведомления. Это называется обнаружением возможностей. В актуальной версии протокола каждый запрос несёт версию и возможности клиента, а сервер описывает себя в ответ на отдельный запрос. В старых версиях это было рукопожатие в начале соединения. Python SDK разницу прячет.

Уведомления — это способ сервера сказать что-то без запроса: список инструментов изменился, ресурс обновился, задача выполнена наполовину. Хост, получив такое, перечитывает список и обновляет то, что видит модель.

Соберём картину. Тестировщик пишет в Claude Code: «Прочитай бриф и скажи, какие риски не учтены». При старте хост уже запустил наш сервер и узнал, что у него есть инструмент «прочитать документ». Модель решает его вызвать. Хост спрашивает подтверждение у человека, клиент отправляет вызов в стандартный ввод процесса, сервер возвращает текст брифа, модель пишет ответ. Слой данных — методы и джейсон, транспорт — ввод-вывод процесса. Поменяйте транспорт на эйч-ти-ти-пи, и шаги останутся теми же.

В следующем уроке разберём подробнее три примитива: инструменты, ресурсы и промпты, — и кто из участников какой примитив контролирует.

Отметка хранится только в вашем браузере