mcp (Python SDK, версию не фиксируем — сверьтесь с документацией), pip, bash, MCP-клиент (IDE или чат с поддержкой MCP), ключ API моделиmeasurements.jsonl с 5 строками замеров (N = 5, 10, 20, 50, 100) и обоснованное число инструментов для вашего сервераТермины: MCP (Model Context Protocol) — открытый протокол, по которому клиент (IDE, чат) подключается к серверу и получает каталог инструментов (tools) — функций, которые модель может вызывать. Обмен идёт сообщениями JSON-RPC 2.0, по stdio (stdin/stdout процесса) или по HTTP. Каталог инструментов целиком уезжает в контекст модели при каждом запросе, поэтому N инструментов — это N описаний в каждом вызове.

pip: python --version.pip install "mcp[cli]" — даёт серверный класс FastMCP и клиент ClientSession.bash (или Git Bash) для цикла замеров, jq для просмотра JSON: jq --version.Чек-лист перед стартом:
python --version показывает 3.11 или новееpython -c "import mcp; print(mcp.__file__)" печатает путь к пакету~/mcp-scaletasks/ — её создадим в шаге 1Пример: cd ~ && mkdir mcp-scale && cd mcp-scale && python -c "import mcp; print(mcp.__file__)" → печатает /…/site-packages/mcp/__init__.py. Если вместо пути ошибка ModuleNotFoundError: No module named 'mcp' — пакет не установился, вернитесь к pip install.

Что сделать: создать сервер mcp_scale_server.py, у которого количество инструментов задаётся переменной окружения TOOLS_COUNT, и пробник probe_measure.py, который подключается к нему по stdio и печатает метрики.
Зачем: чтобы во всех прогонах менялось ровно одно — N. Если менять ещё и набор инструментов, сравнение ничего не покажет.
# mcp_scale_server.py
import os
from mcp.server.fastmcp import FastMCP
N = int(os.environ.get("TOOLS_COUNT", "5"))
mcp = FastMCP("scale-probe")
def make_tool(i: int):
def impl(topic: str) -> str:
return f"tool-{i}: {topic}"
return impl
for i in range(N):
mcp.add_tool(
make_tool(i),
name=f"tool_{i:03d}",
description=f"Вернуть строку tool-{i}. Используй, когда спрашивают про topic-{i}.",
)
if __name__ == "__main__":
mcp.run() # по умолчанию stdio
Имя инструмента передаётся явно (name=), поэтому одинаковые имена внутренних функций impl не конфликтуют — на этом чаще всего спотыкаются.
# probe_measure.py — один прогон: N, размер каталога, задержка
import asyncio, json, os, sys, time
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def run(n: int):
params = StdioServerParameters(
command=sys.executable,
args=["mcp_scale_server.py"],
env={**os.environ, "TOOLS_COUNT": str(n)},
)
async with stdio_client(params) as (r, w):
async with ClientSession(r, w) as s:
await s.initialize()
t0 = time.perf_counter()
res = await s.list_tools()
ms = (time.perf_counter() - t0) * 1000
catalog = json.dumps([t.model_dump() for t in res.tools], ensure_ascii=False)
print(json.dumps({
"n": n,
"tools": len(res.tools),
"catalog_chars": len(catalog),
"catalog_tokens_est": round(len(catalog) / 4),
"list_tools_ms": round(ms, 1),
}))
asyncio.run(run(int(sys.argv[1])))
catalog_tokens_est — грубая оценка «символы / 4»; точное число токенов даёт только токенизатор вашей модели (сверьтесь с документацией).
Как проверить:
python probe_measure.py 5
Пример вывода:
{"n": 5, "tools": 5, "catalog_chars": <ваш-замер>, "catalog_tokens_est": <ваш-замер>, "list_tools_ms": <ваш-замер>}
Главное здесь — "tools": 5 и код возврата 0. Если tools меньше N, сервер поднялся не полностью.
Что сделать: выполнить пробник на пяти значениях N, затем заполнить по 20 задач на клиенте и посчитать долю верных вызовов.
Зачем: узкое место — не «количество инструментов» само по себе, а объём каталога в контексте и точность выбора нужного инструмента. Первое вы измеряете автоматически, второе — прогоном задач.
for n in 5 10 20 50 100; do
python probe_measure.py "$n" >> measurements.jsonl
done
cat measurements.jsonl
Пример: после пяти итераций в файле ровно 5 строк с "n" от 5 до 100. Если строк меньше — соответствующая итерация упала, смотрите её stderr.
Теперь набор задач. Он одинаков для всех N и ссылается только на topic-0 … topic-4, поэтому существует при любом количестве инструментов:
mkdir -p tasks && python - <<'PY'
import json
rows = []
for i in range(5):
for k in range(4):
rows.append({"prompt": f"верни строку для topic-{i} (вариант {k})", "expect": f"tool_{i:03d}"})
open("tasks/tasks.jsonl", "w").write("\n".join(json.dumps(r, ensure_ascii=False) for r in rows))
print("tasks:", len(rows))
PY
Подключите сервер к клиенту. Формат записи одинаков у большинства клиентов, имя файла зависит от клиента — сверьтесь с документацией:
{
"mcpServers": {
"scale-20": {
"command": "python",
"args": ["/home/<user>/mcp-scale/mcp_scale_server.py"],
"env": { "TOOLS_COUNT": "20" }
}
}
}
env здесь обязателен: без него клиент запустит сервер с TOOLS_COUNT по умолчанию, и все прогоны дадут одно и то же число — это самая частая ошибка в таких замерах. Модель для прогона задач берите из обзора нейросетей и моделей 2026 — прогон должен идти на той же модели, с которой сервер будет работать в бою.
Как проверить: на каждой итерации меняйте env.TOOLS_COUNT (5, 10, 20, 50, 100), перезапускайте клиент (перезапуск обязателен — каталог кешируется при старте), прогоняйте 20 задач и записывайте число совпадений expect с фактически вызванным инструментом.
Пример: задача верни строку для topic-3 (вариант 1) при ожидаемом tool_003, а в логе клиента вызов tool_003 — плюс один верный; вызов tool_013 — ошибка выбора.

