MCP или curl в AGENTS.md: считаем точку окупаемости на своём API

Спор «подключить MCP-сервер или описать пару curl-команд в AGENTS.md» обычно сводят к одному числу: сколько токенов занимают схемы инструментов. Но схемы — только постоянная часть расходов. Каждый вызов приносит ответ API, который попадает в контекст; часть префикса кэшируется, часть нет; а если агент сам считает суммы, даты и пагинацию, ошибка в одном вызове может стоить дороже, чем все сэкономленные токены. Ниже — методика, по которой вы измерите обе интеграции на своём API, вычислите точку окупаемости и поймёте, какие преобразования пора переносить из промпта в код.
Важно: все числа в статье — иллюстрация формул, а не результаты наших замеров. Подставляйте собственные измерения.
1. Модель затрат: что именно сравниваем
Для каждой интеграции разложим стоимость одной рабочей сессии на три слагаемых:
- F — постоянная часть. Для MCP это описания и JSON-схемы всех инструментов, которые клиент кладёт в контекст. Для curl — раздел AGENTS.md с рецептами: адреса, заголовки, примеры, правила разбора ответа. Платится один раз на сессию (или реже, если префикс кэшируется).
- v — переменная часть на вызов. Токены, которые модель генерирует, чтобы сформировать вызов, плюс токены результата, которые возвращаются в контекст. Для curl результат — это то, что напечатала команда: сырой JSON, если вы его не фильтруете. Для MCP — то, что вернул сервер, и его можно заранее сократить.
- E — ожидаемая цена ошибок. Вероятность ошибки на вызов, умноженная на стоимость её исправления: повторные вызовы, лишние раунды рассуждений, а в худшем случае — неверный результат, ушедший пользователю.
Для n вызовов в сессии:
C_mcp(n) = F_mcp + n · (v_mcp + p_mcp · K)
C_curl(n) = F_curl + n · (v_curl + p_curl · K)
Здесь p — доля вызовов с ошибкой, K — средняя стоимость исправления в тех же единицах (токенах
или деньгах). Обычно F_mcp > F_curl: схемы занимают больше места, чем три строки curl. Если при этом
MCP дешевле на вызов, существует точка окупаемости:
n* = (F_mcp − F_curl) / ((v_curl + p_curl·K) − (v_mcp + p_mcp·K))
Если знаменатель отрицателен или равен нулю, MCP не окупается никогда — при текущих схемах и ответах. Если
n* меньше типичного числа вызовов в вашей сессии, MCP выгоднее. Всё остальное — измерение этих пяти величин.
2. Подготовка стенда
Нужно:
- доступ к вашему API в режиме только чтения (тестовый ключ или стенд);
- MCP-сервер, оборачивающий те же эндпоинты, и текущий раздел AGENTS.md с curl-рецептами;
jq, Python 3 и, по возможности, способ точного подсчёта токенов для вашей модели;- фиксированный набор из 10–20 задач, типичных для вашего агента (одинаковый для обеих интеграций).
Ключ держите в переменной окружения и не пишите его в файлы, которые попадут в репозиторий или в логи:
export API_BASE="https://staging.example.internal" # пример адреса, замените своим
read -rs API_TOKEN && export API_TOKEN # ввод без эха в терминал
mkdir -p bench/raw bench/filtered
О точности подсчёта. Байты / 4 — грубая оценка для английского текста и JSON; для кириллицы и плотного JSON она
ошибается заметно. Для решения «на границе» используйте токенизатор вашей модели или эндпоинт подсчёта токенов,
если провайдер его предоставляет (например, count_tokens в Anthropic API). Ниже везде
используется функция tokens() — замените её реализацию на точную.
3. Измеряем постоянные затраты F
3.1. Схемы MCP
Получите ровно то, что клиент отдаст модели, — ответ на tools/list. Для stdio-сервера это можно сделать
без клиента агента, передав три JSON-RPC-сообщения (команду запуска сервера замените своей):
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"bench","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| timeout 10 ./your-mcp-server \
| jq -c 'select(.id==2) | .result.tools' > bench/tools.json
wc -c bench/tools.json
jq 'length' bench/tools.json
Если сервер поддерживает другую версию протокола, он вернёт её в ответе на initialize — поправьте поле.
Альтернатива — MCP Inspector (npx @modelcontextprotocol/inspector), где список инструментов виден в интерфейсе.
Учтите, что клиенты оборачивают схемы в собственный системный текст. Самый честный способ — сравнить счётчик входных
токенов в логах клиента для пустой сессии с подключённым сервером и без него. Разница и есть F_mcp.
3.2. Рецепты в AGENTS.md
Вырежьте раздел с рецептами в отдельный файл и посчитайте его так же:
awk '/^## API/{f=1} /^## /&&!/^## API/{f=0} f' AGENTS.md > bench/recipes.md
wc -c bench/recipes.md
Заголовок раздела ## API — пример; используйте свой. Если рецепты разбросаны по файлу, честнее считать
тем же способом, что и для MCP: разница входных токенов пустой сессии с полным и с урезанным AGENTS.md.
3.3. Поправка на кэширование
И схемы, и AGENTS.md обычно стоят в начале контекста и не меняются между сессиями, поэтому попадают в кэшируемый префикс, если ваш провайдер и клиент поддерживают кэширование промпта. Тогда эффективная постоянная часть:
F_eff = F · (h · r + (1 − h))
где h — доля сессий с попаданием в кэш, r — относительная цена кэшированного токена по
тарифу вашего провайдера. Обе величины берите из логов использования и прайса, а не из предположений. Два практических
следствия: во-первых, если клиент меняет порядок или состав инструментов между сессиями, кэш промахивается и
h падает; во-вторых, кэширование уменьшает именно разницу в F, а значит, сдвигает
n* в пользу MCP.
Результаты вызовов кэшу помогают меньше: они попадают в середину диалога и переиспользуются только как часть префикса последующих ходов той же сессии. Зато они платятся заново на каждом следующем ходе, пока остаются в контексте, — об этом в следующем разделе.
4. Измеряем переменные затраты v
Главный источник разницы — размер результата. Сначала зафиксируйте, что агент видит при curl-рецепте, затем — что возвращает MCP-инструмент на тот же запрос.
# Сырой ответ, как его напечатает curl без фильтра
curl -sS --fail-with-body \
-H "Authorization: Bearer $API_TOKEN" \
"$API_BASE/v1/orders?status=open&limit=50" > bench/raw/orders.json
wc -c bench/raw/orders.json
# Тот же ответ после проекции на нужные поля
jq '[.items[] | {id, status, total, currency, updated_at}]' \
bench/raw/orders.json > bench/filtered/orders.json
wc -c bench/filtered/orders.json
Путь и поля — пример; подставьте эндпоинты из своих задач. Используйте только GET-запросы: бенчмарк не должен ничего менять в системе.
Теперь соберите для каждой задачи из набора:
out— токены, которые модель потратила на формирование вызова (видно в логах как выходные токены хода);res— токены результата;stay— сколько последующих ходов результат оставался в контексте.
Последний пункт часто забывают. Результат в 3000 токенов, который висит в контексте ещё пять ходов, стоит не 3000, а около 18 000 входных токенов (с поправкой на кэш). Поэтому:
v ≈ out + res · (1 + stay · c)
где c — средняя относительная цена повторно прочитанного токена (1 без кэша, меньше единицы — с ним).
Удобно положить замеры в CSV и посчитать всё скриптом:
# bench/calls.csv: variant,task,out,res,stay,error
# variant ∈ {mcp,curl}; error ∈ {0,1}
import csv, statistics as st
from collections import defaultdict
C_REREAD = 1.0 # замените на долю цены кэшированного токена, если кэш работает
K = 4000 # средняя стоимость исправления ошибки в токенах — из ваших логов
F = {"mcp": 0, "curl": 0} # подставьте F_eff из раздела 3
rows = defaultdict(list)
for r in csv.DictReader(open("bench/calls.csv")):
v = int(r["out"]) + int(r["res"]) * (1 + int(r["stay"]) * C_REREAD)
rows[r["variant"]].append((v, int(r["error"])))
per_call = {}
for k, xs in rows.items():
v = st.mean(x[0] for x in xs)
p = st.mean(x[1] for x in xs)
per_call[k] = v + p * K
print(f"{k}: v={v:.0f} p_err={p:.2%} per_call={per_call[k]:.0f}")
d = per_call["curl"] - per_call["mcp"]
if d <= 0:
print("MCP не окупается при текущих замерах")
else:
print(f"n* = {(F['mcp'] - F['curl']) / d:.1f} вызовов на сессию")
Значение K = 4000 в скрипте — заглушка, а не рекомендация. Оцените его по логам: возьмите сессии с ошибкой
и посчитайте токены от ошибочного вызова до момента, когда агент вернулся на верный путь.
5. Измеряем ошибки: где агент считает сам
Ошибки делятся на два класса, и их полезно размечать отдельно.
Ошибки вызова. Неверный флаг curl, забытый заголовок, неправильное экранирование query-параметров, несуществующее поле в аргументах. MCP со схемой снижает их частоту, потому что клиент проверяет аргументы до вызова. Curl-рецепт может быть точным, но модель всё равно собирает строку руками.
Ошибки доменных вычислений. Агент получил верные данные, но:
- сложил суммы в разных валютах или в минорных единицах вперемешку с основными;
- сравнил даты без учёта часового пояса;
- прочитал первую страницу и решил, что это весь список;
- округлил там, где бизнес-правило требует отбрасывать дробную часть;
- пересчитал статус по своим правилам вместо того, что вернул API.
Такие ошибки не зависят от транспорта: агент может ошибиться и с MCP, если инструмент возвращает сырые данные. Разница в том, что в MCP-сервер (или в скрипт, который вызывается из AGENTS.md) естественно положить код, который делает вычисление детерминированно. Поэтому при разметке для каждой ошибки отмечайте, могла ли её предотвратить серверная функция.
Как получить разметку: прогоните набор задач на каждой интеграции хотя бы несколько раз (вывод моделей недетерминирован),
для задач с проверяемым ответом сравните результат с эталоном, посчитанным кодом. Число прогонов и задач определяет,
насколько можно доверять p: при 15 задачах разница в одну ошибку — это почти 7 процентных пунктов.
6. Что выносить из промпта в код
Переносите преобразование в код (в MCP-инструмент или в скрипт-обёртку), если выполняется хотя бы одно условие:
- Есть однозначно правильный ответ. Суммы, конвертации, интервалы дат, агрегаты по списку. Модели тут нечего решать, но есть где ошибиться.
- Данных больше, чем нужно для решения. Если агент читает 50 объектов, чтобы найти три просроченных,
фильтр на стороне кода сокращает
resи одновременно убирает риск пропуска. - Нужна пагинация или повторы. Цикл по курсору и обработку 429/5xx с паузами код делает надёжнее, а агенту достаточно итогового результата.
- Правило уже описано в AGENTS.md абзацем текста. Если рецепт содержит «не забудь разделить на 100» или «учитывай UTC», это кандидат на функцию. Текст правила — постоянная стоимость в каждой сессии и всё равно не гарантирует исполнения.
Оставляйте в промпте то, что требует суждения: выбор, какую сущность запрашивать, интерпретацию неоднозначного запроса пользователя, решение, что делать при конфликте данных.
Обратите внимание: перенос в код не обязательно означает MCP. Скрипт scripts/open_orders.sh, который
вызывает API, фильтрует через jq и печатает компактный результат, тоже снижает v и
p, а его описание в AGENTS.md занимает одну строку. Поэтому в бенчмарк стоит включить третий вариант —
«curl + скрипт» — иначе вы сравниваете MCP с намеренно слабым соперником.
7. Проверка результата
Прежде чем принимать решение, убедитесь, что расчёт не обманывает вас:
- Пересчёт вручную. Возьмите одну сессию из логов и сверьте фактические входные и выходные токены
с тем, что даёт формула
F + n · v. Расхождение больше 10–15% значит, что вы что-то не учли (системный текст клиента, повторные чтения, ретраи). - Чувствительность. Пересчитайте
n*сK, уменьшенным и увеличенным вдвое, и сh = 0(кэш не сработал). Если вывод меняется, решение «на границе» — смотрите на распределение числа вызовов в реальных сессиях, а не на среднее. - Распределение n. Посчитайте по логам, сколько вызовов API бывает в сессии: медиану и 90-й перцентиль.
Если медиана ниже
n*, а хвост выше, возможно, нужен MCP-сервер, подключаемый только для определённых задач. - Повторяемость. Повторите замер ответов API через неделю: размеры ответов меняются вместе с данными.
8. Типовые ошибки методики
- Сравнивать только размеры схем. Это
FбезvиE— половина уравнения в лучшем случае. - Сравнивать MCP с фильтрацией и curl без фильтрации. Разница в
resтогда объясняется проекцией полей, а не протоколом. Сравнивайте одинаковую проекцию либо явно включайте вариант «curl + jq». - Игнорировать «время жизни» результата в контексте. Большой ответ, полученный в начале длинной сессии, может стоить больше всех схем вместе.
- Считать один прогон. Частота ошибок по одному прогону — шум.
- Подключать все инструменты сервера. Если агенту нужны три из двадцати,
F_mcpзавышен; многие клиенты позволяют ограничить набор инструментов. - Гонять бенчмарк на боевом API с правами записи. Только GET, только тестовый ключ, никаких токенов в
bench/.
9. Ограничения
- Модель затрат линейная. На практике рост контекста может менять поведение модели и частоту ошибок в длинных сессиях.
- Цена исправления
Kплохо оценивается для ошибок, которые никто не заметил. Если неверный результат может уйти пользователю, считайте эту стоимость отдельно — она редко выражается в токенах. - Методика не учитывает стоимость сопровождения: MCP-сервер нужно обновлять вместе с API, рецепты в AGENTS.md устаревают тише, но тоже устаревают.
- Результат привязан к модели, клиенту и тарифу. Смена любого из них — повод пересчитать.
- Безопасность и права доступа (кто может вызывать что) — отдельный критерий, который в эту формулу не входит.
Итог
Ответ на вопрос «MCP или curl в AGENTS.md: считаем точку окупаемости на своём API» складывается из пяти измерений:
постоянной части обеих интеграций с поправкой на кэш, размера и времени жизни ответов, частоты ошибок и цены их
исправления. Почти всегда самая большая экономия получается не от выбора транспорта, а от того, что детерминированные
преобразования — фильтры, суммы, даты, пагинация — уходят из промпта в код. Сначала сделайте это, затем пересчитайте
n*.
Больше практических методик — в разделе гайдов, определения терминов — в глоссарии.