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

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

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

Аналогии и пояснения вроде «MCP — это как USB Type-C» или «MCP — это Tools, Resources и Prompts» лично мне не добавляли понимания. Поэтому я решил подробно изучить данную технологию и написать статью, где будет понятно, достоверно и применимо. Без магии. Попутно я прошёл обучение у 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. Не спутайте: название сайта похоже на модель 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) формирует запрос к модели. Обратите внимание: мы передаём не только вопрос пользователя, но и список доступных инструментов в поле 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. ИИ мозг сформулировал готовые параметры и имя MCP-инструмента, в котором нуждается. Двойные кавычки \" уже экранированы. Одинарные кавычки здесь сломают парсинг.

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

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

  • Что есть инструмент 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.

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

  • Перечня команд 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 (с бесплатной сертификацией) и полезные видео:

Комментарии (6)


  1. Dmitry_Barovik Автор
    24.04.2026 16:56

    Спасибо, еще одна статья, из которой я ничего не понял :P


  1. dmbozz
    24.04.2026 16:56

    Всё хорошо объяснили, благодарю. Когда кому-то объясняешь, начинаешь и сам лучше понимать, это да.:)
    Интересен вариант коллекции тулзов::
    Допустим, имею несколько разных tool-ов - один, например, как у Вас - найдет погоду и температуру на Мальдивах. Второй - умеет искать среднюю цену на путевку в заданное время. А третий по команде "отформатируй флешку и запиши туда результаты поиска" умеет вызвать системную команду "format D:" с последующей записью на нее.
    Как LLM поймет, что из них и в каком порядке вызвать в ответ на мой промт "собери на флешку 5 самых дешевых вариантов поездки на Мальдивы, и чтоб без дождей, пожалуйста" ?

    Верно ли понимаю, что description - как раз для этого ? чтобы LLM догадалась куда и на каком этапе бежать. И достаточно ли его, или существуют какие-то более тонкие настройки?


    1. Dmitry_Barovik Автор
      24.04.2026 16:56

      Спасибо на добром слове!

      Да, у tools есть поле description. Да, LLM-ка читает описания и на их основе понимает, что умеет делать инструмент. Ну и в обычным промпте (неважно системном или пользовательском) можно дать пояснения: для таких-то целей и в таких-то ситуациях используй такие-то инструменты.

      Например, вот часть реального системного промпта чата GPT 4.5:
      https://github.com/asgeirtj/system_prompts_leaks/blob/main/OpenAI/GPT-4.5.md

      Там, например видно:

      Tools

      bio

      И описание для чего применять.

      Вообще вот здесь много честно слитых программистами системных промптов всемирно известных инструментов: https://github.com/asgeirtj/system_prompts_leaks


  1. uvelichitel
    24.04.2026 16:56

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

    Кому нибудь модель задавала вопросы? Мне вот нет...
    С какой стати модель доверяет тулзам? Достоверность источника(тулзы) чем нибудь подтверждается? Может хоть сертификаты какие)))


    1. Dmitry_Barovik Автор
      24.04.2026 16:56

      Нуу, физически вы как разработчик даёте либо не даёте доступ к MCP серверу. Доступ дали. MCP-сервер взломали. И вуаля, вместо , например, web -поиска или калькулятора код по просьбе LLM вызовет что-то очеееень неожиданное.


    1. Dmitry_Barovik Автор
      24.04.2026 16:56

      Кстати. Первый инструмент, который прописан в Claude Code так и называется: "Задай пользователю вопросы". А второй в списке инструмент: "Вызов командной строки".

      https://github.com/asgeirtj/system_prompts_leaks/blob/main/Anthropic/claude-code.md

      Tools

      AskUserQuestion

      Use this tool when you need to ask the user questions during execution. This allows you to...:

      Bash

      Executes a given bash command with optional timeout...