fix(mcp): search/find/neighbors family (#353, #354, #355, #356)#366
Merged
Conversation
…-type neighbor columns (#353-356) Four correctness/contract fixes in the search/find/neighbors family: #353 — search now forwards role/module/microservice/capability/exclude_roles from NodeFilter into run_search so the filter applies BEFORE pagination, not as a post-filter on the already-paginated page (which could empty a filtered page even when many matches existed deeper in the ranking). #354 — NodeRef gains `name`; the unresolved-call-site ref (other=NodeRef(..., name=callee)) no longer silently drops the callee identifier (pydantic extra='ignore' discarded it, leaving fqn='' and no readable callee). #355 — FindOutput and NeighborsOutput gain `has_more_results`. find_v2 computed it for the hint payload but never put it on the model; clients had to probe to tell if a next page existed. neighbors_v2 computes it where it paginates in Python (None on the single-origin CALLS SQL-paginated path, where calls_row_count is the signal). #356 — the generic flat-label neighbors query no longer RETURNs a fixed column superset regardless of matched edge type (typed-union RETURN anti-pattern that errors on stricter binders). It now issues one single-label query per edge type, RETURNing only that type's columns (map aligned with build_ast_graph REL schemas). Co-Authored-By: Claude <noreply@anthropic.com>
…full set
On the single-origin CALLS neighbors path, has_more_results was None
unconditionally. When paginate_in_sql is False (a node_filter is set,
include_unresolved, or dedup_calls), _neighbors_calls_for_origin loads
the FULL matching set (offset=0, limit=None), so the client already has
every edge -- has_more must be False, not None ("unknown"), so a paging
client need not issue a redundant probe (#355). Keep None only for the
SQL-paginated case where the row/unfiltered counts carry the signal.
Adds a regression test covering both modes (filtered -> False,
unfiltered single-origin -> None). Verified red->green.
Co-Authored-By: Claude <noreply@anthropic.com>
Owner
Author
|
Addressed a finding from a multi-agent review pass:
|
… hint input - _FLAT_EDGE_ATTR_COLUMNS now asserts at import that it covers every EdgeType exactly. A future edge type added to EdgeType + EDGE_SCHEMA + a REL TABLE but missing here would otherwise silently SELECT only (id, label) and yield edges with empty attrs via the .get(label, ()) fallback; the assert trips on both a missing and a stale entry. - The find/neighbors hint payloads used bare model_dump(), so every structured NodeRef carried name: null (and other null fields) into generate_hints. Switch to exclude_none=True -- these dicts feed generate_hints (which reads fields defensively via .get), not the tool result, and it matches the adjacent filter dumps. Verified against test_mcp_v2 / test_mcp_hints / test_client_hint_recovery. Co-Authored-By: Claude <noreply@anthropic.com>
Owner
Author
|
Addressed 2 review findings:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Four correctness/contract fixes in the search/find/neighbors family (
mcp_v2.py).#353 — search filter was post-filtered after pagination
search(filter=…)applied theNodeFilteronly as a post-filter on rows already sliced byoffset/limit, instead of pushing the predicates into the LanceDB query. A filtered page could shrink to 0–2 results even when many matches existed deeper in the ranking; paginating a filtered search gave inconsistent pages. Fix: forwardrole/module/microservice/capability/exclude_rolesfrom theNodeFilterintorun_search(which already accepted them)._node_matches_filterstill re-checks every row afterward (covers the non-pushdownable fields and is the contract guarantee).#354 — unresolved-call-site NodeRef dropped the callee name
NodeRefhad nonamefield, soother=NodeRef(..., name=callee)was silently dropped (pydantic defaultextra="ignore"), leaving every unresolved-call-site edge withfqn=""and no readable callee in the structured ref. Fix: addname: str | None = NonetoNodeRef.#355 —
has_more_resultscomputed but droppedfind_v2computedhas_more_resultsfor the hint payload butFindOutputhad no such field, so MCP clients never learned whether more pages existed. Fix: addhas_more_resultstoFindOutput(wired) andNeighborsOutput(computed where neighbors paginates in Python;Noneon the single-origin CALLS SQL-paginated path, wherecalls_row_countis the signal).#356 — neighbors generic Cypher typed-union RETURN (latent portability)
The flat-label neighbors query RETURNed a fixed superset of attributes regardless of which edge types matched — the anti-pattern AGENTS.md warns about. Missing columns return
Noneon LadybugDB (no live bug), but a stricter binder (Kùzu) would error. Fix: issue one single-label query per edge type, RETURNing only that type's columns (via_FLAT_EDGE_ATTR_COLUMNS, aligned with theREL TABLEschemas inbuild_ast_graph.py).label(e) = $labelscalar equality per the Cypher note.Tests
One new test per fix, plus one FakeGraph test-double update:
test_search_pushes_nodefilter_into_run_search— asserts the 5 fields reachrun_search.test_unresolved_call_site_noderef_carries_callee_name—_unresolved_site_to_edgekeeps callee inother.name.test_find_exposes_has_more_results—has_more_resultsTrue mid-list, False past the end.test_neighbors_flat_labels_select_columns_per_edge_type— each per-label query RETURNs only that type's columns.test_neighbors_route_in_http_calls_returns_callersFakeGraph made label-aware (the per-label split now calls_rowsonce per label; the old double returned the same canned row for both labels).Notes
ontology_versionchange. TheNodeRef.name/*Output.has_more_resultsadditions are additive on the MCP output schemas._rowscalls from 1 to N (one per flat label) for multi-label neighbors; N is small and these are indexed lookups.Closes #353, closes #354, closes #355, closes #356.
🤖 Generated with Claude Code