Граф кода одной командой: ставим graphlens-mcp в проект и перестаём жечь токены на grep

от автора

Это третья часть серии. В части 1 я разобрал движок graphlens — что он делает и как устроен. В части 2 я прогнал бенчмарк на 936 запусков и честно показал, где граф окупается, а где нет. Эта статья — про то, чего не хватало: готовый продукт, который ставится одной командой.

Коротко

В первых двух статьях я сделал две вещи и обе — честно.

Описал движок graphlens: как он берёт исходники на Python, TypeScript, Go, Rust и PHP и нормализует их в единый типизированный граф — узлы-символы, type-aware рёбра (CALLS, HAS_TYPE, INHERITS_FROM), детерминированные SHA-256 ID, межъязыковые границы.

И померил его: 936 прогонов на apache/superset (~400k строк, Python + TypeScript, граница /api/v1/...). Вывод — на задачах анализа влияния структурный граф бьёт grep по стоимости в 10–23 раза, а на точечных запросах разница почти нулевая.

Но была дыра, о которой я в обеих статьях молчал. Движок — это не продукт. Чтобы реально подключить graphlens к агенту, недостаточно pip install и API. Нужно написать кучу обвязки. Эта статья — про то, как я эту обвязку оформил в отдельный продукт graphlens-mcp, который ставится одной командой и сразу начинает работать. Он в alpha, он бесплатный (MIT), и его можно потестить на своём проекте за пять минут.

Откуда вообще растёт боль

Картина, знакомая всем, кто гоняет кодового агента на большом проекте. Сотни тысяч строк, бэкенд на Python, фронт на TypeScript, легаси, в которое страшно лезть. Вы спрашиваете агента «как у нас устроена авторизация» или «что сломается, если я поменяю сигнатуру этого метода» — и он уходит в цикл: grep → открыть → прочитать → перейти по импорту → снова grep. Читает десятки файлов, забивает контекстное окно и жжёт токены.

Это не теория. Anthropic в своём инженерном блоге прямо пишет, что определения инструментов и промежуточные результаты могут «consume 50,000+ tokens before an agent reads a request» — и это до того, как агент вообще начал думать над задачей. Каждый прочитанный файл оседает в контексте и тарифицируется на каждом следующем ходу.

Структурный граф решает ровно это: вместо «прочитай файл и поищи глазами» агент задаёт точный вопрос — «кто вызывает create_order?» — и получает маленький структурированный ответ.

Стена номер один: движок — это ещё не продукт

Когда я выкладывал graphlens, я сознательно сделал ядро узким. Документация это прямо фиксирует: graphlens производит граф-IR и на этом останавливается. Он не хранит состояние и не владеет базой, не следит за файловой системой и не переиндексируется сам, не считает эмбеддинги, не поднимает долгоживущий сервис. Адаптеры — чистые продюсеры данных. Граф — единственный выход.

Для движка это правильное решение: минимальное ядро тривиально тестируется, кэшируется и композируется. Но для практика это стена. Чтобы из движка получить рабочий инструмент для агента, поверх надо написать примерно столько же кода:

  • хранилище графа и его сериализацию;

  • инвалидацию — когда файл изменился, что переиндексировать;

  • watcher за файловой системой;

  • MCP-сервер с набором инструментов, которые агент реально умеет звать;

  • регистрацию сервера в конфиге Claude Code / Cursor / прочих, причём у каждого свой формат;

  • и желательно — навигационный скилл, чтобы агент знал, как этими инструментами пользоваться.

Вот эта прослойка — и есть та работа, которую в реальности приходится делать самому, чтобы «движок» превратился в «инструмент». Я её написал и оформил отдельным проектом. graphlens-mcp — тонкий рантайм поверх движка: graphlens даёт механизмы (парсинг, стабильные ID узлов, резолверы), а graphlens-mcp владеет хранилищем, моделью свежести и agent-facing поверхностью — всем тем, чем graphlens сам быть отказывается.

Тезис, который я хочу донести прямо: «движок предоставляет контракты и API, но на практике продукт так не применяют — чтобы что-то заработало, надо написать много glue-кода самому. Так вот готовый продукт, в alpha, который вы ставите одной командой и начинаете тестировать».

Установка: буквально одна команда плюс init

uv tool install graphlens-mcp        # или: pipx install graphlens-mcpcd your-project && graphlens-mcp init

Что делает init: определяет языки проекта, прогоняет «доктор» по тулчейнам, индексирует код в локальный граф, прописывает MCP-сервер в конфиг ваших агентов (он знает форматы Claude Code, Cursor, Windsurf, VS Code/Copilot, Codex CLI и пишет идемпотентно, не ломая другие ваши серверы) и ставит навигационный скилл. Дальше агент сам запускает сервер из конфига — serve руками вы не зовёте. Перезапускаете агента и спрашиваете что-нибудь вроде «что сломается, если я поменяю сигнатуру create_order?».

Требования: Python ≥ 3.13 (ограничение унаследовано от graphlens). Лицензия MIT. Текущая версия — 0.1.2.

