Skip to content

feat(hints): add machine-parseable hints_structured field to all MCP outputs#209

Merged
HumanBean17 merged 3 commits into
masterfrom
feat/hints-structured
May 23, 2026
Merged

feat(hints): add machine-parseable hints_structured field to all MCP outputs#209
HumanBean17 merged 3 commits into
masterfrom
feat/hints-structured

Conversation

@HumanBean17
Copy link
Copy Markdown
Owner

Summary

  • Add StructuredHint Pydantic model (tool, args, actionable) and hints_structured: list[StructuredHint] field on all five MCP output models (SearchOutput, FindOutput, DescribeOutput, NeighborsOutput, ResolveOutput)
  • Refactor generate_hints to return tuple[list[str], list[_StructuredHint]] — string hints remain backward-compatible, structured hints are additive
  • Map every existing string-hint trigger to a structured equivalent across all branches (describe, find, resolve, neighbors, search); batch-placeholder hints (N2–N7) populate args.ids from results; prose-only hints use actionable=False
  • 36 named structured-hint tests including parity invariant, cap/dedup, and round-trip integration test
  • README mentions hints_structured; propose status locked

Scope

Implements PR-1 from plans/PLAN-HINTS-STRUCTURED.md.
Proposal: propose/HINTS-STRUCTURED-PROPOSE.md

Sentinel checks

git diff master..HEAD -- | grep -c "ONTOLOGY_VERSION\|build_ast_graph.py\|MCP_HINTS_FIELD_DESCRIPTION"
# 0 matches
grep -c 'hints: list\[str\]' mcp_v2.py
# 5

Validation

.venv/bin/ruff check .
# All checks passed

.venv/bin/python -m pytest tests -v
# 733 passed, 9 skipped

Key design decisions

  • No re-index required — MCP response shape only, no graph/index changes
  • Backward compatiblehints: list[str] unchanged; hints_structured is additive
  • Single generate_hints function returning both string and structured hints from unified trigger logic
  • Internal _StructuredHint NamedTuple in mcp_hints.py avoids circular import with mcp_v2.py; Pydantic StructuredHint converts from it at call sites

🤖 Generated with Claude Code

@HumanBean17
Copy link
Copy Markdown
Owner Author

Code Review

Overall this is well-executed — backward-compatible, clean separation via _StructuredHint NamedTuple to avoid circular imports, and thorough test coverage. A few issues to flag:


Issues

1. resolve/none branch appends extra search structured hint for all hint_kinds (mcp_hints.py ~line 850)

The struct_pairs.append(_StructuredHint("search", ...)) is outside the if/elif/else on hint_kind, so when hint_kind="route", two structured hints are emitted (one find + one search) but only one string hint. This means len(structured) > len(string), violating the parity invariant — except the parity test only checks hint_kind="symbol". Either move the search append into the else branch only, or update the parity test to cover route/client cases.

2. resolve/many structured hint has placeholder empty args (mcp_hints.py ~line 846)

struct_pairs.append(_StructuredHint(
    "resolve", {"identifier": "", "hint_kind": ""}, False, PRIORITY_META,
))

The identifier and hint_kind are empty strings — not useful to a machine consumer. Consider either omitting this hint entirely (there's no actionable next step for "many" beyond "tighten your query"), or populating with the actual resolved_identifier.

3. find page-full structured hint has empty filter: {} (mcp_hints.py ~line 920)

struct_pairs.append(_StructuredHint(
    "find", {"kind": kind, "filter": {}, "limit": int(lim)}, False, PRIORITY_META,
))

Since actionable=False this is advisory, but {} for filter gives the consumer nothing to work with. Consider including the original filter from the request payload, or at minimum the kind-specific filter field that was used.


Minor observations

  • _to_structured_hints accepts list[Any] — understandable given the cross-module constraint, but a protocol/TypedDict could tighten this without circular imports.
  • describe method EXPOSES uses direction="out" while the string template is named TPL_DESCRIBE_METHOD_INBOUND_ROUTE. The direction is correct, but the naming contrast is confusing for future readers.
  • No hints_structured=[] on error return paths — works fine via Pydantic default_factory=list, but being explicit (like hints=[]) would be more consistent.
  • finalize_structured_hints uses json.dumps(args, sort_keys=True) for dedup key — correct given the small scale, but worth noting.

Test coverage

Excellent. 36 new tests covering every variant, parity invariant, cap/dedup, round-trip integration, and prose-only actionable=False. The _hints() wrapper for existing tests is clean.


Verdict

The resolve/none extra search hint (#1) is the most notable issue — worth fixing before merge to prevent a subtle parity violation. The other two are lower-priority polish.

🤖 Generated with Claude Code

@HumanBean17
Copy link
Copy Markdown
Owner Author

Re-review: All issues addressed

All three items from the prior review are fixed:

  1. resolve/none search hint — moved inside the else block; route/client no longer get an extra search structured hint.
  2. resolve/many placeholder args — now populates identifier and hint_kind from the actual payload instead of empty strings.
  3. find page-full filter — now passes flt (the actual request filter) instead of {}.
  4. Parity test — expanded to cover hint_kind="route" and hint_kind="client" resolve/none cases.

LGTM. Ship it.

🤖 Generated with Claude Code

HumanBean17 and others added 2 commits May 24, 2026 01:29
Add StructuredHint model (tool, args, actionable) alongside existing
hints: list[str] on all five MCP output models. Refactor generate_hints
to return (string_hints, structured_hints) tuple. Map every existing
string-hint trigger to a structured equivalent. Batch-placeholder
hints (N2-N7) populate args.ids from results. Prose-only hints use
actionable=False. Full test coverage (36 named tests), parity
invariant enforced, round-trip integration test passes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ty tests

- resolve/many: populate identifier and hint_kind from payload instead of empty strings
- find/page-full: include actual filter dict instead of empty {}
- parity test: add resolve/none route and client cases to verify no extra search hint

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@HumanBean17 HumanBean17 force-pushed the feat/hints-structured branch from 247f560 to 8078578 Compare May 23, 2026 22:30
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@HumanBean17 HumanBean17 merged commit a71e739 into master May 23, 2026
1 check passed
HumanBean17 added a commit that referenced this pull request May 23, 2026
PR-1 (#209) has been merged, implementing machine-parseable
hints_structured field on all MCP outputs.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
HumanBean17 added a commit that referenced this pull request May 23, 2026
…utput (#212)

After HINTS-STRUCTURED landed (#209), generate_hints returns
(list[str], list[_StructuredHint]). Update the proposal to account
for the dual-list pattern: structured hint mapping table, structured
test rows, and updated implementation notes.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@HumanBean17 HumanBean17 deleted the feat/hints-structured branch May 24, 2026 12:08
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