Skip to content

Commit 81c9f1c

Browse files
address review: resolve success guard, round-trip coverage, invariants
Document status-driven resolve hints; suppress when success is explicitly false. Extend round-trip with status many and route/client none paths. Assert resolve invariants on exception output. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 4c71733 commit 81c9f1c

3 files changed

Lines changed: 62 additions & 3 deletions

File tree

mcp_hints.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,18 @@ def generate_hints(
135135
) -> list[str]:
136136
"""Return up to 5 road-sign hint strings for a success-only MCP v2 payload dict.
137137
138-
Callers must pass ``success: True`` payloads only for hint rows; this function
139-
returns ``[]`` when ``success`` is false or missing.
138+
For ``search`` / ``find`` / ``describe`` / ``neighbors``, callers must pass
139+
``success: True``; this function returns ``[]`` when ``success`` is false or
140+
missing. The ``resolve`` branch is **status-driven** (``status``,
141+
``resolved_identifier``, ``candidates``, optional seeds) and does not require
142+
``success`` in the payload; an explicit ``success: False`` still suppresses
143+
hints (defense in depth).
140144
"""
141145
pairs: list[tuple[int, str]] = []
142146

143147
if output_kind == "resolve":
148+
if payload.get("success") is False:
149+
return []
144150
status = str(payload.get("status") or "")
145151
if status == "one":
146152
return []

mcp_v2.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1185,13 +1185,15 @@ def resolve_v2(
11851185
capped = ranked[:_RESOLVE_CANDIDATE_CAP]
11861186
return _resolve_finalize_success(trimmed, hint_kind, capped)
11871187
except Exception as exc:
1188-
return ResolveOutput(
1188+
out = ResolveOutput(
11891189
success=False,
11901190
status="none",
11911191
message=str(exc),
11921192
hints=[],
11931193
resolved_identifier=None,
11941194
)
1195+
_resolve_assert_invariants(out)
1196+
return out
11951197

11961198

11971199
@validate_call(config={"arbitrary_types_allowed": True})

tests/test_mcp_hints.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import inspect
4+
from collections import Counter
45
from typing import Any
56

67
import pytest
@@ -422,6 +423,20 @@ def _resolve_symbol_id_status_one(kuzu_graph) -> str:
422423
return sym_id
423424

424425

426+
def _resolve_symbol_short_name_status_many(kuzu_graph) -> str:
427+
rows = kuzu_graph._rows( # noqa: SLF001
428+
"MATCH (s:Symbol) WHERE s.kind = 'method' RETURN s.name AS name",
429+
)
430+
counts = Counter(str(r["name"]) for r in rows if r.get("name"))
431+
dup_name = next((name for name, c in counts.items() if c >= 2), None)
432+
if dup_name is None:
433+
pytest.fail("no duplicated method short names in bank-chat fixture")
434+
out = resolve_v2(dup_name, hint_kind="symbol", graph=kuzu_graph)
435+
if not (out.success and out.status == "many" and len(out.candidates) >= 2):
436+
pytest.fail(f"expected status many for short name {dup_name!r}, got {out.status!r}")
437+
return dup_name
438+
439+
425440
def _resolve_symbol_identifier_status_none(kuzu_graph) -> str:
426441
ident = "com.nonexistent.ZzzMissing"
427442
out = resolve_v2(ident, hint_kind="symbol", graph=kuzu_graph)
@@ -547,6 +562,19 @@ def test_hints_resolve_status_many_truncated_cap_wording() -> None:
547562
assert "10 candidates" in hints[0]
548563

549564

565+
def test_hints_resolve_success_false_suppresses() -> None:
566+
hints = generate_hints(
567+
"resolve",
568+
{
569+
"success": False,
570+
"status": "none",
571+
"resolved_identifier": "com.foo.Bar",
572+
"hint_kind": "symbol",
573+
},
574+
)
575+
assert hints == []
576+
577+
550578
def test_hints_resolve_payload_missing_identifier_suppressed() -> None:
551579
hints = generate_hints(
552580
"resolve",
@@ -573,6 +601,29 @@ def test_hints_resolve_v2_round_trip(kuzu_graph) -> None:
573601
assert wildcard_out.resolved_identifier == "com.foo.*Service"
574602
assert wildcard_out.hints == []
575603

604+
many_ident = _resolve_symbol_short_name_status_many(kuzu_graph)
605+
many_out = resolve_v2(many_ident, hint_kind="symbol", graph=kuzu_graph)
606+
assert many_out.resolved_identifier == many_ident
607+
assert many_out.hints
608+
assert "candidates" in many_out.hints[0]
609+
assert "tighten identifier" in many_out.hints[0]
610+
611+
route_ident = "POST /v1/__no_such_resolve_route__"
612+
route_out = resolve_v2(route_ident, hint_kind="route", graph=kuzu_graph)
613+
assert route_out.success is True
614+
assert route_out.status == "none"
615+
assert route_out.resolved_identifier == route_ident
616+
assert route_out.hints
617+
assert "find(kind='route'" in route_out.hints[0]
618+
619+
client_ident = "__no_such_resolve_client_target__"
620+
client_out = resolve_v2(client_ident, hint_kind="client", graph=kuzu_graph)
621+
assert client_out.success is True
622+
assert client_out.status == "none"
623+
assert client_out.resolved_identifier == client_ident
624+
assert client_out.hints
625+
assert "find(kind='client'" in client_out.hints[0]
626+
576627
invalid_out = resolve_v2("", graph=kuzu_graph)
577628
assert invalid_out.success is False
578629
assert invalid_out.resolved_identifier is None

0 commit comments

Comments
 (0)