Что сделать: собрать все замеры в таблицу, добавить колонку точности и выбрать N.
Зачем: решение принимается по двум ограничениям сразу — каталог должен влезать в бюджет контекста, а точность выбора не должна проваливаться.
python - <<'PY'
import json
rows = [json.loads(l) for l in open("measurements.jsonl")]
print(f"{'N':>4} {'tools':>6} {'tok≈':>7} {'ms':>7}")
for r in rows:
print(f"{r['n']:>4} {r['tools']:>6} {r['catalog_tokens_est']:>7} {r['list_tools_ms']:>7}")
PY
Пример вывода:
N tools tok≈ ms
5 5 <замер> <замер>
10 10 <замер> <замер>
20 20 <замер> <замер>
50 50 <замер> <замер>
100 100 <замер> <замер>
Заполните сводную таблицу (числа — свои; в примере только структура):
| N инструментов | tools в `list_tools` | Каталог, токенов ≈ | `list_tools`, мс | Верных вызовов из 20 |
|---|---|---|---|---|
| 5 | 5 | `<замер>` | `<замер>` | `<0–20>` |
| 10 | 10 | `<замер>` | `<замер>` | `<0–20>` |
| 20 | 20 | `<замер>` | `<замер>` | `<0–20>` |
| 50 | 50 | `<замер>` | `<замер>` | `<0–20>` |
| 100 | 100 | `<замер>` | `<замер>` | `<0–20>` |
Правило выбора: берите максимальное N, при котором доля верных вызовов не ниже порога, заданного заранее (например, 0,9), а catalog_tokens_est оставляет запас контекста под сам диалог. Всё, что выше этой точки, выносите в отдельные серверы — клиент подключает несколько серверов одновременно:
{
"mcpServers": {
"scale-core": { "command": "python", "args": ["/home/<user>/mcp-scale/mcp_scale_server.py"], "env": { "TOOLS_COUNT": "20" } },
"scale-extra": { "command": "python", "args": ["/home/<user>/mcp-scale/mcp_scale_server.py"], "env": { "TOOLS_COUNT": "10" } }
}
}
Как проверить: финальный чек-лист.
measurements.jsonl ровно 5 строк, значения n совпадают с 5, 10, 20, 50, 100tools в каждой строке равно nenv.TOOLS_COUNT рабочего конфигаpython probe_measure.py <N*> даёт тот же toolsПроблема. Клиент не видит ни одного инструмента, пробник зависает
Error: Unexpected token 'I', "INFO ..." is not valid JSONprint() или логгер пишет в stdout. По stdio-транспорту stdout занят сообщениями JSON-RPC, любая посторонняя строка ломает поток.print() из сервера:import logging, sys
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
Проверка: python probe_measure.py 5 снова печатает "tools": 5, а лог виден в stderr.
Проблема. При 100 инструментах модель называет инструмент, которого нет
Error: Tool tool_112 not foundtool_000 … tool_099 отличаются одним числом; модель путает соседние имена. Это и есть измеряемая деградация точности, а не сбой протокола.scale-extra). Проверка: curl не нужен, достаточно повторить прогон 20 задач на новом N.Проблема. Запрос падает на этапе генерации, хотя list_tools работает
Error: 400 prompt is too long (текст у разных провайдеров отличается — сверьтесь с документацией)catalog_tokens_est для 100 и для 20 из measurements.jsonl, выставьте TOOLS_COUNT по бюджете модели и перезапустите клиент. Если инструменты нужны все — подключайте их отдельными серверами и держите в основном только часто используемые.Оптимальное число инструментов MCP-сервера определяется двумя замерами: объёмом каталога в токенах и долей верных вызовов на фиксированном наборе задач. Прогоните сетку 5–100 на своих реальных инструментах и зафиксируйте N\*, при котором точность держится выше порога, а каталог оставляет запас контекста.
Сколько инструментов должно быть у MCP-сервера?
Столько, сколько проходит ваш собственный замер: каталог влезает в бюджет контекста, а доля верных вызовов не ниже заданного порога. Универсального числа нет — оно зависит от модели и от того, насколько похожи описания инструментов.
Что такое MCP простыми словами?
Протокол, по которому LLM-клиент получает от сервера список функций (tools) и вызывает их. Клиент и сервер общаются сообщениями JSON-RPC 2.0, чаще всего через stdin/stdout дочернего процесса.
Почему много инструментов замедляет работу?
Каждое описание инструмента уходит в контекст при каждом запросе: растёт число входных токенов, а вместе с ним задержка и риск переполнения контекста. Плюс модели труднее выбрать нужный инструмент среди похожих имён.
Как уменьшить число инструментов без потери функций?
Разнести их по нескольким серверам в одном конфиге клиента, убрать дублирующие по смыслу инструменты и оставить в основном сервере только часто используемые. Если нужен бесплатный вариант клиента для прогонов, см. рабочие способы пользоваться ИИ-моделями бесплатно.