Summary
Drop the hints: list[str] field from all five MCP tool outputs and add a reason: str field to StructuredHint instead. This consolidates two parallel hint systems into one, reducing surface area and maintenance burden while preserving all information.
Motivation
The hints_structured field was added in #209 as a machine-parseable companion to the original string hints. Both are generated in mcp_hints.py, consumed by the same LLM clients, and produce largely the same information in different formats. This is exactly the kind of redundant surface area the project principles warn against:
- "Don't widen the public surface 'just in case'"
- "Breaking changes are always allowed"
String hints carry advisory context (e.g. "no match — try search(query='Foo')") that hints_structured currently loses when actionable=false. Rather than maintain two fields, we can capture that context directly in structured hints.
Example
Actionable hint (describe finds no match)
Before (two fields):
{
"results": [],
"hints": [
"no match — try search(query='BankAccount') for ranked fuzzy lookup",
"try find(role='service') to browse by role"
],
"hints_structured": [
{"tool": "search", "args": {"query": "BankAccount"}, "actionable": True},
{"tool": "find", "args": {"role": "service"}, "actionable": True}
]
}
After (single field):
{
"results": [],
"hints_structured": [
{
"tool": "search",
"args": {"query": "BankAccount"},
"actionable": True,
"reason": "no match — try ranked fuzzy lookup"
},
{
"tool": "find",
"args": {"role": "service"},
"actionable": True,
"reason": "browse by role to discover related symbols"
}
]
}
Non-actionable hint (advisory)
Before:
{
"hints": ["results look weak — narrow the query or try find(role=…)"],
"hints_structured": [
{"tool": "find", "args": {}, "actionable": False}
]
}
After:
{
"hints_structured": [
{
"tool": "find",
"args": {"role": "service"},
"actionable": False,
"reason": "results look weak — narrow the query or try find with a role filter"
}
]
}
Proposed change
Remove:
hints: list[str] from all five output models in mcp_v2.py
- String hint generation logic in
mcp_hints.py
MCP_HINTS_FIELD_DESCRIPTION constant
- Parity test (
test_structured_hints_parity_with_string_hints)
Add:
reason: str field to StructuredHint — carries the advisory text (e.g. "no match for this symbol", "results look weak — narrow the query")
- Example:
StructuredHint(tool="search", args={"query": "Foo"}, actionable=True, reason="no match — try ranked fuzzy lookup")
Update:
MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION to reflect it's now the sole hint mechanism
- All hint templates in
mcp_hints.py to emit reason alongside tool/args
- Docs (
README.md, docs/AGENT-GUIDE.md, AGENTS.md) to remove references to hints field
- Tests to assert on
reason content instead of string hints
Scope
mcp_hints.py — generation logic
mcp_v2.py — output models and tool handlers
tests/test_mcp_hints.py — test updates
README.md, docs/AGENT-GUIDE.md — doc updates
propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md, propose/completed/HINTS-STRUCTURED.md — historical reference only, no changes needed
No reindex required — this is an output-only change, no graph or index schema impact.
Summary
Drop the
hints: list[str]field from all five MCP tool outputs and add areason: strfield toStructuredHintinstead. This consolidates two parallel hint systems into one, reducing surface area and maintenance burden while preserving all information.Motivation
The
hints_structuredfield was added in #209 as a machine-parseable companion to the original stringhints. Both are generated inmcp_hints.py, consumed by the same LLM clients, and produce largely the same information in different formats. This is exactly the kind of redundant surface area the project principles warn against:String
hintscarry advisory context (e.g. "no match — try search(query='Foo')") thathints_structuredcurrently loses whenactionable=false. Rather than maintain two fields, we can capture that context directly in structured hints.Example
Actionable hint (describe finds no match)
Before (two fields):
{ "results": [], "hints": [ "no match — try search(query='BankAccount') for ranked fuzzy lookup", "try find(role='service') to browse by role" ], "hints_structured": [ {"tool": "search", "args": {"query": "BankAccount"}, "actionable": True}, {"tool": "find", "args": {"role": "service"}, "actionable": True} ] }After (single field):
{ "results": [], "hints_structured": [ { "tool": "search", "args": {"query": "BankAccount"}, "actionable": True, "reason": "no match — try ranked fuzzy lookup" }, { "tool": "find", "args": {"role": "service"}, "actionable": True, "reason": "browse by role to discover related symbols" } ] }Non-actionable hint (advisory)
Before:
{ "hints": ["results look weak — narrow the query or try find(role=…)"], "hints_structured": [ {"tool": "find", "args": {}, "actionable": False} ] }After:
{ "hints_structured": [ { "tool": "find", "args": {"role": "service"}, "actionable": False, "reason": "results look weak — narrow the query or try find with a role filter" } ] }Proposed change
Remove:
hints: list[str]from all five output models inmcp_v2.pymcp_hints.pyMCP_HINTS_FIELD_DESCRIPTIONconstanttest_structured_hints_parity_with_string_hints)Add:
reason: strfield toStructuredHint— carries the advisory text (e.g. "no match for this symbol", "results look weak — narrow the query")StructuredHint(tool="search", args={"query": "Foo"}, actionable=True, reason="no match — try ranked fuzzy lookup")Update:
MCP_HINTS_STRUCTURED_FIELD_DESCRIPTIONto reflect it's now the sole hint mechanismmcp_hints.pyto emitreasonalongside tool/argsREADME.md,docs/AGENT-GUIDE.md,AGENTS.md) to remove references tohintsfieldreasoncontent instead of string hintsScope
mcp_hints.py— generation logicmcp_v2.py— output models and tool handlerstests/test_mcp_hints.py— test updatesREADME.md,docs/AGENT-GUIDE.md— doc updatespropose/completed/HINTS-ROAD-SIGNS-PROPOSE.md,propose/completed/HINTS-STRUCTURED.md— historical reference only, no changes neededNo reindex required — this is an output-only change, no graph or index schema impact.