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
-
resolve a controller class → get symbol id (40-char SHA1, no prefix).
-
describe(id=…) → hint like:
routes via members: neighbors(['<id>'],'out',['DECLARES.EXPOSES'])
-
Agent calls MCP with:
{"ids": "['<id>']", "direction": "out", "edge_types": ["DECLARES.EXPOSES"]}
→ success=false, message: Unknown id prefix for \['']``
-
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.py — TPL_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.py — pre_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
Summary
Battle testing showed agents following
describeroad-sign hints literally and callingneighborswith Python-style list syntax in theidsargument. That fails withUnknown id prefix for \['']`. The same flow works when the agent sends a **JSON-encoded** array string ("[""]"), because FastMCP pre-parses valid JSON beforeneighbors_v2` runs.This is primarily a hint-format / agent ergonomics issue, not broken graph data or wrong ids from
resolve/describe.Reproduction
resolvea controller class → get symbol id (40-char SHA1, no prefix).describe(id=…)→ hint like:Agent calls MCP with:
{"ids": "['<id>']", "direction": "out", "edge_types": ["DECLARES.EXPOSES"]}→
success=false,message:Unknown id prefix for \['']``Same id with:
{"ids": "[\"<id>\"]", "direction": "out", "edge_types": ["DECLARES.EXPOSES"]}→ works (FastMCP
pre_parse_json→ reallist[str]).Direct
neighbors_v2('["<id>"]', …)in Python fails (no FastMCP layer); bare id string orlist[str]works.Root cause
mcp_hints.py)neighbors(['{id}'],…)— not valid MCP JSON.func_metadata.pre_parse_json)str | list[str]params,json.loadson string values;"['…']"is invalid JSON (single quotes) → skipped → one bogus id string.mcp_v2._resolve_node_kindUnknown id prefix.Template source:
TPL_DESCRIBE_TYPE_ROUTES_VIA_MEMBERSand siblings inmcp_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.pyto JSON/MCP shape, e.g.neighbors(ids="<id>", direction="out", edge_types=["DECLARES.EXPOSES"]).2. Document only (
docs/AGENT-GUIDE.md,server.pyidsdescription)State: hints are advisory; use
record.id; never"['…']";ids= string or JSON array.3.
_coerce_ids()inmcp_v2.neighbors_v2(mirrorfilter/edge_filter)json.loadson string; optional unwrap single-element Python bracket form; or teaching error.4. Fail loud only (no coercion)
Detect
['…/["…string and return a dedicatedmessage(no silent fix).5. Hybrid (3 + 4)
Valid JSON array strings → list;
['single-id']→ unwrap or teach.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_evalfallback whenjson.loadsfails — cross-repo, security review.10. Host/agent rules only
Cursor rule for
ids— not shipped with server.Suggested combinations
Related code
mcp_hints.py—TPL_DESCRIBE_TYPE_*_VIA_MEMBERS, etc.mcp_v2.py—_resolve_node_kind,neighbors_v2(origins = [ids] if isinstance(ids, str) else list(ids))mcp/server/fastmcp/utilities/func_metadata.py—pre_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