Skip to content

hints: Python-style neighbors(['id'],…) copies fail; JSON stringified ids work #195

@HumanBean17

Description

@HumanBean17

Summary

Battle testing showed agents following describe road-sign hints literally and calling neighbors with Python-style list syntax in the ids argument. That fails with Unknown id prefix for \['']`. The same flow works when the agent sends a **JSON-encoded** array string ("[""]"), because FastMCP pre-parses valid JSON before neighbors_v2` runs.

This is primarily a hint-format / agent ergonomics issue, not broken graph data or wrong ids from resolve/describe.

Reproduction

  1. resolve a controller class → get symbol id (40-char SHA1, no prefix).

  2. describe(id=…) → hint like:

    routes via members: neighbors(['<id>'],'out',['DECLARES.EXPOSES'])
    
  3. Agent calls MCP with:

    {"ids": "['<id>']", "direction": "out", "edge_types": ["DECLARES.EXPOSES"]}

    success=false, message: Unknown id prefix for \['']``

  4. Same id with:

    {"ids": "[\"<id>\"]", "direction": "out", "edge_types": ["DECLARES.EXPOSES"]}

    works (FastMCP pre_parse_json → real list[str]).

Direct neighbors_v2('["<id>"]', …) in Python fails (no FastMCP layer); bare id string or list[str] works.

Root cause

Layer Behavior
Hints (mcp_hints.py) Templates use pseudo-Python: neighbors(['{id}'],…) — not valid MCP JSON.
FastMCP (func_metadata.pre_parse_json) For str | list[str] params, json.loads on string values; "['…']" is invalid JSON (single quotes) → skipped → one bogus id string.
mcp_v2._resolve_node_kind Bogus string not in graph → Unknown id prefix.

Template source: TPL_DESCRIBE_TYPE_ROUTES_VIA_MEMBERS and siblings in mcp_hints.py (also find/describe/neighbors success hints).

What works today

{"ids": "<sha1>", "direction": "out", "edge_types": ["DECLARES.EXPOSES"]}
{"ids": ["<sha1>"], "direction": "out", "edge_types": ["DECLARES.EXPOSES"]}
{"ids": "[\"<sha1>\"]", ...}

(via FastMCP pre-parse)

Fix options (pick one or combine)

1. Change hint templates (recommended baseline)

Rewrite mcp_hints.py to JSON/MCP shape, e.g. neighbors(ids="<id>", direction="out", edge_types=["DECLARES.EXPOSES"]).

  • Pros: Fixes what agents copy; no runtime risk.
  • Cons: Many locked templates + tests to update.

2. Document only (docs/AGENT-GUIDE.md, server.py ids description)

State: hints are advisory; use record.id; never "['…']"; ids = string or JSON array.

  • Pros: Fast, no behavior change.
  • Cons: LLMs may still ignore prose.

3. _coerce_ids() in mcp_v2.neighbors_v2 (mirror filter / edge_filter)

json.loads on string; optional unwrap single-element Python bracket form; or teaching error.

  • Pros: Works without FastMCP; better errors.
  • Cons: Heuristics; widens accepted inputs.

4. Fail loud only (no coercion)

Detect ['… / ["… string and return a dedicated message (no silent fix).

  • Pros: Strict contract; small diff.
  • Cons: Still fails until retry.

5. Hybrid (3 + 4)

Valid JSON array strings → list; ['single-id'] → unwrap or teach.

  • Pros: Best battle-test UX.
  • Cons: Tests for edge cases.

6. Align docs with real id shape

README / AGENT-GUIDE: live symbol ids are 40-char hex from graph; sym: examples are misleading.

7. Structured hints (larger)

Optional hints_structured: [{tool, args}] — out of scope for a one-line fix unless we want Shape 2.

8. Shorter hint labels

Drop embedded call syntax; agent uses describe.record.id + schema.

9. Upstream FastMCP

ast.literal_eval fallback when json.loads fails — cross-repo, security review.

10. Host/agent rules only

Cursor rule for ids — not shipped with server.

Suggested combinations

Goal Combo
Minimal durable 1 + 2 + 6
Battle-test resilient 1 + 5
Strict contract 1 + 4

Related code

  • mcp_hints.pyTPL_DESCRIBE_TYPE_*_VIA_MEMBERS, etc.
  • mcp_v2.py_resolve_node_kind, neighbors_v2 (origins = [ids] if isinstance(ids, str) else list(ids))
  • FastMCP: mcp/server/fastmcp/utilities/func_metadata.pypre_parse_json (Claude “JSON in a string” workaround)

Do not rely on

FastMCP pre-parse alone — it fixes "[\"…\"]" but not "['…']", and direct Python/tests bypass it.


Labels suggestion: bug, hints, mcp, dx

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions