Ми живемо в дивовижний час. Попросити 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" (Adjacency List).
У файлі project.db створюються всього дві таблиці:
- entities (Сутності) — це вузли (NODES) нашого графа.
- Кожен рядок в цій таблиці — це окрема сутність у коді: функція, клас, змінна, інтерфейс.
- Зберігаються її ім'я, тип, шлях до файлу та координати в коді.
- relationships (Відношення) — це РІБРА (EDGES) нашого графа.
- Кожен рядок — це зв'язок між двома сутностями (sourceId → targetId).
- Найважливіше тут — тип зв'язку:
- calls: функція A викликає функцію B.
- extends: клас Cat успадковує клас Animal.
- implements: клас UserService реалізує інтерфейс IUserService.
- imports: файл A імпортує сутність із файлу B.
Як цей граф будується?
Цей процес автоматизований за допомогою агентної системи:
- CollectorAgent — сканує файли, за допомогою Tree-sitter парсить їх в AST і знаходить всі вузли (сутності), записуючи їх в таблицю entities.
- AnalysisAgent — знову проходить по AST, але тепер шукає зв'язки між уже знайденими вузлами. Знаходить виклик функції — створює ребро calls. Бачить extends — створює ребро extends. І так далі, заповнюючи таблицю relationships.
В результаті, без зовнішніх залежностей і складних серверів, ми отримуємо повну, докладну та готову до запитів карту всього нашого проєкту в одному файлі.
5. Семантичний пошук у code-graph-rag-mcp: Пошук за змістом, а не за словами
Уявіть, що ви шукаєте у проєкті код, який відповідає за обробку платежів. Можете використовувати звичайний пошук (Ctrl+F) за словом "payment". Але що, якщо розробник назвав відповідні функції handleTransaction
, processCharge
або executeOrder
? Звичайний пошук їх не знайде.
Семантичний пошук вирішує саме цю проблему. Він шукає код не за точним збігом ключових слів, а за семантичною близькістю.
Як це реалізовано в проєкті (під капотом)
Процес складається з трьох основних етапів: векторизація, збереження та пошук.
Етап 1: Векторизація коду (Створення ембеддингів)
За цей етап відповідає SemanticAgent
.
- Що відбувається: Агент проходить по всім сутностям у коді (функціях, класах, методах).
- Що він бере: Він бере не тільки сам код, але й, що важливіше, контекстну інформацію:
- Назва функції/класу (
authenticateUser
). - Коментарі та JSDoc/Docstrings (
/** ... */
). - Іноді навіть імена змінних.
- Перетворення в вектори: Використовуючи бібліотеку
@xenova/transformers
(Transformers.js), агент завантажує заздалегідь навчану мовну модель (наприклад,all-MiniLM-L6-v2
). Ця модель перетворює зібраний текст на ембеддінг — числовий вектор (наприклад, масив з 384 чисел), який представляє семантичне значення цього фрагмента. - Важливо: Весь цей процес відбувається повністю локально на вашій машині. Ні ваш код, ні його метадані нікуди не відправляються.
Етап 2: Зберігання векторів в SQLite
Куди зберігати ці числові вектори, щоб за ними можна було швидко шукати?
- Технологія: Проєкт використовує геніальне розширення для SQLite під назвою
sqlite-vec
. - Що воно робить:
sqlite-vec
додає в звичайну базу даних SQLite можливість зберігати векторні ембеддинги та виконувати по ним сверхшвидкий пошук найближчих сусідів (ANN, Approximate Nearest Neighbor). - Перевага: Це позбавляє від необхідності піднімати окрему векторну базу даних (Pinecone, Chroma, Weaviate). Усі дані — і граф коду, і семантичні вектори — лежать в одному легковаговому файлі
project.db
.
Етап 3: Виконання пошуку
Це відбувається, коли ви ставите запит LLM.
- Ваш запит: Ви пишете у Claude: "Знайди код, який відповідає за аутентифікацію користувача".
- Дія LLM: Модель розуміє, що це пошуковий запит, і викликає інструмент
semantic_search
з вашим текстом як аргументом. - Що робить інструмент:Він бере ваш запит ("аутентифікація користувача") та за допомогою тієї ж моделі з
@xenova/transformers
перетворює його на такий же вектор. - Потім виконує SQL-запит до
sqlite-vec
, говорячи: "Знайди мені топ-5 векторів у базі, які найближчі (за косинусною відстанню) до вектора мого запиту". sqlite-vec
миттєво знаходить найрелевантніші фрагменти коду. Це можуть бути функції з назвамиlogin
,verifyToken
,jwtMiddleware
— навіть якщо у них немає слова "аутентифікація", їх семантичні вектори будуть близькі до вектора вашого запиту.- Результат: Інструмент повертає LLM список знайдених сутностей, і модель формує для вас змістовну відповідь.
Схема роботи
Ключові переваги цього підходу
- Пошук за наміром: Ви можете описувати те, що шукаєте, своїми словами, а не вгадувати точні назви функцій.
- Стійкість до синонімів та різних формулювань:
payment
,charge
,transaction
— для семантичного пошуку це близькі за змістом поняття. - Природна мова: Дозволяє LLM використати свою сильну сторону — розуміння природної мови — для пошуку по кодовій базі.
- Приватність та локальність: Увесь процес, від створення ембеддингів до пошуку, відбувається на вашій машині.
В підсумку, семантичний пошук ідеально доповнює графовий аналіз. Якщо граф відповідає на питання "Як код влаштований?", то семантичний пошук відповідає на питання "Де в коді знаходиться потрібна мені функціональність?". Разом вони дають LLM безпрецедентну глибину розуміння вашого проєкту.
Ітогова архітектура
5. Чому не моноліт, а багагентна система?
Індексація великого проєкту — складний процес. Його можна реалізувати як один великий скрипт, але це погана ідея.
- Проблема: Монолітний індексатор важко відлагоджувати та масштабувати. Якщо на етапі аналізу залежностей станеться збій, весь процес зупиниться.
- Рішення: Декомпозиція на агентів, кожен з яких має свою зону відповідальності:
- Collector Agent: "Розвідник". Швидко сканує файли і будує базовий AST. Його завдання — зібрати сирі дані.
- Analysis Agent: "Аналітик". Бере сирі дані і збагачує їх, знаходячи зв'язки: хто кого викликає, хто від кого наслідується.
- Semantic Agent: "Лінгвіст". Створює векторні ембеддинги для семантичного пошуку.
- Refactoring Agent: "Техлід". Шукає дублікати, аналізує складність і знаходить «вузькі місця». Такий підхід дозволяє розпаралелити роботу, зробити систему відмовостійкою та легко додавати нові види аналізу в майбутньому.
Ітогова архітектура
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
?».
Що відбувається «під капотом»:
- LLM бачить ключові слова «зміню» і «інтерфейс» та викликає інструмент
impact_analysis
з аргументомIUserService
. - Сервер миттєво виконує запит до свого графа в SQLite: «Знайти всі сутності, які реалізують або прямо залежать від
IUserService
». - Сервер повертає список класів (
UserService
,AdminController
) і файлів, які будуть зачеплені. - LLM отримує цей структурований список і генерує зрозумілу відповідь: «Зміна
IUserService
зачепить класUserService
, який його реалізує, іAdminController
, який використовує його для ін'єкції залежностей. Вам потрібно оновити реалізацію у цих файлах».
Це не просто пошук за текстом. Це глибокий аналіз, заснований на розумінні структури коду.
Установка та інтеграція
Почати працювати простіше простого.
Встановлення:
Налаштування Claude Desktop:
Після цього просто оберіть code-graph-rag
у списку інструментів у Claude і починайте ставити запитання про свій проєкт.
Висновок
Проєкт code-graph-rag-mcp
— це крок від «LLM як генератора тексту» до «LLM як члена команди». Надаючи моделi глибокий контекст через графи знань, ми відкриваємо зовсім нові можливості для автоматизації рутинних задач, аналізу складних систем та безпечного рефакторингу.
Вибраний технологічний стек — MCP, Tree-sitter та SQLite — не випадковий. Він є результатом пошуку балансу між продуктивністю, простотою використання та потужністю. Це локальний, приватний та неймовірно швидкий інструмент, який може стати вашим незамінним помічником у розробці.
Спробуйте самі і дайте своїй LLM «суперсилу» — знання вашого коду.
Посилання на проєкт
Коментарі