Всем привет! Эта статья — небольшое продолжение и развитие одной из моих прошлыхстатей. В этот раз я хочу подробнее разобраться в MCP: как его можно использовать на практике, какой overhead он добавляет и в каких случаях он действительно оправдан. Для этого мы рассмотрим несколько вариантов реализации MCP‑серверов и сравним их с обычными инструментами без MCP.
Введение и план исследования
В одном из проектов мы столкнулись с тем, что использование MCP давало слишком большую задержку. Тогда у нас не было возможности точно локализовать причину, и мы отказались от этого подхода в пользу обычных функций. Сейчас появилась возможность вернуться к этому вопросу и разобрать его более системно. Ниже приведён план экспериментов, который будет использоваться в статье:
Реализация
Назначение
Монолит без MCP
Прямые вызовы функций в одном процессе
Нижняя граница latency (идеальный baseline)
Монолит с MCP (in-process)
MCP внутри одного процесса (без сети)
Overhead паттерна без сетевых hop
Разделённые MCP (docker net)
3 отдельных сервиса MCP + сеть контейнеров
Влияние сетевых hop и межсервисности
Thin MCP HTTP + JSON
fastmcp 2.0 + BlackSheep + JSON
Оптимизированный Python‑вариант
Thin MCP HTTP + YAML
fastmcp 2.0 + BlackSheep + YAML
Влияние сериализации
Thin MCP IPC (iceoryx2/zeroMQ)
fastmcp 2.0 + IPC / zero-copy
Сравнение network vs shared memory
MCP runtime на C++
Реализация MCP‑runtime на C++
Влияние языка и runtime, стабильность хвостов
Описание сценариев экспериментов
S0: Монолит без MCP
В этом сценарии все инструменты реализованы как обычные функции Python, вызываемые напрямую в процессе LLM‑приложения. Здесь нет MCP‑инфраструктуры, серверов или клиентов. Это baseline для минимальной задержки и нижняя граница возможного overhead.
S1: Монолит с MCP (in‑process)
В этом сценарии отдельный MCP‑сервер создаётся внутри того же процесса, что и основное LLM‑приложение. Клиент MCP взаимодействует с ним внутрипроцессно, без сетевых вызовов. Такой вариант позволяет оценить overhead самого MCP‑паттерна без влияния сети.
S2: Разделённые MCP (docker net)
Три отдельных Docker‑контейнера, каждый со своим MCP‑сервером для части инструментов. LLM‑приложение взаимодействует с ними по внутренней сети контейнеров. Этот сценарий позволяет оценить влияние сетевых hops и распределённой архитектуры.
S3a: Thin MCP HTTP + JSON
Используется FastMCP 2.0 с веб‑сервером BlackSheep и JSON‑сериализацией. Это минималистичный HTTP‑стек для оценки более лёгкого Python‑варианта реализации.
S3b: Thin MCP HTTP + YAML
Аналогично S3a, но вместо JSON используется YAML. Этот сценарий нужен для сравнения влияния формата сериализации на производительность.
S4: Thin MCP IPC (iceoryx2)
FastMCP 2.0 + thin proxy + IPC backend на базе iceoryx2. Сценарий нужен для сравнения сетевого взаимодействия с межпроцессным обменом данными и zero‑copy‑подходом.
S5: MCP runtime на C++
Полноценная реализация MCP runtime на C++ для инструментов. Здесь оценивается влияние языка реализации и runtime на latency.
Инструменты и стек
- Docker– контейнерная платформа для изоляции процессов и управления зависимостями. Позволяет упаковать приложение со всем окружением в переносимый контейнер.
- fastmcp 2.0– легковесный фреймворк для создания MCP‑серверов с асинхронным API. Упрощает интеграцию инструментов и данных в агентские рабочие процессы.
- BlackSheep– быстрый асинхронный веб‑сервер, интегрируемый с fastmcp. Обеспечивает высокую производительность для обработки HTTP-запросов.
- iceoryx2– IPC‑система с нулевым копированием, обеспечивает низкую задержку между процессами. Идеально подходит для систем, критичных ко времени отклика.
- MCP‑runtime на C++-- собственная C++ реализация MCP‑runtime. Один из pet проектов, который я делал.
- Milvus– масштабируемый движок векторного поиска, используемый для хранения embeddings. Позволяет эффективно выполнять семантический поиск по большим наборам данных.
- Langraph-- Библиотека для создания агентских LLM‑приложений с графами состояний и workflow.
- Langchain-- Фреймворк для разработки LLM‑приложений с компонентной архитектурой, интеграцией и инструментами. Основное, что мы возьмем из данного фреймворка -- это OpenAi клиент и инструменты для реализации llm tools.
- LLamaCpp-- библиотека для запуска LLM локально с эффективной загрузкой моделей и низкой задержкой. Использовалась только для запуска rerankera. Тут я использовал: Qwen3-Reranker-0.6B-bf16.
- Ollama-- легковесный сервис для локального запуска и управления LLM-моделями через HTTP API. Обеспечивает удобное взаимодействие с различными моделями. Использовалась для вычисления embeddings. В качестве модели для embeddings я использовал Qwen3-embeddings-8b с размерностью вектора 4096 значений.
S0: Монолит
Данная реализация подразумевает, что мы откажемся от mcp серверов совсем и будем использовать различные функции в качестве инструмента. С помощью неё мы сможем измерить накладные расходы, которые мы вносим, выбирая один из вариантов с MCP-серверами.
Диаграмма с архитектурой агента:
Архитектура Monolit
Документ описывает runtime-архитектуруmodules/monolit.
Контекст системы
Поток выполнения
Тестирование
Теперь проведем эксперименты для этого я взял 13 вопросов, которые затрагивают разные темы и нацелены на использования инструментов. Где-то ответ можно получить за один вызов инструмента, где-то нужно сделать чуть больше вызовов. Дальше мы проанализируем полученные результаты.
Здесь отображен p95 от продолжительности в наносекундах.
p95 -- 95‑й процентиль (значение, ниже которого находятся 95 % измерений). В основном используется для оценки задержки.
Как видно из графика один, то у нас только часть вызовов с временем исполнения < 1s. В основном это связано с использованием reranker'а, который добавляет временами большую задержку. Ниже я привел фото с один из трейсов, который находится в диапазоне с временем выполнения меньше 1 секунды.
И также пример, где задержка больше чем 2 секунды.
Из них видно, что большая часть времени уходит на реранкер и получения эмбедингов для поиска. Сам же поиск в векторной базе достаточно быстрый.
S2: Разделённые MCP (docker net)
Model Context Protocol (MCP) — это открытый протокол, который позволяет безопасно и стандартизированно подключать инструменты, данные и контекст к LLM-агентам. В отличие от монолитной архитектуры, где все инструменты выполняются внутри одного процесса, MCP обеспечивает декомпозицию функциональности на независимые серверы, которые могут запускаться отдельно и взаимодействовать через сетевые интерфейсы.
В сценарии S2 мы исследуем разделённую архитектуру, где каждый MCP-сервер работает в собственном Docker-контейнере, а общение между LLM-приложением и серверами происходит через внутреннюю сеть контейнеров. Такой подход позволяет оценить накладные расходы, связанные с сетевыми hops, сериализацией данных и межпроцессным взаимодействием, что особенно важно для распределённых систем с высокими требованиями к latency.
Для данной реализации я взял полученный проект из нулевого этапа и переделал его. Я вынес реализацию каждого mcp сервера в отдельный пакет, сделал его сборку через dockerfile и запустил все через docker compose. На этих этапах мне сильно помог и ускорил работу gpt 5.3 codex.
Результаты первого пробного запуска
Даже при первом запуске первое, что видно -- это задержка. Ниже я прикрепил фотографию, которая показывает, полное время исполнения tools.
Мы видим здесь, что на исполнениеfaq_knowledge_tool.execушло 493 ms и наchunks_knowledge_tool.execушло 588 ms. Это время полного запроса к mcp, то есть оно включает не только embeddings, reranker, search, а так же все накладные расходы(на mcp сервис и http запросы).
Сами mcp сервера мы тоже логируем, поэтому можем посмотреть на то, сколько же времени ушло на выполнения самих инструментов и сколько на накладные расходы.
Мы видим что наfaq_knowledge_toolушло 408ms, а наchunks_knowledge_toolушло 494ms. Тогда мы получаем, что задержка равна 85 и 94 ms соответственно.
Дальше прогоним эту систему через наш тестовый бенчмарк и посмотрим на задержки. Ниже представлена таблица с результатами тестирования.
graph_avg_ms
graph_p50_ms
graph_p95_ms
mcp_avg_ms
mcp_p50_ms
mcp_p95_ms
overhead_avg_ms
overhead_p50_ms
overhead_p95_ms
expert_tool
chunks_knowledge_tool
faq_knowledge_tool
Как видно из результатов, средняя дополнительная задержка, связанная с использованием MCP-серверов, составляет около169 мс на один вызов инструмента в среднем по двум инструментам. Эта величина включает сетевое взаимодействие между контейнерами, сериализацию и десериализацию данных, а также накладные расходы MCP-runtime.
В исследовании в основном используется показания только по двум инструментам, так как только по ним было набрано достаточное количество прогонов.
По сравнению с монолитным baseline (S0), где вызов инструмента происходит напрямую внутри процесса, такая задержка является заметной. Однако в контексте полного времени выполнения инструмента она составляет лишь часть общей latency. Основное время выполнения по-прежнему уходит на вычислительно более тяжёлые операции, такие как получение эмбеддингов и применение reranker.
Таким образом, распределённая архитектура с MCP-серверами действительно добавляет дополнительную задержку, однако в большинстве практических сценариев она не является доминирующим фактором в общей производительности системы. При этом такой подход даёт преимущества в виде изоляции компонентов, масштабируемости и более гибкой архитектуры системы.
S1: MCP (in-process)
В этом сценарии MCP-инфраструктура сохраняется, однако все MCP-серверы запускаются внутри того же процесса, что и LLM-приложение.
Клиент MCP взаимодействует с инструментами через FastMCP runtime, но без использования сетевого транспорта (HTTP, TCP или IPC). Вызовы инструментов выполняются напрямую через объекты MCP-серверов в памяти процесса.
Такой подход позволяет изолировать накладные расходы самого MCP-паттерна, включая:
- диспетчеризацию вызова инструмента;
- обработку аргументов и схем;
- внутреннюю сериализацию MCP;
- runtime-логику FastMCP.
При этом исключается влияние сетевых hops, контейнерной сети и HTTP-обработки.
Таким образом мы увидим, разницу между S0 и S1, которая покажет накладные расходы самой MCP-абстракции, а разница между S1 и S2 позволит оценитьстоимость сетевого взаимодействия между сервисами.
И приведу сами результаты:
graph_avg_ms
graph_p50_ms
graph_p95_ms
exec_avg_ms
exec_p50_ms
exec_p95_ms
overhead_avg_ms
overhead_p50_ms
overhead_p95_ms
expert_tool
chunks_knowledge_tool
faq_knowledge_tool
Как мы видим из них, в среднем мы тратим около 10-11 ms на накладные расходы, которые иногда достигают около 35 ms. Таким образом, влияние MCP‑абстракции на общую производительность минимально. И сравнивая S2 и S1 мы можем увидеть, что из 169 ms (полученные в s2) мы тратим где-то 10 ms (в среднем) на сам mcp runtime, и 159 ms на сеть.
S3a. Thin MCP HTTP + JSON
В этом сценарии мы проверяем гипотезу: можно ли минимизировать overhead сети и runtime, если отказаться от «тяжелых» стандартных реализаций MCP-серверов в пользу максимально легковесного стека. Для этого я взял проект со стадии S2 и с помощью codex аккуратно перенес его на blacksheep.
Идея подхода
В классическом варианте (S2) каждый сервис несет в себе полный MCP-runtime, что может быть накладно, если инструмент включает сложную бизнес-логику и сам сервер включает в себя большое кол-во инструментов. В реализацииThin MCPмы разделяем ответственность:
- MCP Proxy (Gateway):Минимальный FastMCP-сервер, который только создает интерфейсы инструментов для LLM-приложения. Он не содержит бизнес-логики, а просто транслирует входящие JSON-RPC вызовы в обычные HTTP-запросы.
- Business Logic Service:Асинхронный Python-сервис на базеBlackSheep, который обрабатывает чистый JSON.
Преимущества подхода
- Языковая независимость:Поскольку Proxy общается с логикой по обычному HTTP/JSON, сам исполнитель инструмента (Tool Executor) может быть написан на любом языке (Go, Rust, C++), даже если для этого языка еще нет официального MCP SDK. Что очень удобно, так как официальный sdk есть далеко не для каждого языка.
- Гибкость масштабирования:Мы можем эффективно балансировать нагрузку на уровне HTTP-запросов между множеством инстансов логики.
Далее мы сравним, насколько такой подход выигрывает (или проигрывает) стандартному сетевому взаимодействию из S2.
exec_service
graph_avg_ms
graph_p50_ms
graph_p95_ms
exec_avg_ms
exec_p50_ms
exec_p95_ms
overhead_avg_ms
overhead_p50_ms
overhead_p95_ms
expert_tool
mcp-models
chunks_knowledge_tool
mcp-milvus-storage
faq_knowledge_tool
mcp-milvus-storage
По первой серии замеров сценарий S3a выглядел немного лучше S2: средний overhead по двум основным инструментам составил около 128 мс против 169 мс в сценарии S2. Однако повторные прогоны показали, что это преимущество не является устойчивым: в разных сериях измерений накладные расходы получались сопоставимыми с S2, а иногда и выше. Поэтому данный прогон мы не считаем репрезентативным. Ниже результаты одного из других прогонов.
exec_service
graph_avg_ms
graph_p50_ms
graph_p95_ms
exec_avg_ms
exec_p50_ms
exec_p95_ms
overhead_avg_ms
overhead_p50_ms
overhead_p95_ms
expert_tool
mcp-models
chunks_knowledge_tool
mcp-milvus-storage
faq_knowledge_tool
mcp-milvus-storage
Тогда это означает, что основное преимущество thin-подхода в данной реализации связано не столько с производительностью, сколько с архитектурной гибкостью: упрощением proxy-слоя, языковой независимостью backend-сервисов и удобством масштабирования HTTP-исполнителей.
S3b. Thin MCP HTTP + YAML
В этом сценарии используется та же архитектураThin MCP, что и в S3a, однако меняется формат сериализации междуLLM-приложением и MCP-Proxy. Вместо JSON-ответа proxy теперь возвращает результат в видеYAML-строки.
Важно отметить, что изменение формата касаетсятолько взаимодействия LLM ↔ MCP-Proxy. Внутреннее взаимодействие между proxy и сервисами бизнес-логики по-прежнему выполняется черезHTTP + JSON. Это сделано для того, чтобы изолировать влияние именно формата сериализации на уровне интерфейса LLM и не смешивать его с накладными расходами внутренней инфраструктуры.
Цель этого эксперимента — оценить, влияет ли формат сериализации ответа MCP-Proxy на общую задержку выполнения инструмента. Ниже представлены результаты
exec_service
graph_avg_ms
graph_p50_ms
graph_p95_ms
exec_avg_ms
exec_p50_ms
exec_p95_ms
overhead_avg_ms
overhead_p50_ms
overhead_p95_ms
expert_tool
mcp-models
chunks_knowledge_tool
mcp-milvus-storage
faq_knowledge_tool
mcp-milvus-storage
Как видно из них, при использовании YAML задержка выполнения инструмента увеличилась. Средний дополнительный overhead составляет примерно274 ms на вызов инструмента(рассматривается также вывод по двум инструментам).
Для сравнения, в сценарии S3a (JSON) в первой серии замеров средний overhead составлял около 128 ms, однако повторные прогоны показали заметную вариативность этого значения. Тем не менее даже на этом фоне YAML-сценарий демонстрирует более высокий overhead.
Следует отметить, что инструментexpert_toolв этом сценарии вызывался редко (n=1), поэтому он не использовался как основной источник выводов о влиянии сериализации. Основной анализ проводился по инструментамchunks_knowledge_toolиfaq_knowledge_tool, для которых была собрана серия повторных запусков.
Полученные результаты показывают, что использование YAML в качестве формата обмена между LLM и MCP-Proxy может приводить к дополнительным накладным расходам. Вероятными причинами являются более дорогая сериализация/десериализация.
S4 Thin MCP IPC (iceoryx2/ZeroMQ)
В данном варианте изначально планировалось использовать iceoryx2, но я столкнулся с некоторой проблемой. К сожалению, в используемом варианте интеграции данная библиотека не предоставила удобной асинхронной модели работы. И из-за этого пришлось делать дополнительную обвязку, которая увеличила задержку. Результаты прогона я приведу ниже, в которых и можно увидеть эту задержку.
exec_service
graph_avg_ms
graph_p50_ms
graph_p95_ms
exec_avg_ms
exec_p50_ms
exec_p95_ms
overhead_avg_ms
overhead_p50_ms
overhead_p95_ms
expert_tool
thin-mcp-ipc
chunks_knowledge_tool
thin-mcp-ipc
faq_knowledge_tool
thin-mcp-ipc
В сценарии S4 изначально предполагалось, что использование IPC позволит существенно снизить накладные расходы за счёт отказа от HTTP и использования более эффективного транспорта.
Реализация на iceoryx2 не показала ожидаемого выигрыша, что было связано с ограничениями библиотеки и отсутствием удобной асинхронной модели. В связи с этим был проведён дополнительный эксперимент с использованием ZeroMQ, который поддерживает асинхронное взаимодействие.
ZeroMQ-- это высокопроизводительная библиотека обмена сообщениями, предназначенная для создания масштабируемых распределенных систем. Она поддерживает асинхронный ввод-вывод и предоставляет набор гибких паттернов взаимодействия (таких как REQ/REP), что позволяет минимизировать накладные расходы при передаче данных между компонентами системы.
Архитектура осталась той же:
- внешний FastMCP по HTTP;
- внутренние tool workers без HTTP;
- обмен между proxy и worker’ами через IPC.
Менялся только сам transport: вместо iceoryx2 request/response использовался ZeroMQ.
exec_service
graph_avg_ms
graph_p50_ms
graph_p95_ms
exec_avg_ms
exec_p50_ms
exec_p95_ms
overhead_avg_ms
overhead_p50_ms
overhead_p95_ms
expert_tool
thin-mcp-zmq
chunks_knowledge_tool
thin-mcp-zmq
faq_knowledge_tool
thin-mcp-zmq
Результаты показали, что при использовании ZeroMQ средний overhead для основных инструментов удерживается примерно на уровне ~200 ms на вызов.
На этом фоне S4 (ZeroMQ) показывает более низкий и более предсказуемый уровень задержки по сравнению с воспроизводимыми результатами S3. При этом в S3 наблюдалась значительная вариативность: более низкие значения overhead встречались в отдельных прогонах, но не воспроизводились стабильно.
Таким образом, в рассматриваемой архитектуре переход к IPC в сочетании с асинхронной моделью исполнения оказался более эффективным с практической точки зрения, чем thin MCP over HTTP. Основной эффект при этом связан не только с заменой HTTP на IPC, но и с более удачной моделью взаимодействия между proxy и backend-worker’ами.
S5 C++ Runtime
В данном разделе мы рассмотрим, как переход на C++ реализацию позволяет дополнительно снизить, а возможно и нет, накладные расходы и повысить общую производительность системы по сравнению с Python.
Как я упоминал ранее для этого я буду использоватьсвою библиотеку(http протокол, находится в ветке devel) для mcp серверов. Сначала мы будем рассматривать не thin подход, а обычный. Ниже представлены результаты замеров.
exec_service
graph_avg_ms
graph_p50_ms
graph_p95_ms
exec_avg_ms
exec_p50_ms
exec_p95_ms
overhead_avg_ms
overhead_p50_ms
overhead_p95_ms
expert_tool
cpp-runtime
chunks_knowledge_tool
cpp-runtime
faq_knowledge_tool
cpp-runtime
Результаты показывают, что переход на C++ реализацию действительно позволяет снизить накладные расходы по сравнению с Python-реализациями MCP-серверов.
Для основных инструментов (chunks_knowledge_toolиfaq_knowledge_tool) средний overhead составляет порядка 130–145 ms на вызов, что ниже значений, полученных в сценариях S2 (docker-сервисы) и S3 (thin MCP over HTTP), а также немного лучше результатов S4 (IPC + ZeroMQ).
Однако полученный выигрыш нельзя назвать радикальным. Несмотря на использование более производительного языка и более эффективного runtime, итоговая задержка остаётся на том же порядке величины — сотни миллисекунд.
Это указывает на то, что в рассматриваемой системе вклад языка реализации в общую latency ограничен. Основные накладные расходы по-прежнему связаны не столько с самим runtime, сколько с архитектурой взаимодействия между компонентами, сетевыми или IPC hop’ами и моделью исполнения запросов.
Таким образом, переход на C++ позволяет получить умеренное снижение overhead и более предсказуемое поведение системы, однако не решает проблему latency кардинально.
Иными словами, как и в случае с S4, ключевым фактором производительности остаётся не выбор языка сам по себе, а общая архитектура системы и способ организации взаимодействия между proxy и backend-компонентами.
Итоговые выводы
В рамках данного исследования были рассмотрены несколько вариантов организации взаимодействия между MCP proxy и backend-инструментами: прямой вызов сервисов (S2), thin MCP поверх HTTP (S3), IPC-транспорт (S4) и нативная реализация на C++ (S5).
Результаты показывают, что интуитивные ожидания о влиянии отдельных факторов (таких как HTTP, IPC или язык реализации) не всегда подтверждаются на практике.
Во-первых, использование thin MCP поверх HTTP (S3) привело к значительной вариативности задержек. Несмотря на наличие отдельных прогонов с низким overhead, эти результаты не воспроизводились стабильно, что делает такой подход менее надёжным с практической точки зрения.
Во-вторых, переход к IPC (S4) сам по себе не гарантирует снижения latency. Реализация на iceoryx2 не дала ожидаемого выигрыша, что показало ограниченность подхода "заменить транспорт и получить ускорение". В то же время использование ZeroMQ, благодаря поддержке асинхронной модели взаимодействия, позволило получить более стабильный и предсказуемый overhead.
В-третьих, переход на C++ (S5) позволил дополнительно снизить накладные расходы, однако выигрыш оказался умеренным. Несмотря на более эффективный runtime, итоговая задержка осталась на том же порядке величины — сотни миллисекунд.
Совокупный анализ всех сценариев показывает, что основным фактором, определяющим latency в MCP-системах, является не столько выбор транспорта (HTTP vs IPC) или языка реализации (Python vs C++), сколько архитектура системы и модель исполнения запросов.
Наиболее успешные конфигурации (S4 с ZeroMQ и S5) объединяет не просто использование более "быстрых" технологий, а наличие асинхронной модели взаимодействия и более эффективной организации работы backend-компонентов.
Таким образом, при проектировании MCP-систем оптимизация должна быть направлена в первую очередь на:
- снижение количества hop’ов между компонентами;
- выбор подходящей модели конкурентного выполнения;
- минимизацию блокирующих операций;
- упрощение взаимодействия между proxy и backend.
Оптимизация транспорта и языка реализации может дать дополнительный выигрыш, однако без изменения архитектуры системы этот эффект остаётся ограниченным.
Иными словами, в контексте MCP latency определяется не столько тем, "на чём" реализована система, сколько тем, "как" она устроена.
Что в итоге с thin MCP
Thin MCP подход сам по себе не оказался «серебряной пулей» для снижения latency.
С одной стороны, он действительно упрощает архитектуру:
- убирается тяжёлый MCP runtime из backend-сервисов;
- появляется языковая независимость;
- становится проще масштабировать бизнес-логику.
С другой стороны, с точки зрения производительности он не даёт гарантированного выигрыша.
В сценарии S3 (HTTP) наблюдалась высокая вариативность: в отдельных прогонах задержка была ниже, чем в S2, однако эти результаты не воспроизводились стабильно. В худших случаях overhead оказывался сопоставимым или даже выше.
При этом сочетание thin MCP с более удачной моделью взаимодействия (S4, ZeroMQ) дало гораздо более предсказуемый результат, даже без радикального снижения абсолютной задержки.
Таким образом, thin MCP — это скорее архитектурный инструмент, чем способ оптимизации latency.
Он хорошо подходит для:
- декомпозиции системы;
- упрощения proxy-слоя;
- построения гибкой и масштабируемой архитектуры.
Но для снижения задержек его необходимо использовать в сочетании с продуманной моделью исполнения, а не рассматривать как самостоятельную оптимизацию.