Skip to content

fix: prompt Message wrapping + add detail_level=minimal to get_architecture_overview#483

Open
selika wants to merge 1 commit into
tirth8205:mainfrom
selika:fix/fastmcp-prompt-and-overview-minimal
Open

fix: prompt Message wrapping + add detail_level=minimal to get_architecture_overview#483
selika wants to merge 1 commit into
tirth8205:mainfrom
selika:fix/fastmcp-prompt-and-overview-minimal

Conversation

@selika
Copy link
Copy Markdown

@selika selika commented May 15, 2026

Summary

Two independent fixes, both surfaced from the same MCP slash-command failure (/code-review-graph:architecture_map):

1. fastmcp ≥3.2 prompt Message wrapping (regression fix)

fastmcp 3.2.4 stopped accepting raw dict messages in prompt return values:

McpError: messages[0] must be Message or str, got dict.
Use Message({'role': 'user', 'content': '...'}) to wrap the value.

This breaks all 5 MCP prompt templates (review_changes, architecture_map, debug_issue, onboard_developer, pre_merge_check) since they return list[dict].

Fix: wrap each message via fastmcp.prompts.prompt.Message so role is explicit and future multi-turn prompts compose naturally. Added a tiny _user() helper to keep the five templates uniform.

2. get_architecture_overview detail_level="minimal" (token-efficiency)

The default output embeds every community's full members list and every individual cross-community edge. On a 2,878-node repo (ebm-copilot — TS/Python/Bash/R, 390 files) this measured 655KB — over the MCP tool output token cap, forcing fallback-to-file and defeating the whole point of the token-efficient API.

The token-efficiency preamble already tells the LLM to call get_architecture_overview(detail_level="minimal") first, but minimal wasn't actually a supported value — so this PR finally makes the prompt's own contract work.

minimal mode:

  • communities → keep only {id, name, size, cohesion, dominant_language}, drop members
  • cross_community_edges → aggregate by (source_community, target_community) pair → {source_community (name), target_community (name), edge_count, top_kinds}
  • warnings → unchanged

Measured on ebm-copilot:

mode size reduction
standard 655 KB
minimal 5.4 KB 120×

Output stays useful — the top pair v1-route ↔ services-list (318 CALLS edges) immediately surfaces the hottest coupling.

Test plan

  • uv run pytest tests/test_prompts.py — 34 passed (updated to assert msg.role / msg.content.text)
  • uv run pytest tests/test_tools.py — 3 new cases for minimal mode (drops members, aggregates pairs, summary label)
  • uv run pytest tests/test_integration_v2.py — step 9 updated for Message attribute access
  • Full suite: 1233 passed, 1 skipped, 2 xpassed. The 6 remaining failures are pre-existing pytest-asyncio plugin issues in tests/test_main.py::TestApplyToolFilter (the test file uses @pytest.mark.asyncio but pytest-asyncio is not a dev dep — out of scope here).
  • uvx ruff check on changed files — clean.
  • End-to-end: architecture_map prompt now renders through fastmcp's in-process Client (verified the exact MCP path the slash command uses).
  • Smoke test on real ebm-copilot graph confirms the 120× reduction.

🤖 Generated with Claude Code

…ecture_overview

Two independent fixes bundled because both surfaced from the same MCP
slash-command failure ('/code-review-graph:architecture_map').

1. fastmcp >=3.2 rejects raw dicts in prompt return values:
   "messages[0] must be Message or str, got dict". Wrap all five prompt
   templates via fastmcp.prompts.prompt.Message so the slash commands
   render again. Tests updated to assert attribute access
   (msg.role / msg.content.text).

2. get_architecture_overview gains detail_level="minimal". Default
   "standard" output embeds every community's member list plus every
   individual cross-community edge — measured at 655KB on a 2,878-node
   repo (ebm-copilot), exceeding the MCP tool output token cap and
   forcing fallback-to-file. Minimal mode drops member lists and
   aggregates edges to one row per (source_community, target_community)
   with edge_count + top edge kinds; same repo: 5.4KB (120x reduction)
   while keeping the data needed to spot coupling smells.

Quality:
- 1233 tests pass; the 6 remaining failures are pre-existing
  pytest-asyncio plugin issues in tests/test_main.py (unrelated).
- ruff: All checks passed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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