Skip to content
Open
Prev Previous commit
Next Next commit
fix(mcp): reconcile uri/path resolution, numeric nodeId, and add stru…
…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.
  • Loading branch information
Znbiz committed Jul 9, 2026
commit 786717ffd1ca1d2dc46e3fb1a6f415f2c793ff9c
2 changes: 2 additions & 0 deletions crates/codegraph-server/src/domain/call_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ pub(crate) struct CallGraphResult {
pub used_fallback: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fallback_message: Option<String>,
pub match_confidence: &'static str,
}

// ============================================================
Expand Down Expand Up @@ -290,6 +291,7 @@ pub(crate) async fn get_call_graph(
diagnostic,
used_fallback: used_fallback_field,
fallback_message,
match_confidence: crate::domain::node_resolution::match_confidence(used_fallback),
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/codegraph-server/src/domain/callers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub(crate) struct CallersResult {
pub used_fallback: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fallback_message: Option<String>,
pub match_confidence: &'static str,
}

/// Result of `get_callees`.
Expand All @@ -49,6 +50,7 @@ pub(crate) struct CalleesResult {
pub used_fallback: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fallback_message: Option<String>,
pub match_confidence: &'static str,
}

// ============================================================
Expand Down Expand Up @@ -117,6 +119,7 @@ pub(crate) async fn get_callers(
diagnostic,
used_fallback: used_fallback_field,
fallback_message,
match_confidence: crate::domain::node_resolution::match_confidence(used_fallback),
}
}

Expand Down Expand Up @@ -180,6 +183,7 @@ pub(crate) async fn get_callees(
diagnostic,
used_fallback: used_fallback_field,
fallback_message,
match_confidence: crate::domain::node_resolution::match_confidence(used_fallback),
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/codegraph-server/src/domain/impact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ pub(crate) struct ImpactResult {
pub used_fallback: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fallback_message: Option<String>,
pub match_confidence: &'static str,
}

// ============================================================
Expand Down Expand Up @@ -291,6 +292,7 @@ pub(crate) async fn analyze_impact(
warnings,
used_fallback: used_fallback_field,
fallback_message,
match_confidence: crate::domain::node_resolution::match_confidence(used_fallback),
}
}

Expand Down
2 changes: 0 additions & 2 deletions crates/codegraph-server/src/domain/node_props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ pub(crate) fn line_end(node: &Node) -> u32 {
}

/// Optional variant — returns None when neither key is present.
#[allow(dead_code)]
pub(crate) fn line_start_opt(node: &Node) -> Option<u32> {
line_start_opt_from_props(&node.properties)
}

/// Optional variant — returns None when neither key is present.
#[allow(dead_code)]
pub(crate) fn line_end_opt(node: &Node) -> Option<u32> {
line_end_opt_from_props(&node.properties)
}
Expand Down
Loading