Model Context Protocol (MCP): как ИИ-агенты «разговаривают» с внешним миром

Model Context Protocol (MCP): как ИИ-агенты «разговаривают» с внешним миром

Если вы читали или смотрели материалы про MCP, возможно, сталкивались с таким комментарием:

«Спасибо, ещё одна статья, из которой я ничего не понял».

Объяснения вроде «MCP — это как USB Type-C» или «MCP — это Tools, Resources и Prompts» не всегда помогают. Поэтому я решил разобраться в технологии досконально и написать понятную, точную и практичную статью — без магии. В процессе прошёл обучение у Anthropic: курсы бесплатные, на английском, с выдачей сертификатов.

Перед собой я поставил три главных вопроса:

  • Что такое MCP и как он связан с ИИ?
  • Как LLM может вызывать внешние инструменты?
  • Как модель, которая умеет только генерировать текст, может читать файлы, открывать сайты или вызывать API?

Для программистов, уже знакомых с серверными примитивами MCP — tools, resources, prompts — будут интересны клиентские: sampling, roots, elicitation. Они звучат загадочно. Например, через sampling MCP-сервер может «сжигать» ваши LLM-токены для своих задач. А через roots — получать доступ к файлам на вашем компьютере.

Поехали.

🔄 Механика диалога: OpenAI-совместимый API в деталях

Люди общаются с ИИ-моделями (GPT, Qwen, Deepseek и др.) через интерфейс чат-бота. Программы — через API. В индустрии уже утвердился стандарт: OpenAI-совместимый API. Вы получаете ключ и можете обмениваться с моделью запросами и ответами в формате JSON.

Рассмотрим, как это работает, на примере.

🎭 Сценарий: «Погода на Мальдивах»

Системный промпт (role: system):

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

Пользователь спрашивает (role: user):

«Я еду завтра на Мальдивы. Брать ли зонтик?»

Модель может ответить сразу — без вызова инструментов:

«Зачем вам зонтик? На Мальдивах всегда прекрасная погода. Даже если промокнете — настроение это не испортит.»

Но более умный подход — признать, что данных нет, и делегировать задачу. Модель может «ответить вопросом на вопрос»:

«Уважаемая программа, я отвечу пользователю, если вы: вызовете инструмент get_weather (с нужными датами, страной и городом) и вернёте мне результат.»

Вы уже видели три стандартные роли: system, user, assistant. Но есть и четвёртая — tool. Именно через неё MCP интегрируется в диалог.

🔹 Шаг 1: Запрос от пользователя (с системным промптом)

Ваша программа (назовём её Host) отправляет запрос модели. Важно: вместе с вопросом передаётся список доступных инструментов в поле tools (в примере — один: get_weather).

💡 Обратите внимание: tools — это не MCP-запрос. Это просто описание, которое «подсказывает» модели: «Если нужно узнать погоду — вот как попросить».

🔹 Ответ от LLM — «вопросом на вопрос»

Модель понимает: у неё нет актуальных данных. Она не выдумывает, а просит вызвать инструмент.

Ключевые детали ответа:

  • content: null — модель не генерирует текст для пользователя. Либо текст, либо вызов инструмента — не одновременно.
  • finish_reason: "tool_calls" — сигнал вашему коду: «Стоп. Не показывай ответ. Сначала выполни инструмент».
  • arguments — строка с валидным JSON. Модель уже подставила параметры: страну, даты и т.д. Кавычки экранированы — важно для парсинга.

🎭 Это и есть интеллект. Не в выполнении, а в понимании: у модели нет данных, есть инструмент, который может помочь, и она знает, какие параметры ему передать.

LLM не вызывает MCP-сервер напрямую. Она говорит вашему коду: «Я не могу ответить, но знаю, кто может. Сходи, спроси вот так, а результат верни мне — я соберу финальный ответ».

🔹 Шаг 2: Ваш код выполняет MCP-запрос (не модель!)

Теперь вступает ваш код (Host). Он видит finish_reason: "tool_calls" и действует.

⚠️ Важно: MCP-сервер — это просто программа. Это не ИИ. Он получает параметры, делает запрос (например, к погодному API), возвращает строку. Всё.

MCP-клиент (ваш код) общается с MCP-сервером через JSON-RPC 2.0. Передача данных возможна двумя способами:

  1. stdio — стандартные потоки ввода-вывода. Используется при локальной установке.
  2. Streamable HTTP/HTTPS — для общения по сети.