Полезные флаги init: --root <dir>, --agent claude_code --agent cursor (повторяемый), --no-agent, --no-skills, --db <path>, --yes (принять найденных агентов без вопросов).

Инструменты, которые получает агент

Сервер выставляет восемь инструментов. Их немного и они нарезаны под конкретные вопросы, которые агент задаёт о коде:

Инструмент

Назначение

search_symbols

Полнотекстовый поиск по именам символов — начинать отсюда

get_node_info

Сниппет исходника + сигнатура + докстринг + позиция узла

get_file_structure

Карта символов файла (outline)

get_callees

Что вызывает функция (исходящие, до max_depth)

get_callers

Кто вызывает функцию — основной инструмент анализа влияния

get_neighbors

Узлы в пределах N переходов в любом направлении

find_references

Не-вызовы: аннотации типов, присваивания

get_cross_language_calls

Связи через границы сервисов (HTTP/gRPC/очереди)

Важная деталь честности: каждый ответ несёт статус качества графа (ok | degraded, агрегированный по всем возвращённым узлам), так что агент никогда не примет частичный ответ за полный. Списки ограничены MAX_RESULTS (200) и несут флаг truncated — без тихого обрезания.

CLI: пять команд

Команда

Что делает

graphlens-mcp init

Определить языки → доктор тулчейнов → полная индексация → настроить агентов → поставить скилл

graphlens-mcp serve

Запустить MCP-сервер по stdio. Запускается агентом, не вами (--no-watch отключает watcher)

graphlens-mcp status

Языки, статус тулчейнов, размер и свежесть графа

graphlens-mcp reindex

Полная пересборка (например, после установки нового тулчейна)

graphlens-mcp remove

Снять регистрацию у агентов; с --purge-db — удалить локальный граф

Сценарий из реальной работы: анализ влияния

Вот ради чего всё затевалось. Возьмём типичную задачу рефакторинга: «что сломается, если я поменяю сигнатуру create_order?».

Без графа агент грепает имя по всему репозиторию, получает каждое вхождение create_order (включая тёзок в тестах и комментариях), читает каждый файл-совпадение целиком, идёт по импортам, перечитывает уже прочитанное — и на сложной кодовой базе делает 27 обращений к инструментам, прежде чем собрать картину (а иногда и упирается в лимит ходов, так и не дойдя до ответа).

С graphlens-mcp цепочка короткая и точная:

search_symbols("create_order")        # 1. найти узелget_callers(id, max_depth=5)          # 2. кто вызывает — радиус пораженияfind_references(id)                    # 3. где ещё упоминается (аннотации, присваивания)get_cross_language_calls(id)           # 4. бьёт ли фронт в этот эндпоинт через /api/v1/...

Дальше агент зовёт get_node_info только для тех мест, которым реально нужна детализация, — вместо того чтобы читать каждый файл-вызывающий целиком. Вместо «вывалить кодовую базу в контекст» — «навигировать по структуре»: кто вызывает, от чего зависит, что сломается.

Рекомендованный паттерн прямо зашит в навигационный скилл, который ставит init: search_symbolsget_callers/find_references → суммировать, и get_node_info точечно.

Модель свежести: почему граф не протухает на лету

Главное отличие готового продукта от «движка + скрипт» — граф остаётся актуальным сам, пока вы редактируете код.

graphlens-mcp держит свежесть через filesystem watcher (стартует вместе с serve, отключается флагом --no-watch). Когда файл меняется на диске, сервер переиндексирует connected set — изменённый файл плюс файлы, которые его импортируют, и файлы, которые импортирует он сам — одним полным analyze, чтобы межфайловые рёбра пересобрались корректно, а не остались наполовину битыми. Удаление файла вычищает его символы и обновляет импортёров. Никакого поллинга и никакой «скелетной» фазы: каждая (пере)индексация выдаёт полный граф, который резолвер способен дать.

Отдельно решена проблема правок «пока сервер был выключен»: при старте serve делает one-shot reconcile — сканирует проект, индексирует новые файлы, вычищает исчезнувшие, обновляет изменённые, и только потом передаёт управление watcher’у.

Граф лежит в <project>/.graphlens/graph.db (SQLite). Это регенерируемый кэш — его безопасно удалить, reindex пересоберёт. Добавьте .graphlens/ в ignore вашей VCS.

Сколько это экономит токенов

Теперь то, ради чего вы дочитали до сюда — цифры. И я даю их с той же честностью, что и в части 2: это замеры на конкретном кейсе, а не универсальный рейтинг.

В части 2 я мерил graphlens против файлового поиска на apache/superset, меняя у одного и того же агента ровно одну вещь — какой MCP-сервер отдаёт контекст. На сложных задачах (анализ влияния, разрешение неоднозначных имён, полный набор переопределений):

Инструмент

Токены

Вызовы

$/задача

filesystem (grep+read)

12596

27

$0.424

graphlens (граф)

748

1

$0.018

