fix(build): ignore global MCP IDs in legacy detection - #2441
fix(build): ignore global MCP IDs in legacy detection#2441aryanbonigala wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR addresses a false-positive in the legacy-ID detection logic for MCP nodes. In graphify/build.py, it adds a new _MCP_GLOBAL_ID_KINDS set and a _has_global_id helper, then makes graph_has_legacy_ids skip nodes whose IDs are global by construction (like mcp_command, mcp_package, env_var) rather than derived from the source file stem. The intent is to prevent freshly-built graphs containing a nested .mcp.json from being incorrectly flagged as using an outdated ID scheme. The change also adds parametrized unit tests and an end-to-end extraction test in tests/test_build.py covering the exemption, the narrowness of that exemption for file-scoped MCP kinds, and malformed metadata handling.
No blocking issues surfaced.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 802 functions depend on the 200 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
graph_has_legacy_ids()— 9 callers, 6 callees
Verification — 802 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 601 function(s) in the blast radius were not formally verified this run
· 1 grounded finding(s) anchored inline below.
| return meta.get("mcp_kind") in _MCP_GLOBAL_ID_KINDS | ||
|
|
||
|
|
||
| def graph_has_legacy_ids(nodes: list, root: str | Path | None = None, sample: int = 300) -> bool: |
There was a problem hiding this comment.
graph_has_legacy_ids()
fans out to 6 callees (efferent coupling); 9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Summary
graph_has_legacy_ids()usessource_location == "L1"as a cheap proxy for "this node is file-level, so its ID is unambiguously the file stem." MCP ingestion breaks that assumption: JSON exposes no line information, somcp_ingeststamps every node it emits with line 1 — including node kinds whose IDs are deliberately global rather than derived fromsource_file.mcp_command,mcp_package, andenv_varare global by design:mcp_command_npxis meant to be the same node across every config file that invokesnpx, so its ID never carries a file stem.For a nested config such as
sub/.mcp.json, the canonical stem issub_mcpwhile_old_file_stems()also yields the bare pre-#1504 formmcp. A perfectly valid, freshly generatedmcp_command_npxtherefore matches the oldmcp_stem prefix but not the canonical one, and the heuristic reports the graph as legacy. Because the IDs are correct, rebuilding does not clear it — the warning is permanent.A root-level
.mcp.jsonnever tripped this: theremcpis the canonical stem, so the same IDs hit the canonical branch first.The fix excludes only the three documented global MCP kinds from the heuristic, via a named
_MCP_GLOBAL_ID_KINDSset and a_has_global_id()predicate that readsmetadata.mcp_kinddefensively (missing or non-dict metadata is not exempted and does not raise). File-scoped MCP nodes (mcp_config_file,mcp_server) are stem-derived and remain fully subject to legacy detection, as do genuine pre-#1504 IDs. Callers, warning wording, and the graph schema are unchanged.Fixes #2408
Testing
Focused legacy/MCP tests:
The three new assertions fail on the base commit (both
mcp_-prefixed global kinds and the nested end-to-end case) and pass with the fix.Related suites:
Full suite:
Lint and whitespace:
uv.lockis unchanged (git diff -- uv.lockis empty).The end-to-end regression (
test_fresh_mcp_graph_is_not_flagged_legacy) is parametrized over nestedsub/.mcp.jsonand root-level.mcp.json, and drives real extraction throughgraphify.extract.extract()rather than a hand-built node dict — it assertsmcp_command_npxis actually present before asserting the graph is not flagged. A companion test keepsmcp_config_file/mcp_servernodes and malformed-metadata nodes under detection so the exemption cannot widen silently.