- BrainTools - https://www.braintools.ru -

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

Взаимодействие MCP и LLM

Взаимодействие MCP и LLM

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

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

Аналогии и пояснения вроде «MCP — это как USB Type-C» или «MCP — это Tools, Resources и Prompts» лично мне не добавляли понимания. Поэтому я решил подробно изучить данную технологию и написать статью, где будет понятно, достоверно и применимо. Без магии. Попутно я прошёл обучение [1] у Anthropic (ссылки дам, сертификаты выдают, курсы бесплатные, правда на английском).

Я ставил себе цель ответить на вопросы:

  • Что такое MCP и как он связан с ИИ?

  • Как чат GPT (большая языковая модель, LLM) может вызвать какой-то инструмент (tool)?

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

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

Итак, поехали.

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

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

💡 Лайфхак: как общаться бесплатно с разными моделями. Такую возможность предоставляет console.groq.com [2]. Не спутайте: название сайта похоже на модель Grok от Илона Маска, но это другой сервис.

Но мы отвлеклись. Диалог с моделью через OpenAI-совместимый API выглядит примерно так:

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

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

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

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

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

Модель могла бы ответить (role: assistant):

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

Здесь не было вызвано никаких инструментов. ИИ решил, что знает ответ независимо от погоды.

Но! Модель могла бы поступить как в еврейском анекдоте — ответить вопросом на вопрос. Примерно так:

«Уважаемая программа, я отвечу пользователю, если вы:

  1. Вызовете ваш инструмент get_weather (где я уже подставила нужные даты, страну и город).

  2. Вернёте мне ответ от get_weather в чат, тогда я соберу финальную рекомендацию.»

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

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

Ваша программа (назовём её Host) формирует запрос к модели. Обратите внимание [3]: мы передаём не только вопрос пользователя, но и список доступных инструментов в поле tools(в данном случает единственный инструмент get_weather).

POST /v1/chat/completions
{
  "model": "google/gemma-4-26b-a4b-it",
  "messages": [
    {
      "role": "system",
      "content": "Ты подсказываешь людям как им одеваться в путешествие в зависимости от погоды. В твоем распоряжении есть инструмент get_weather с параметрами (дата начала путешествия, дата конца, страна, населенный пункт)."
    },
    
    {
      "role": "user",
      "content": "Я еду завтра (1 мая 2026 года) на Мальдивы на недельку. Брать ли зонтик :-)"
    }
  ],
  
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Получает прогноз погоды для указанного города. Возвращает температуру, вероятность осадков, давление и силу ветра.",
        "parameters": {
          "type": "object",
          "properties": {
            "country":    {"type": "string",                   "description": "Country name, e.g. Maldives"},
            "city":       {"type": "string",                   "description": "City name, e.g. Malé"},
            "start_date": {"type": "string", "format": "date", "description": "Start date in YYYY-MM-DD"},
            "end_date":   {"type": "string", "format": "date", "description": "End date in YYYY-MM-DD"}
          },
          "required": ["country", "city", "start_date", "end_date"]
        }
      }
    }
  ]
}

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

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

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

... начало пропущено ...
{
  "message": {
    "role": "assistant",
    "content": null,
    "tool_calls": [
      {
        "id": "chatcmpl-tool-abac20964c6ac906",
        "type": "function",
        "function": {
          "name": "get_weather",
          "arguments": "{"city": "Malé", "country": "Maldives", "end_date": "2026-05-08", "start_date": "2026-05-01"}"
        }
      }
    ]
  },
  "finish_reason": "tool_calls"
}
... конец пропущен ...
Полный текст JSON здесь:
{
  "id": "chatcmpl-9e9710ae64b15c96",
  "object": "chat.completion",
  "created": 1777024800,
  "model": "google/gemma-4-26b-a4b-it",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "chatcmpl-tool-abac20964c6ac906",
            "type": "function",
            "function": {
              "name": "get_weather",
              "arguments": "{"city": "Malé", "country": "Maldives", "end_date": "2026-05-08", "start_date": "2026-05-01"}"
            }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ],
  "usage": {
    "prompt_tokens": 275,
    "completion_tokens": 57,
    "total_tokens": 332
  }
}

Обратите внимание:

  • ✅поле content: null, т.е. модель не генерирует текст для отображения пользователю, а запрашивает инструмент. Или текст в поле content, или вызов инструментов в массиве tool_calls. Либо-либо, но не вместе.

  • finish_reason: "tool_calls" — это сигнал вашему коду: «Стоп. Не показывай ответ пользователю. Сначала выполни инструмент». (Все коды возврата, кроме finish_reasonразберем ниже).

  • arguments — это строка, содержащая валидный JSON. ИИ мозг [4] сформулировал готовые параметры и имя MCP-инструмента, в котором нуждается. Двойные кавычки " уже экранированы. Одинарные кавычки здесь сломают парсинг.

🎭 Это и есть интеллект [5]. Не в выполнении запроса. А в понимании:

  • Что у модели нет данных «в голове»

  • Что есть инструмент get_weather, который может помочь

  • Какие параметры нужны (country, city, start_date)

  • Как извлечь их из вопроса пользователя («завтра» → 2026-05-01, «Мальдивы» → country: "Maldives")

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

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

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

# 1. Парсим вызов инструмента
tool_call = response.choices[0].message.tool_calls[0]
tool_name = tool_call.function.name  # "get_weather"
args = json.loads(tool_call.function.arguments)  # {"country": "Maldives", ...}

# 2. Вызываем MCP-сервер (это НЕ запрос к LLM!)
result = await mcp_client.call_tool(name=tool_name, arguments=args)

# 3. Получаем : 
# result = "Malé, Maldives: 31°C, 20% chance of rain"

⚠️ Важно: MCP-сервер в простейшем случае — это просто программа. Сервер не «думает», это не ИИ. Программа получила параметры, сделала запрос к погодному API (или кэшу), вернула строку с ответом. Всё.

MCP-клиент (ваша программа) общается с MCP-сервером посредством обмена JSON сообщениями по стандарту JSON-RPC 2.0.

Транспорта для доставки сообщений два:

  1. stdio — стандартные потоки ввода-вывода (stdin, stdout, stderr). Используется, когда MCP-сервер и MCP-клиент установлены на одном компьютере.

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

Ответ от MCP-сервера приходит примерно в таком виде:

{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Malé, Maldives: 31°C, 20% chance of rain, wind 12 km/h"
      }
    ],
    "isError": false
  }
}

💡 Обратите внимание: внутри content[].text лежит обычная строка. Именно её вы потом «упакуете» в role: "tool" для отправки обратно в LLM. MCP-сервер не знает про assistant, user или finish_reason. Он просто отдаёт данные в своём формате.

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

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

... Полный повтор беседы ...
{
      "role": "tool", // <- Четвертая роль стандарта OpenAI API
      "tool_call_id": "chatcmpl-tool-abac20964c6ac906", // <- Связка с вопросом выше
      "content": "Malé, Maldives: 31°C, 20% chance of rain, wind 12 km/h"
}
... Описание инструментов, которые модель имеет права использовать ...
Полный JSON без сокращений здесь:
POST /v1/chat/completions
{
  "model": "google/gemma-4-26b-a4b-it",
  "messages": [
    {
      "role": "system",
      "content": "Ты создан для помощи людям в том, как им одеваться в путешествия в зависимости от погоды. В твоем распоряжении есть инструмент get_weather с параметрами (дата начала путешествия, дата конца, страна, населенный пункт)."
    },
    
    {
      "role": "user",
      "content": "Я еду завтра на Мальдивы. Брать ли зонтик?"
    },
    
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [
        {
          "id": "chatcmpl-tool-abac20964c6ac906",
          "type": "function",
          "function": {
            "name": "get_weather",
            "arguments": "{"city": "Malé", "country": "Maldives", "start_date": "2026-05-01", "end_date": "2026-05-08"}"
          }
        }
      ]
    },
    
    {
      "role": "tool", // <- Четвертая роль стандарта OpenAI API
      "tool_call_id": "chatcmpl-tool-abac20964c6ac906", // <- Связка с вопросом выше
      "content": "Malé, Maldives: 31°C, 20% chance of rain, wind 12 km/h"
    }
  ],
  
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Получает прогноз погоды для указанного города. Возвращает температуру, вероятность осадков, давление и силу ветра.",
        "parameters": {
          "type": "object",
          "properties": {
            "country": {"type": "string", "description": "Country name, e.g. Maldives"},
            "city": {"type": "string", "description": "City name, e.g. Malé"},
            "start_date": {"type": "string", "format": "date", "description": "Start date in YYYY-MM-DD"},
            "end_date": {"type": "string", "format": "date", "description": "End date in YYYY-MM-DD"}
          },
          "required": ["country", "city", "start_date", "end_date"]
        }
      }
    }
  ]
}

Почему передаем в role: "tool"и зачем tool_call_id ?

  • ✅ Чтобы модель понимала: это не новый вопрос от человека, а ответ на её же запрос

  • ✅ Чтобы связать результат с конкретным вызовом через tool_call_id

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

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

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

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "На Мальдивах завтра 31°C, вероятность дождя 20%. Зонтик можно не брать, но лёгкий дождевик не помешает. Хорошего отдыха! ☀️",
      "tool_calls": null
    },
    "finish_reason": "stop"
  }]
}
Полный JSON итогового ответа от LLM здесь:
{
  "id": "chatcmpl-906e5202d20eeb3d",
  "object": "chat.completion",
  "created": 1777026960,
  "model": "google/gemma-4-26b-a4b-it",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "На Мальдивах завтра 31°C, вероятность дождя 20%. Зонтик можно не брать, но лёгкий дождевик не помешает. Хорошего отдыха! ☀️",
        "tool_calls": []
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 350,
    "completion_tokens": 94,
    "total_tokens": 444
  }
}

Пояснения:

  • finish_reason: "stop" это сигнал: модель закончила, можно показывать ответ пользователю.

  • content содержит финальный, человеческий текст.

  • tool_calls: [] или null — больше инструментов не нужно.

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

    Значение

    Когда появляется

    stop

    Модель “штатно” сгенерировала завершающий токен. Можно “курсор” для ввода следующего ответа показывать пользователю

    tool_calls

    Модель сгенерировала требования, чтобы ей вызвали один или несколько инструментов и написали ответы

    length

    Достигнут лимит max_tokens или общий лимит контекста

    content_filter

    Сработала система модерации (запрещённый контент, токсичность и т.п.)

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

Я подробно описал как работает Tools. Статья уже получается длинной, поэтому только упомяну два других примитива.

Примитив

Аналогия в программировании

Кто владелец

Зачем нужен

Tools

Процедуры/Функции (Write + Read)

Модель решает, когда вызвать

Действия: запрос к БД, отправка email, расчёт. Могут менять состояние.

Resources

Read-only данные

Приложение (Host) подкидывает в контекст

Файлы, конфиги, записи БД. Доступ по URI: file:///docs.md, postgres://db/users?id=1

Prompts

Переиспользуемые шаблоны

Пользователь / UI

Готовые сценарии для человека: «Ревью кода», «Планирование отпуска». Не для автоматизации.

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

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

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

В таблице перечислены возможности, которые предоставляет MCP-клиент:

Команда

Что делает

Почему стоит напрячься

sampling/create

Сервер просит вашу LLM что-то сгенерировать

Может «съесть» ваши токены. Публичный сервер погоды может использовать вашу модель для саммаризации логов, а платить будете вы.

elicitation/request

Сервер запрашивает подтверждение или данные у пользователя

«Подтвердите перевод на счёт X» или «Введите номер карты».

logging/message

Сервер шлёт логи и прогресс выполнения

Похоже на режим thinking у LLM. Полезно для отладки и отображения пользователю прогресса выполнения (например, в процентах)

roots

Вы даёте серверу доступ к локальным папкам

file:///Users/Projects/.env? Да, при неправильной настройке MCP-сервер прочитает ваши секреты.

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

MCP — это описание формата, протокол для обмена JSONами. MCP состоит всего из двух слоёв:

  1. ТранспортHTTP/HTTPS (по сети) или STDIO (локально, как subprocess).

  2. ДанныеJSON-RPC 2.0. Строгая договорённость о том, как упаковывать запросы, ответы и уведомления. В статье я практически это не затронул.

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

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

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

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

  • 🧠 LLM — это мозг. Понимает задачу, принимает решение, когда и какой инструмент вызвать и с какими параметрами.

  • Ваш код (Host) — это руки. Выполняет реальные запросы к MCP-серверам, получает данные, возвращает их модели.

  • ⚙️ MCP-сервер — это программа. Скрипт, API, БД, файловая система и т.д. Он не AI. Он просто ждёт вызова и отдаёт результат.

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

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

  • finish_reason: "tool_calls" — сигнал делегации.

  • role: "tool" — канал возврата данных.

  • content: null — правило чистого разделения.

Продолжение следует …

Спасибо, что дочитали. Уверен вы смогли прояснить для себя что же такое MCP.

Если по комментариям будет виден интерес [6] в подробном пояснении:

  • Перечня команд MCP (tools/list, resource/get, … )

  • Форматов MCP-сообщений (jsonrpc, params, result)

  • Других примитивов, кроме tools (resources, prompts, sampling)

  • Как написать свой MCP-сервер или MCP-клиент на Python.

  • Как отлаживать с помощью mcp inspector (почти аналог Postman и Swagger, но для MCP)

, то я с удовольствием напишу следующую часть статьи по вашим интересам.

Подписывайтесь на телеграмм @APIMAGIC — буду публиковать обновления и отвечать на вопросы.

P.S. Обещанные ссылки на курс MCP от Anthropic (с бесплатной сертификацией) и полезные видео:

Автор: Dmitry_Barovik

Источник [10]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/29344

URLs in this post:

[1] обучение: http://www.braintools.ru/article/5125

[2] console.groq.com: http://console.groq.com

[3] внимание: http://www.braintools.ru/article/7595

[4] мозг: http://www.braintools.ru/parts-of-the-brain

[5] интеллект: http://www.braintools.ru/article/7605

[6] интерес: http://www.braintools.ru/article/4220

[7] Introduction to Model Context Protocol: https://anthropic.skilljar.com/introduction-to-model-context-protocol

[8] Model Context Protocol: Advanced Topics: https://anthropic.skilljar.com/model-context-protocol-advanced-topics

[9] Video: MCP Deep Dive (YouTube): https://www.youtube.com/watch?v=7j1t3UZA1TY

[10] Источник: https://habr.com/ru/articles/1027508/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1027508

www.BrainTools.ru

Rambler's Top100