Skip to content

fix(mcp): resolver reliability, structured diagnostics, token-efficiency tools#12

Open
Znbiz wants to merge 9 commits into
codegraph-ai:mainfrom
Znbiz:fix/mcp-resolution-and-token-efficiency
Open

fix(mcp): resolver reliability, structured diagnostics, token-efficiency tools#12
Znbiz wants to merge 9 commits into
codegraph-ai:mainfrom
Znbiz:fix/mcp-resolution-and-token-efficiency

Conversation

@Znbiz

@Znbiz Znbiz commented Jul 9, 2026

Copy link
Copy Markdown

Summary

  • Fixes numeric nodeId args, relative/absolute path mismatches, and a CodeFile-node resolution bug that together made uri+line/nodeId lookups unreliable across most MCP tools.
  • Adds structured SymbolNotFoundReason diagnostics + suggested_next_queries() and an always-present match_confidence field, replacing one generic "Could not find symbol" message.
  • codegraph_reindex_workspace now detects a reindex that left the graph empty (status: "degraded") and auto-retries once with force=true.
  • Adds codegraph_probe_symbol (cheapest possible symbol lookup) and codegraph_index_health (cheap staleness/cross-namespace check).
  • Dedupes callers/callees in codegraph_get_detailed_symbol (previously returned twice) and adds an opt-in compact mode.
  • Makes all 5 codegraph_get_edit_context sections independently toggleable, skipping the underlying query (not just serialization) when disabled.
  • Fixes a Python-specific bug where self.method()/cls.method() calls — the majority of intra-class calls — never became Calls edges in the graph.

See MCP_FIX_REPORT.md for the full before/after/effect writeup per change.

Test plan

  • cargo test -p codegraph-server --lib — 369 passed, 0 failed, 1 ignored
  • cargo test -p codegraph-python --lib — 35 passed, 0 failed
  • Each commit on this branch builds standalone (cargo build -p codegraph-server / -p codegraph-python) — verified via isolated worktrees per commit

Znbiz added 9 commits July 9, 2026 19:02
sysinfo can report exactly 0 bytes available under memory pressure. The
old threshold check (avail < 1_500_000_000) treated that the same as
"no memory left" and permanently forced graph-only mode (no embeddings).
Extract the check into should_skip_embeddings_for_available_memory() and
treat 0 as an unreliable reading instead of OOM, so the server still
attempts to load the embedding model.
…ctured not-found diagnostics

Several independent but related resolution bugs made uri+line and nodeId
lookups unreliable across almost every MCP tool:

- nodeId args were read with `.as_str()` only, so a numeric `{"nodeId": 1306}`
  (the same JSON type codegraph_symbol_search itself returns) silently failed
  to resolve. Add arg_node_id() to accept both.
- The indexer stores node paths relative to the workspace root, while
  handlers required an absolute file:// URI and compared with `==` — the two
  never matched. Worse, a failed URI parse silently became an empty string,
  which could accidentally match synthetic nodes and return an unrelated
  symbol with no error at all. Add paths_match()/resolve_uri_to_path() and
  use them everywhere a strict `==`/file:// URI was assumed.
- find_nearest_node treated nodes without an explicit line range (e.g. a
  Python module's CodeFile node) as a 0..0 range, which won every
  containment check for target_line == 0 and shadowed real symbols. Skip
  nodes without a line range instead.
- Failed resolutions returned one generic "Could not find symbol" message
  regardless of cause. Add SymbolNotFoundReason (invalid_uri /
  workspace_not_indexed / file_not_indexed) with a hint and
  suggested_next_queries(), wired into every uri+line/nodeId tool.
- Add a match_confidence ("exact" | "fallback") field, always present, so
  callers don't have to reconstruct trust level from the optional
  used_fallback field.
- codegraph_reindex_workspace could report status: "success" on a reindex
  that left the graph empty. Compare node_count after indexing against files
  processed and mark the response "degraded"; auto-retry once with
  force=true before returning, since that's what reliably fixes it.
A dedicated, minimal-cost way to check "did uri+line/nodeId resolve to the
symbol I expect" before paying for a full get_symbol_info or
get_detailed_symbol call. Returns only name/type/node_id/uri/line_start/
line_end/used_fallback/match_confidence — no source, no callers/callees,
no graph traversal. Added to the Core tool profile.
…mode

get_detailed_symbol called query_engine.get_symbol_info(node_id), which
already computes callers/callees at depth 1 and embeds them in the nested
symbol object, and then called get_callers/get_callees again with the same
arguments for the top-level fields — the response contained every caller
twice (once nested, once top-level) plus a redundant graph traversal.

Introduce DetailedSymbolMeta (the same metadata, without callers/callees)
and derive the top-level callers/callees from the single get_symbol_info
call instead. Add an opt-in `compact` flag that caps callers/callees at 5
entries each (with `*_truncated` counts) for symbols with large caller
lists, where that list dominates response size.
get_edit_context always computed all 5 sections (symbol/callers/tests/
memories/recentChanges) and the symbol's full source code, even when a
caller only needed one or two of them.

Add EditContextOptions (includeSymbol/includeCallers/includeTests/
includeMemories/includeRecentChanges, maxCallers, maxTests — defaults
unchanged from prior behavior) and thread it through get_edit_context so
disabling a section skips its underlying query entirely, not just its
serialization — the compute savings matter as much as the token savings.
EditContextSymbol.code becomes optional so includeSymbol=false can omit
the single most expensive field while still returning name/type/location.
Agents had no cheap way to check whether the current index is stale or
empty before trusting a "no results" answer, or to see what other projects
share the same on-disk graph database. Reuses existing infrastructure
(the _registry:<slug> entries persist_graph already writes, graph_db_generation(),
GitExecutor::head_commit(), IndexState::all_hashes()) rather than adding new
storage.

Returns current node/edge counts, generation, last-indexed time, a short git
HEAD hint, a cheap re-hash-based staleness check against already-tracked
files (no directory walk — that's what a real reindex is for), the other
namespaces sharing this machine's graph.db, and suggested_next_queries when
something looks off. Also surfaces codegraph_index_health as a suggested
next query on a degraded codegraph_reindex_workspace response.
Two independent bugs meant a call from inside one method to another via
self./cls. — the majority of calls inside any Python class — never became a
Calls edge in the graph:

- ir_to_graph registered functions in node_map only under their bare name,
  but extract_class extracts calls with a qualified caller name
  ("ClassName.method_name"). Looking up the caller always missed, so the
  call fell through to cross-file unresolved-call resolution instead of a
  same-file edge.
- extract_callee_name returned the full attribute text ("self.helper") for
  a `self.helper()` call instead of the bare method name, which couldn't
  match anything in node_map either.

Register methods under both the bare and qualified name, and strip
self./cls. from the callee name before matching. Also fixes Contains
(class -> method) edges incorrectly linking same-named methods across two
different classes in one file, which used the same bare-name lookup.

get_callers/get_callees/get_call_graph/analyze_impact on methods were
systematically undercounting call relationships unless the callee happened
to also be found by the cross-file fallback.
Summarizes the resolver, MCP tool, and Python call-graph fixes on this
branch: what was broken, what changed, and the measured effect of each.
…ADME

Удалён временный MCP_FIX_REPORT.md — его содержимое перенесено в раздел
Unreleased CHANGELOG.md без выпуска новой версии.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant