Skip to content

Commit 3e903be

Browse files
committed
docs: add MCP fix/improvement report
Summarizes the resolver, MCP tool, and Python call-graph fixes on this branch: what was broken, what changed, and the measured effect of each.
1 parent f9bcacc commit 3e903be

1 file changed

Lines changed: 213 additions & 0 deletions

File tree

MCP_FIX_REPORT.md

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# Отчёт: правки MCP-инструментов и резолвера символов
2+
3+
Период: 2026-07-08 — 2026-07-09.
4+
5+
Свод изменений, сделанных в рамках диагностики и доработки MCP-сервера:
6+
исправление ошибок в резолюции символов и call-графа, повышение
7+
достоверности ответов инструментов, снижение расхода токенов.
8+
9+
## 1. Memory-gate: корректная обработка `available_memory == 0`
10+
11+
**Файлы:** `memory.rs`, `mcp/engine.rs`.
12+
13+
- **Было:** проверка «хватает ли памяти для загрузки ONNX-модели
14+
эмбеддингов» была продублирована в двух местах с одинаковым порогом
15+
`1_500_000_000` байт. Показание `available_memory == 0` (что `sysinfo`
16+
реально отдаёт под memory pressure) трактовалось как «памяти нет», и
17+
сервер гарантированно уходил в graph-only режим без семантического
18+
поиска.
19+
- **Стало:** логика вынесена в `should_skip_embeddings_for_available_memory`,
20+
используется из обоих мест. `0` теперь трактуется как «показание
21+
неизвестно», а не «памяти нет» — сервер пробует загрузить модель вместо
22+
автоматического отказа.
23+
- **Эффект:** на машине с нестабильными показаниями `sysinfo` (382–482 MB,
24+
иногда ровно 0) сервер стал грузить модель эмбеддингов вместо
25+
гарантированной деградации до graph-only.
26+
27+
## 2. `nodeId` как число ломал резолюцию узла
28+
29+
**Файл:** `mcp/server.rs`.
30+
31+
- **Было:** `args.get("nodeId").and_then(|v| v.as_str())` — если клиент
32+
передавал ID числом (`{"nodeId": 1306}`, ровно так, как его отдаёт
33+
`codegraph_symbol_search`), парсинг молча проваливался в `None`, и
34+
инструмент отвечал «узел не найден», хотя он существовал в графе.
35+
- **Стало:** добавлена `arg_node_id(v) -> Option<NodeId>`, принимающая и
36+
число, и строку. Применена во всех 5 местах (`get_callers`,
37+
`get_callees`, `traverse_graph`, `get_symbol_info`,
38+
`get_detailed_symbol`).
39+
- **Эффект:** инструменты, принимающие `nodeId`, теперь работают
40+
независимо от того, в каком JSON-типе клиент передал идентификатор.
41+
42+
## 3. Рассинхронизация форматов путей (относительный vs `file://`)
43+
44+
**Файлы:** `node_resolution.rs`, `mcp/server.rs`, `handlers/ai_context.rs`.
45+
46+
- **Было:** индексатор хранит путь относительно workspace-root
47+
(`./crates/foo/bar.rs`), а обработчики требовали `file://` URI,
48+
превращали его в абсолютный путь и сравнивали через `==` — строки
49+
никогда не совпадали. Наихудший случай: при ошибке парсинга URI путь
50+
молча превращался в пустую строку, которая случайно совпадала с path
51+
нескольких синтетических узлов — инструмент возвращал **случайный,
52+
не относящийся к запросу символ** без явной ошибки.
53+
- **Стало:** добавлены `paths_match()` (сравнение путей по границе
54+
компонента, устойчивое к relative/absolute; пустые строки не совпадают
55+
ни с чем) и `resolve_uri_to_path()` (принимает и `file://`, и обычный
56+
путь). Применены во всех местах, где раньше было строгое `==` по path.
57+
- **Эффект:** `get_ai_context`, `get_edit_context`, `get_callers`,
58+
`get_callees`, `traverse_graph`, `get_symbol_info`, `get_detailed_symbol`,
59+
`find_related_tests`, `analyze_complexity` перестали как не находить
60+
проиндексированные файлы, так и молча подменять символ на неверный.
61+
62+
## 4. `codegraph_reindex_workspace`: ложный `status: "success"` на пустом графе
63+
64+
**Файл:** `mcp/server.rs`.
65+
66+
- **Было:** мягкий (`force=false`) реиндекс мог отрапортовать успех, даже
67+
если граф после реиндекса фактически пуст.
68+
- **Стало:** после индексации сверяется `node_count` с количеством
69+
обработанных файлов; при явном расхождении статус — `degraded`.
70+
Дополнительно: если мягкий реиндекс дал `degraded`, инструмент
71+
автоматически повторяет его с `force=true` один раз (поле
72+
`auto_retried_with_force`), прежде чем возвращать ответ клиенту.
73+
- **Эффект:** типовой случай рассинхронизации инкрементального состояния
74+
теперь устраняется без ручного повторного вызова с `force=true`.
75+
76+
## 5. Структурированные причины «symbol not found»
77+
78+
**Файлы:** `node_resolution.rs`, `mcp/server.rs`.
79+
80+
- **Было:** все инструменты при неудачной резолюции возвращали одну и ту
81+
же общую фразу без указания причины (неверный URI? неиндексированный
82+
файл? пустой workspace?).
83+
- **Стало:** `enum SymbolNotFoundReason` (`InvalidUri`, `WorkspaceNotIndexed`,
84+
`FileNotIndexed`) с кодом и actionable-подсказкой на каждый случай,
85+
плюс машиночитаемый `suggested_next_queries()` — подключены во всех
86+
8 инструментах, резолвящих `uri+line`/`nodeId`.
87+
- **Эффект:** агент получает конкретное объяснение причины отказа и
88+
готовую подсказку, что вызвать дальше, вместо непрозрачной общей
89+
ошибки.
90+
91+
## 6. `match_confidence` в ответах uri+line-инструментов
92+
93+
**Файлы:** `node_resolution.rs`, `callers.rs`, `symbol_info.rs`,
94+
`call_graph.rs`, `impact.rs`.
95+
96+
- **Было:** уровень доверия к резолюции виден только опционально
97+
(`used_fallback`, поле присутствует лишь при `true`).
98+
- **Стало:** добавлено поле `match_confidence: "exact" | "fallback"`,
99+
присутствующее в ответе всегда.
100+
- **Эффект:** единообразная, всегда доступная проверка достоверности
101+
ответа без реконструкции состояния из опционального поля.
102+
103+
## 7. `codegraph_probe_symbol` — новый лёгкий инструмент
104+
105+
**Файлы:** `domain/probe_symbol.rs` (новый), `mcp/server.rs`, `mcp/tools.rs`.
106+
107+
- **Задача:** проверить «попал ли запрос в правильный символ» без
108+
full-context ответа.
109+
- **Реализация:** принимает `uri+line`/`nodeId`, возвращает только
110+
`name`/`type`/`node_id`/`uri`/`line_start`/`line_end`/`used_fallback`/
111+
`match_confidence` — без source, без callers/callees.
112+
- **Эффект:** на порядок более дешёвая по токенам проверка резолюции по
113+
сравнению с полным `get_symbol_info`.
114+
115+
## 8. `get_detailed_symbol`: дублирование callers/callees + `compact`
116+
117+
**Файлы:** `symbol_info.rs`, `mcp/server.rs`, `mcp/tools.rs`,
118+
`handlers/ai_query.rs`.
119+
120+
- **Было:** `get_detailed_symbol` считал callers/callees дважды — один раз
121+
внутри `get_symbol_info` (вложенно в `symbol.callers`/`symbol.callees`),
122+
второй раз отдельным вызовом `get_callers`/`get_callees` с теми же
123+
аргументами — итоговый JSON содержал один и тот же список каждого
124+
caller'а дважды, плюс лишний повторный обход графа на сервере.
125+
- **Стало:** `DetailedSymbolMeta` — вариант без вложенных callers/callees;
126+
top-level `callers`/`callees` берутся из уже посчитанного результата.
127+
Добавлен opt-in `compact: bool` (default `false`) — обрезает списки до
128+
5 записей с полями `*_truncated`.
129+
- **Эффект:** устранено дублирование данных в ответе и лишний обход
130+
графа; для символов с большим числом caller'ов доступен компактный
131+
режим ответа.
132+
133+
## 9. Секционный `get_edit_context`
134+
135+
**Файлы:** `edit_context.rs`, `mcp/server.rs`, `mcp/tools.rs`.
136+
137+
- **Было:** все 5 секций (`symbol`/`callers`/`tests`/`memories`/
138+
`recentChanges`), включая дорогое поле `code`, вычислялись безусловно.
139+
- **Стало:** добавлены переключатели `includeSymbol`/`includeCallers`/
140+
`includeTests`/`includeMemories`/`includeRecentChanges` и лимиты
141+
`maxCallers`/`maxTests` — секции пропускаются **на этапе вычисления**,
142+
а не только сериализации. По умолчанию поведение не изменилось.
143+
- **Эффект:** клиент может запросить только нужные секции и снизить
144+
нагрузку на сервер, а не только объём ответа.
145+
146+
## 10. `find_nearest_node` резолвился в `CodeFile`-узел вместо символа
147+
148+
**Файл:** `node_resolution.rs`.
149+
150+
- **Было:** узел файла (`NodeType::CodeFile`) создаётся без
151+
`line_start`/`line_end`; чтение через не-`Option` геттеры молча
152+
подставляло `0`, что давало диапазон `0..0`. При запросе с
153+
`target_line == 0` (частый дефолт, если клиент не указал строку) такой
154+
узел формально «выигрывал» у любой реальной функции/класса как
155+
«самое тесное совпадение» (`range_size = 0`) — резолвер возвращал файл
156+
вместо символа, без пометки fallback, с `code: "<source not available>"`
157+
и пустыми callers/tests.
158+
- **Стало:** `find_nearest_node` использует `line_start_opt`/`line_end_opt`
159+
и пропускает узлы без явно проставленного диапазона строк. Если в файле
160+
нет ни одного символа с реальным диапазоном, резолвер честно возвращает
161+
`None` вместо фейкового совпадения на файл.
162+
- **Эффект:** запрос с `line: 0` (или любой строкой вне размеченных
163+
символов) теперь резолвится в ближайший реальный символ файла, а не в
164+
бесполезный узел файла.
165+
166+
## 11. Вызовы `self.method()`/`cls.method()` не попадали в call-граф (Python)
167+
168+
**Файлы:** `codegraph-python/src/extractor.rs`,
169+
`codegraph-python/src/parser_impl.rs`.
170+
171+
- **Было:** два независимых дефекта одновременно исключали из графа
172+
подавляющее большинство вызовов внутри Python-класса:
173+
1. `node_map` при добавлении методов регистрировался только под голым
174+
именем (`"method_name"`), а вызовы извлекались с квалифицированным
175+
именем caller'а (`"ClassName.method_name"`) — поиск `caller` в
176+
`node_map` всегда проваливался, вызов уходил в межфайловое
177+
разрешение вместо локального ребра.
178+
2. `extract_callee_name` для `self.helper()` возвращала весь текст узла
179+
(`"self.helper"`) вместо голого имени (`"helper"`) — такая строка не
180+
совпадала ни с одной записью `node_map`.
181+
- **Стало:** методы регистрируются в `node_map` дополнительно под
182+
квалифицированным ключом; `extract_callee_name` для `self.`/`cls.`
183+
возвращает голое имя метода. Заодно исправлено связывание
184+
`Contains`-рёбер класс→метод при одноимённых методах в разных классах
185+
одного файла (раньше могли связаться с чужим методом).
186+
- **Эффект:** вызовы вида `self.method()`/`cls.method()` — то есть
187+
большинство вызовов внутри Python-класса — теперь становятся
188+
`Calls`-рёбрами в графе; `get_callers`/`get_callees`/`get_call_graph`/
189+
`analyze_impact` на методах перестали систематически недооценивать
190+
связи.
191+
192+
## 12. `codegraph_index_health` — новый инструмент диагностики индекса
193+
194+
**Файлы:** `mcp/server.rs`, `mcp/tools.rs`.
195+
196+
- **Задача:** дать агенту дешёвый способ проверить, не устарел ли индекс,
197+
и увидеть состояние всех известных namespace одним вызовом (вместо
198+
нескольких MCP-сессий).
199+
- **Реализация:** переиспользует уже существующую инфраструктуру
200+
(`_registry:<slug>` записи в общей `graph.db`, `graph_db_generation()`,
201+
`GitExecutor::head_commit()`, `IndexState::all_hashes()`). Отдаёт
202+
текущее состояние графа, generation, git HEAD, число изменившихся
203+
файлов с момента индексации, список других namespace и
204+
`suggested_next_queries` при обнаруженном дрейфе.
205+
- **Эффект:** агент может проверить актуальность индекса и увидеть все
206+
проиндексированные проекты без дополнительных round-trip'ов.
207+
208+
## Проверка
209+
210+
- `cargo test -p codegraph-server` — 369 unit-тестов, 0 упавших.
211+
- `cargo test -p codegraph-python` — 35 unit-тестов, 0 упавших.
212+
- Живые прогоны через `--run-tool` на реальном многофайловом workspace
213+
подтвердили каждый фикс отдельно (см. историю коммитов для деталей).

0 commit comments

Comments
 (0)