Привет, Хабр!

Мы живем в удивительное время. Попросить LLM написать для нас код стало так же естественно, как гуглить ошибку. Но у этой магии есть предел. Попросите модель написать quickSort, и она справится блестяще. А теперь попросите ее: «Добавь метрики Prometheus в метод processOrder в нашем проекте».

И тут магия рушится. LLM — это гениальный, но страдающий амнезией стажер. Она знает все языки мира, но не имеет ни малейшего понятия о вашем проекте. Она не знает, какой у вас логгер, как вы обрабатываете ошибки и что у вас уже есть готовый MetricsService. В лучшем случае вы получите общий, неидиоматичный код. В худшем — сломаете половину логики.

Стандартный RAG (Retrieval-Augmented Generation) — это как дать стажеру доступ к одному файлу. Полезно, но картину целиком он все равно не увидит. А что, если мы могли бы дать ему не просто файл, а полный доступ к знаниям тимлида-архитектора? Что, если бы LLM могла видеть не просто строки кода, а всю паутину связей, зависимостей и паттернов вашего проекта?

Сегодня я расскажу о проекте code-graph-rag-mcp — это не просто очередной RAG-пайплайн. Это полноценный MCP-сервер, который строит граф знаний вашего кода и дает LLM «архитектурное зрение», превращая ее из простого кодера в настоящего цифрового ассистента.

Архитектурные решения: Почему именно так?

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

1. Почему не REST API, а MCP (Model Context Protocol)?

Можно было бы поднять обычный Express/Fastify сервер с REST-эндпоинтами. Но это плохой выбор для такой задачи.

  • Проблема: REST — это протокол без состояния (stateless). Каждый запрос — новая история. А нам нужна постоянная, «живая» связь между LLM и нашим кодом. LLM должна иметь возможность задавать уточняющие вопросы в рамках одной сессии, сохраняя контекст.

  • Решение: @modelcontextprotocol/sdk. Это специализированный протокол, созданный Anthropic именно для таких задач. Он работает поверх WebSocket или IPC, обеспечивая постоянное соединение. Это позволяет LLM не просто «дергать» эндпоинты, а вести полноценный диалог с инструментами, кэшировать результаты и строить сложные цепочки вызовов. Это нативный язык общения для Claude.

2. Почему не Neo4j, а SQLite + sqlite-vec?

Граф кода — значит, нужна графовая база данных, верно? Не всегда.

Проблема: Профессиональные графовые СУБД (Neo4j, TigerGraph) — это тяжеловесные серверные решения. Они требуют отдельной установки, настройки и потребляют много ресурсов. Для локального инструмента, который каждый разработчик запускает на своей машине, это избыточно.

  • Решение: better-sqlite3 и расширение sqlite-vec. Это гениальное в своей простоте решение:

    • Zero-Configuration: SQLite — это просто файл. Никаких серверов, портов и паролей. Запустил — и работает.

    • Производительность: better-sqlite3 — одна из самых быстрых реализаций SQLite для Node.js. Для локальных задач ее скорости более чем достаточно.

    • Все в одном: Расширение sqlite-vec добавляет векторный поиск прямо в SQLite! Нам не нужно поднимать отдельную векторную базу (Chroma, Weaviate), что радикально упрощает стек. Граф связей и семантические векторы живут в одном файле.

3. Почему не Regex, а Tree-sitter?

Как разобрать код на десятке языков и не сойти с ума?

  • Проблема: Регулярные выражения — хрупкий и ненадёжный способ парсинга кода. Они ломаются на любой нестандартной конструкции. Использовать отдельные парсеры для каждого языка (Babel для JS, AST для Python) — сложно и громоздко.

  • Решение: web-tree-sitter. Это универсальный парсер, который:

    • Сверхбыстрый: Написан на C и скомпилирован в WebAssembly.

    • Устойчив к ошибкам: Если в коде есть синтаксическая ошибка (а она есть почти всегда в процессе редактирования), Tree-sitter не падает, а строит частичное дерево. Это критически важно для инструмента, работающего в реальном времени.

    • Мультиязычный: Достаточно подключить готовую грамматику для нужного языка, и он работает. Это позволяет проекту легко поддерживать JS, TS, Python и добавлять новые языки в будущем.

4. Сердце системы: Код как граф в SQLite

А теперь самое главное. Где и как живет этот граф? Может, для этого нужна тяжелая графовая СУБД вроде Neo4j? Нет, и это осознанное решение.

  • Проблема: Профессиональные графовые СУБД — это избыточность для локального инструмента. Они требуют отдельной установки, настройки и потребляют много ресурсов.

  • Решение: Гениальная простота SQLite. Мы эмулируем графовую структуру с помощью двух обычных реляционных таблиц. Это классический подход, известный как "список смежности" (Adjacency List).

