Если вы читали или смотрели материалы про 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. Передача данных возможна двумя способами:
- stdio — стандартные потоки ввода-вывода. Используется при локальной установке.
- 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-сообщениями. Он состоит из двух слоёв:
- Транспорт — HTTP/HTTPS (по сети) или STDIO (локально).
- Данные — 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 — правило чистого разделения.