Ответ от MCP-сервера приходит в виде строки. Важно: сервер не знает про роли assistant или user. Он просто отдаёт данные.

💡 Обратите внимание: внутри content[].text — обычная строка. Её вы «упакуете» в роль tool, чтобы вернуть в LLM.

🔹 Шаг 3: Второй запрос к LLM (с результатом инструмента)

Теперь вы отправляете полную историю диалога, включая результат инструмента. Обратите внимание на новую роль — tool — и обязательный tool_call_id.

Зачем это нужно?

  • ✅ Чтобы модель поняла: это не новый вопрос, а ответ на её запрос.
  • ✅ Чтобы связать результат с конкретным вызовом.
  • ✅ Чтобы при параллельных вызовов (нескольких инструментов) модель не запуталась.

🔹 Финальный ответ от LLM

Теперь у модели есть всё: вопрос и актуальные данные. Она генерирует ответ:

Ключевые моменты:

  • finish_reason: "stop" — сигнал: можно показывать ответ пользователю.
  • content — финальный, человекочитаемый текст.
  • tool_calls: [] — больше инструментов не нужно.

📋 Все значения finish_reason в OpenAI-совместимом API

  • stop — модель завершила генерацию.
  • tool_calls — модель запросила вызов инструмента.
  • length — достигнут лимит токенов.
  • content_filter — сработала модерация (запрещённый контент).

📦 Примитивы MCP-сервера (Tools, Resources, Prompts)

Я подробно разобрал Tools. Остальные примитивы — кратко.

Resources — это read-only данные: файлы, конфиги, записи из БД. Доступ через URI (например, file:///docs.md или postgres://db/users?id=1). Их подкидывает в контекст приложение (Host).

Prompts — переиспользуемые шаблоны: «Ревью кода», «Планирование отпуска». Их задаёт пользователь или интерфейс. Не предназначены для автоматизации.

💡 Важно: LLM напрямую работает только с Tools. Если хотите, чтобы модель сама решала, когда читать файл — оберните resources/read в Tool. Тогда она сможет запросить его через tool_calls.

🌑 Тёмная сторона протокола (о чём молчат туториалы)

MCP — двусторонний протокол. Сервер может не только отдавать данные, но и запрашивать ресурсы у клиента.

Что может делать MCP-сервер:

  • sampling/create — просит вашу LLM что-то сгенерировать. Может «съесть» ваши токены. Например, публичный сервер может использовать вашу модель для своих задач — а платить придётся вам.
  • elicitation/request — запрашивает подтверждение или данные у пользователя. Например: «Подтвердите перевод» или «Введите номер карты».
  • logging/message — отправляет логи и прогресс. Полезно для отладки и отображения статуса (например, «50% выполнено»).
  • roots — запрашивает доступ к локальным папкам. При неправильной настройке сервер может прочитать .env-файлы с секретами.

🔌 Итак, что такое MCP

MCP — это формат обмена JSON-сообщениями. Он состоит из двух слоёв:

  1. Транспорт — HTTP/HTTPS (по сети) или STDIO (локально).
  2. Данные — JSON-RPC 2.0. Чёткий формат запросов, ответов и уведомлений.

Какую проблему решает MCP?

Раньше каждый разработчик ИИ-агента писал велосипед: парсил Swagger, хардкодил эндпоинты, вручную валидировал параметры.

MCP заменяет это самоописанием. ИИ-агент (MCP-клиент) просто спрашивает у сервера: {"method": "tools/list"} — и получает список инструментов и их параметров в виде JSON. Человеку и модели — понятно.

Таким образом:

  • 🧠 LLM — мозг. Понимает задачу, решает, когда и какой инструмент вызвать.
  • ✋ Ваш код (Host) — руки. Выполняет запросы к MCP-серверам, возвращает данные модели.
  • ⚙️ MCP-сервер — программа. Это не ИИ. Он ждёт вызова и отдаёт результат: скрипт, API, БД, файл и т.д.

MCP — это стандартизированный способ для вашего кода получать инструменты и данные, чтобы «кормить» ими LLM через OpenAI-совместимый API.

Никакой магии. LLM не вызывает серверы. Серверы не думают. Ваш код — единственный мост.

  • finish_reason: "tool_calls" — сигнал делегации.
  • role: "tool" — канал возврата данных.
  • content: null — правило чистого разделения.
Читать оригинал