В файле project.db создаются всего две таблицы:

  1. entities (Сущности) — это УЗЛЫ (NODES) нашего графа.

    • Каждая строка в этой таблице — это отдельная сущность в коде: функция, класс, переменная, интерфейс.

    • Хранятся ее имя, тип, путь к файлу и координаты в коде.

  2. relationships (Отношения) — это РЁБРА (EDGES) нашего графа.

    • Каждая строка — это связь между двумя сущностями (sourceId → targetId).

    • Самое важное здесь — тип связи:

      • calls: функция A вызывает функцию B.

      • extends: класс Cat наследует класс Animal.

      • implements: класс UserService реализует интерфейс IUserService.

      • imports: файл A импортирует сущность из файла B.

Как этот граф строится?

Этот процесс автоматизирован с помощью агентной системы:

  1. CollectorAgent сканирует файлы, с помощью Tree-sitter парсит их в AST и находит все узлы (сущности), записывая их в таблицу entities.

  2. AnalysisAgent снова проходит по AST, но теперь ищет связи между уже найденными узлами. Находит вызов функции — создает ребро calls. Видит extends — создает ребро extends. И так далее, наполняя таблицу relationships.

В результате, без внешних зависимостей и сложных серверов, мы получаем полную, подробную и готовую к запросам карту всего нашего проекта в одном файле.

5. Семантический поиск в code-graph-rag-mcp: Поиск по смыслу, а не по словам

Представьте, что вы ищете в проекте код, отвечающий за обработку платежей. Вы можете использовать обычный поиск (Ctrl+F) по слову "payment". Но что, если разработчик назвал соответствующие функции handleTransaction, processCharge или executeOrder? Обычный поиск их не найдет.

Семантический поиск решает именно эту проблему. Он ищет код не по точному совпадению ключевых слов, а по смысловой близости (семантике).

Как это реализовано в проекте (под капотом)

Процесс состоит из трех основных этапов: векторизация, хранение и поиск.

Этап 1: Векторизация кода (Создание эмбеддингов)

За этот этап отвечает SemanticAgent.

  1. Что происходит: Агент проходит по всем сущностям в коде (функциям, классам, методам).

  2. Что он берет: Он берет не только сам код, но и, что важнее, контекстную информацию:

    • Название функции/класса (authenticateUser).

    • Комментарии и JSDoc/Docstrings (/** ... */).

    • Иногда даже имена переменных.

  3. Преобразование в векторы: Используя библиотеку @xenova/transformers (Transformers.js), агент загружает предварительно обученную языковую модель (например, all-MiniLM-L6-v2). Эта модель преобразует собранный текст в эмбеддинг — числовой вектор (например, массив из 384 чисел), который представляет семантическое значение этого фрагмента.

    • Важно: Весь этот процесс происходит полностью локально на вашей машине. Ни ваш код, ни его метаданные никуда не отправляются.

Этап 2: Хранение векторов в SQLite

Куда сохранять эти числовые векторы, чтобы по ним можно было быстро искать?

  1. Технология: Проект использует гениальное расширение для SQLite под названием sqlite-vec.

  2. Что оно делает: sqlite-vec добавляет в обычную базу данных SQLite возможность хранить векторные эмбеддинги и выполнять по ним сверхбыстрый поиск ближайших соседей (ANN, Approximate Nearest Neighbor).

  3. Преимущество: Это избавляет от необходимости поднимать отдельную векторную базу данных (Pinecone, Chroma, Weaviate). Все данные — и граф кода, и семантические векторы — лежат в одном легковесном файле project.db.

Этап 3: Выполнение поиска

Это происходит, когда вы задаете вопрос LLM.

  1. Ваш запрос: Вы пишете в Claude: "Найди код, который отвечает за аутентификацию пользователя".

  2. Действие LLM: Модель понимает, что это поисковый запрос, и вызывает инструмент semantic_search с вашим текстом в качестве аргумента.

  3. Что делает инструмент:

    • Он берет ваш запрос ("аутентификация пользователя") и с помощью той же модели из @xenova/transformers превращает его в такой же вектор.

    • Затем он выполняет SQL-запрос к sqlite-vec, говоря: "Найди мне топ-5 векторов в базе, которые наиболее близки (по косинусному расстоянию) к вектору моего запроса".

    • sqlite-vec мгновенно находит самые релевантные фрагменты кода. Это могут быть функции с именами login, verifyToken, jwtMiddleware — даже если в них нет слова "аутентификация", их семантические векторы будут близки к вектору вашего запроса.

  4. Результат: Инструмент возвращает LLM список найденных сущностей, и модель формирует для вас осмысленный ответ.