Это сокращение токенов примерно на 94% и стоимости в 10–23 раза, при одном вызове инструмента вместо двадцати семи. На простых точечных запросах («где определён класс») выигрыш скромнее — 690 токенов против 1780, около 61%, и я честно повторю вывод части 2: на точечных справках граф особо не нужен, grep справляется. Граф окупается на структурной работе — рефакторинг, оценка радиуса поражения, трассировка.

Это согласуется с независимыми замерами по соседним инструментам. По актуальному README CodeGraph (перевалидирован на Claude Opus 4.8, 2026-06-02, медиана 4 прогонов на 7 репозиториях) — 47% меньше токенов, 58% меньше вызовов инструментов, 35% дешевле, 46% быстрее (более ранняя сборка README давала 57%/71%). А независимый недельный обзор Ry Walker Research на четырёх репозиториях намерил «70% median tool-call reduction, 59% fewer tokens, and 49% faster responses». Порядок величины везде один: на структурных вопросах граф режет и токены, и round-trips в разы.

Оговорка, без которой цифры нельзя переносить на ваш проект: мой замер — это один репозиторий (apache/superset) и 26 задач; cost_usd — API-эквивалент, а не счёт по подписке; провал засчитан как точность 0. Хотите проверить на своём коде — стенд открыт (github.com/Neko1313/agent-context-bench).

Чем graphlens-mcp сознательно не является

Чтобы не выглядело рекламно — статус честный: early. Ядро навигации работает, остальное в развитии. Известные ограничения зафиксированы прямо в документации:

  • Watcher переиндексирует connected set изменения, а не весь проект. Рефакторинг, который рябью идёт через много слоёв косвенности — или создание файла, который уже импортирует неизменённый файл, — может потребовать полного reindex для точного графа.

  • Межъязыковые рёбра COMMUNICATES_WITH пересобираются на полном reindex и могут эродировать на инкрементальных правках (запрос по границам всё ещё резолвит связи, но для точной кросс-языковой картины зовите reindex).

  • Деградация без тулчейна. Python работает из коробки (ty в комплекте). Для TypeScript нужен Node, для Go/Rust/PHP — их тулчейны; без них язык репортится как degraded (структура распарсена, вызовы/типы не полностью разрешены) — но init это никогда не блокирует, а status показывает реальный статус по каждому языку с подсказкой, что доставить.

И принципиально: graphlens-mcp не считает эмбеддинги и не делает семантический поиск «по смыслу» — граф структурный и type-aware, а не векторный индекс. Если нужно «найди код, концептуально похожий на rate limiting, как бы он ни назывался» — это к векторным инструментам, не сюда. graphlens-mcp отвечает на структурные вопросы: кто вызывает, от чего зависит, что сломается.

Зачем это вам и как начать

Ставьте, если вы регулярно гоняете Claude Code, Cursor или совместимый клиент на большом полиглотном проекте и устали смотреть, как агент жжёт токены на рекурсивный grep по репозиторию. Особенно если часто делаете анализ влияния перед рефакторингом — это ровно тот режим, где граф окупается.

Не заморачивайтесь, если проект маленький (десятки файлов — grep отработает мгновенно) или вам нужен в основном семантический поиск по смыслу.

Порог входа — нулевой: всё локально, ничего не уезжает наружу, MIT, удаляется одной командой (graphlens-mcp remove --purge-db). Поставьте на свой главный проект, убедитесь, что MCP активен в агенте, и сравните число вызовов инструментов на одном и том же архитектурном вопросе — с графом и без.

uv tool install graphlens-mcpcd your-project && graphlens-mcp initgraphlens-mcp status        # проверить языки и свежесть графа

Заключение

Часть 1 описала движок. Часть 2 его померила и честно показала, где он выигрывает, а где проседает. Эта часть закрывает разрыв между «есть API» и «есть продукт».

Движок graphlens сознательно отдаёт контракты и останавливается — это правильно для движка. Но в реальности продукт так не применяют: чтобы что-то заработало в связке с агентом, поверх надо написать хранилище, watcher, MCP-слой и регистрацию у агентов. Я эту обвязку написал и оформил в graphlens-mcp — тонкий рантайм поверх движка, который ставится одной командой, сам держит граф свежим и режет токены на структурных вопросах в разы.

Он в alpha, и я говорю это прямо: ядро навигации работает, межъязыковые рёбра и whole-project re-link ещё допиливаю. Но потестить уже можно за пять минут на своём репозитории — и именно обратная связь с реальных проектов мне сейчас нужнее всего. Issue, замеры на ваших кодовых базах, претензии к гранулярности инструментов — всё в репозиторий. Чем больше независимых прогонов на разных проектах, тем ближе мы к ответу, который можно переносить, а не «работает на superset».

Репозиторий: github.com/Neko1313/graphlens-mcp
Движок: github.com/Neko1313/graphlens
Бенчмарк: github.com/Neko1313/agent-context-bench

ссылка на оригинал статьи https://habr.com/ru/articles/1052776/