Model Context Protocol (MCP): как ИИ-агенты «разговаривают» с внешним миром. ai.. ai. llm.. ai. llm. mcp.. ai. llm. mcp. ml.. ai. llm. mcp. ml. model context protocol.. ai. llm. mcp. ml. model context protocol. python.. ai. llm. mcp. ml. model context protocol. python. ии-агенты.. ai. llm. mcp. ml. model context protocol. python. ии-агенты. Машинное обучение.. ai. llm. mcp. ml. model context protocol. python. ии-агенты. Машинное обучение. Проектирование API.
Взаимодействие 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 (с бесплатной сертификацией) и полезные видео:

Автор: Dmitry_Barovik

Источник