Схема работы

   Ваш запрос               Инструмент `semantic_search`          База данных SQLite
("user auth code")   ───>   1. "user auth code" → [0.1, 0.9, ...] (Вектор запроса)
                              2. ЗАПРОС К SQLITE:
                                 "Найти векторы, близкие к [0.1, 0.9, ...]"
                                                                   ▲
                                                                   │
                                 3. ПОЛУЧИТЬ РЕЗУЛЬТАТЫ:           │ (sqlite-vec)
                                    - login()                      │
                                    - verifyToken()                ▼
                                                                Хранилище векторов
                                                               (для login, verifyToken, ...)

Ключевые преимущества этого подхода

  1. Поиск по намерению: Вы можете описывать то, что ищете, своими словами, а не угадывать точные названия функций.

  2. Устойчивость к синонимам и разным формулировкам: payment, charge, transaction — для семантического поиска это близкие по смыслу понятия.

  3. Естественный язык: Позволяет LLM использовать свою сильную сторону — понимание естественного языка — для поиска по кодовой базе.

  4. Приватность и локальность: Весь процесс, от создания эмбеддингов до поиска, происходит на вашей машине.

В итоге, семантический поиск идеально дополняет графовый анализ. Если граф отвечает на вопрос "Как код устроен?", то семантический поиск отвечает на вопрос "Где в коде находится нужная мне функциональность?". Вместе они дают LLM беспрецедентную глубину понимания вашего проекта.

Итоговая архитектура

5. Почему не монолит, а многоагентная система?

Индексация большого проекта — сложный процесс. Его можно реализовать как один большой скрипт, но это плохая идея.

  • Проблема: Монолитный индексатор сложно отлаживать и масштабировать. Если на этапе анализа зависимостей произойдет сбой, весь процесс остановится.

  • Решение: Декомпозиция на агентов, каждый со своей зоной ответственности:

    • Collector Agent: «Разведчик». Быстро сканирует файлы и строит базовый AST. Его задача — собрать сырые данные.

    • Analysis Agent: «Аналитик». Берет сырые данные и обогащает их, находя связи: кто кого вызывает, кто от кого наследуется.

    • Semantic Agent: «Лингвист». Создает векторные эмбеддинги для семантического поиска.

    • Refactoring Agent: «Техлид». Ищет дубликаты, анализирует сложность и находит «узкие места». Такой подход позволяет распараллелить работу, сделать систему отказоустойчивой и легко добавлять новые виды анализа в будущем.

Итоговая архитектура

         ╔════════════════════╗      ╔════════════════════╗
         ║    Claude Desktop  ║<═════║      MCP Server    ║
         ║ (или другой клиент)║      ║ (на Node.js + SDK) ║
         ╚════════════════════╝      ╚═════════╦══════════╝
                                               ║ (Запросы через 13 инструментов)
                                               ▼
  ╔════════════════════════════════════════════════════════════════════════╗
  ║                        База знаний (Knowledge Base)                    ║
  ║                     ┌────────────────────────────────┐                 ║
  ║                     │  SQLite файл (project.db)      │                 ║
  ║                     │                                │                 ║
  ║                     │  • Граф кода (таблицы)         │                 ║
  ║                     │  • Векторы (sqlite-vec)        │                 ║
  ║                     └────────────────────────────────┘                 ║
  ╚═══════════════════════════════════════╦════════════════════════════════╝
                                          ║ (Построение и обновление)
  ┌──────────────────┐      ┌─────────────┴────────────┐      ┌──────────────────────────┐
  │   Кодовая база   ├─────▶│  Парсинг (Tree-sitter)   ├─────▶│   Агентная система       │
  │ (JS, TS, Python) │      └──────────────────────────┘      │ (Collector, Analyzer...) │
  └──────────────────┘                                        └──────────────────────────┘

13 мощных инструментов: Арсенал вашего AI-ассистента

Сервер предоставляет 13 специализированных инструментов, которые LLM может использовать для анализа вашего проекта:

  • Основные: get_entities, semantic_search, find_similar_code, get_entity_details, search_by_pattern.

  • Анализ связей: get_relationships, analyze_dependencies, get_call_graph, impact_analysis.

  • Рефакторинг: suggest_refactoring, find_duplicates, analyze_complexity, find_hotspots.

Производительность: 5.5x быстрее нативных инструментов

Метрика

Нативный Claude

MCP CodeGraph

Улучшение

Время выполнения

55.84 с

<10 с

5.5x быстрее

Потребление памяти

Зависит от процесса

~65MB

Оптимизировано

Количество функций

Базовые паттерны

13 инструментов

Комплексный анализ

Точность

На основе паттернов

Семантическая

Превосходящая

Технические характеристики:

  • Скорость индексации: 100+ файлов в секунду.

  • Время ответа на запросы: <100 мс.

Практический пример: От вопроса к инсайту за секунды

Запрос в Claude Desktop:
> «Что сломается, если я изменю интерфейс IUserService?».

Что происходит «под капотом»:

  1. LLM видит ключевые слова «изменю» и «интерфейс» и вызывает инструмент impact_analysis с аргументом IUserService.

  2. Сервер мгновенно выполняет запрос к своему графу в SQLite: «Найти все сущности, которые реализуют или напрямую зависят от IUserService».

  3. Сервер возвращает список классов (UserService, AdminController) и файлов, которые будут затронуты.

  4. LLM получает этот структурированный список и генерирует человекочитаемый ответ: «Изменение IUserService затронет класс UserService, который его реализует, и AdminController, который использует его для инъекции зависимостей. Вам потребуется обновить реализацию в этих файлах».

Это не просто поиск по тексту. Это глубокий анализ, основанный на понимании структуры кода.

Установка и интеграция

Начать работу проще простого.

Установка:

npm install -g @er77/code-graph-rag-mcp

Настройка Claude Desktop:

npx @modelcontextprotocol/inspector add code-graph-rag \
  --command "npx" \
  --args "@er77/code-graph-rag-mcp /path/to/your/codebase"

После этого просто выберите code-graph-rag в списке инструментов в Claude и начинайте задавать вопросы о своем проекте.

Заключение

Проект code-graph-rag-mcp — это шаг от «LLM как генератора текста» к «LLM как члена команды». Предоставляя модели глубокий контекст через графы знаний, мы открываем совершенно новые возможности для автоматизации рутинных задач, анализа сложных систем и безопасного рефакторинга.

Выбранный технологический стек — MCP, Tree-sitter и SQLite — не случаен. Он является результатом поиска баланса между производительностью, простотой использования и мощностью. Это локальный, приватный и невероятно быстрый инструмент, который может стать вашим незаменимым помощником в разработке.

Попробуйте сами и дайте своей LLM «суперсилу» — знание вашего кода.

Ссылка на проект

Комментарии (0)


  1. SweetDreamBoss
    17.09.2025 20:38

    Интересно, но всё таки достаточно сложный процесс для обычных обывателей, которые хотят просто ввести свой проект

    Я разработал нечто подобное, но назвал Капсула памяти, суть в том что в конце каждой сессии ты прописываешь триггер, в том же чате, и капсула обновляется учитывая сегодняшним доработки, сохраняя знания, опыт и контекст


    1. AppCrafter
      17.09.2025 20:38

      А можно подробнее?


      1. SweetDreamBoss
        17.09.2025 20:38

        Я написал статью здесь, жду когда пройдет модерацию.

        Если кратко:

        Капсула памяти Sh/AIDS v6.2 — это система сохранения и передачи контекста для ИИ, которая решает ключевую проблему «потери памяти» между сессиями(LLM).

        Ключевые преимущества:

        · Мгновенное восстановление контекста — ИИ сразу работает с вашими задачами, без повторных объяснений

        · Экономия 90% времени — исключает рутину онбординга в каждом новом диалоге

        · Персонализированные ответы — ИИ учитывает ваш стиль мышления, цели и предпочтения

        · Масштабируемость — подходит для личного использования, командной работы и бизнес-процессов

        · Повышение качества решений — за счёт полного контекста ИИ дает более точные и релевантные ответы

        Основная польза: Превращает разрозненные диалоги с ИИ в непрерывный осмысленный процесс,где каждое взаимодействие строится на основе предыдущего опыта. Это особенно ценно для сложных задач, требующих долгосрочной работы: обучение, проектирование, анализ данных и карьерное развитие.


  1. Dharmendra
    17.09.2025 20:38

    идея отличная, но есть немного дегтя сугубого имхо:
    - заваливать LLM еще одной кучей тулов через MCP - путь к запутыванию модели, ей и так не просто если прицепили Jira, Github и еще парочку.
    - про rerank я что-то не нашел в статье ничего
    - использование простеньких моделей для embedding - медленно, печально, слабо и частая зарядка ноута :), а хороших - нужен хоть какой то GPU с VRAM (или mac studio m3).
    - скорость отработки через mcp - имхо, это не нативно юзать фреймворки а-ля LangChain/Graph иже с ними. Но, конечно, статья не про написание своего агента, а про юзание готового с расширением обвески-фичей. Тут наверное идея правильная от безысходности. Спасибо за идею, будем думать.



  1. folkote
    17.09.2025 20:38

    Roocode с codebase indexing делает это