feat(deck): compact card/comment retrieval (summaries, filters, board overview)
Deck read tools returned too many tokens to be usable as boards grow — even deck_get_stacks(description_max_length=1) exceeded the MCP token limit because every card was fully serialized in list views. - Add compact projection models (DeckCardSummary, DeckCommentSummary, StackOverview, BoardOverviewResponse) and a uniform detail="summary"|"full" knob (summary default) on deck_get_cards / get_stacks / get_stack / get_archived_stacks. - Add pre-serialization filtering: status (open/done/archived/all), label, assigned_to. - Add deck_get_board_overview: board title + label legend + stacks with compact card rows + counts in a single call. - Compact comments: detail / message_max_length / newest-first order on deck_get_card_comments. - Docs + unit/integration tests. BREAKING CHANGE: deck list tools now default to detail="summary" and status="open". The include_archived_cards parameter is replaced by status (use status="all" to include archived cards); pass detail="full" to restore the previous per-card shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c662d57c4f
commit
b11103064c
@@ -353,3 +353,71 @@ async def test_deck_card_comment_message_too_long_mcp(
|
||||
{"card_id": card_id, "message": too_long},
|
||||
)
|
||||
assert result.isError is True, "Expected validation error for >1000 char message"
|
||||
|
||||
|
||||
# Compact retrieval (summary projection + board overview)
|
||||
|
||||
|
||||
async def test_deck_get_stacks_summary_default_omits_full_card_fields_mcp(
|
||||
nc_mcp_client: ClientSession, temporary_board_with_card: tuple
|
||||
):
|
||||
"""deck_get_stacks defaults to compact summaries: the card row carries
|
||||
title/labels but not the heavy full-card fields (owner/type/etag)."""
|
||||
board_data, stack_data, card_data = temporary_board_with_card
|
||||
board_id = board_data["id"]
|
||||
|
||||
result = await nc_mcp_client.call_tool("deck_get_stacks", {"board_id": board_id})
|
||||
assert result.isError is False, f"deck_get_stacks failed: {result.content}"
|
||||
payload = json.loads(result.content[0].text)
|
||||
|
||||
cards = [c for stack in payload["stacks"] for c in (stack.get("cards") or [])]
|
||||
card = next(c for c in cards if c["id"] == card_data["id"])
|
||||
# Summary fields present...
|
||||
assert card["title"] == card_data["title"]
|
||||
assert "hasDescription" in card
|
||||
assert "labels" in card and isinstance(card["labels"], list)
|
||||
# ...heavy full-card fields absent.
|
||||
assert "owner" not in card
|
||||
assert "type" not in card
|
||||
|
||||
|
||||
async def test_deck_get_stacks_detail_full_keeps_card_fields_mcp(
|
||||
nc_mcp_client: ClientSession, temporary_board_with_card: tuple
|
||||
):
|
||||
"""detail="full" restores the complete card objects (owner/type present)."""
|
||||
board_data, _, card_data = temporary_board_with_card
|
||||
board_id = board_data["id"]
|
||||
|
||||
result = await nc_mcp_client.call_tool(
|
||||
"deck_get_stacks", {"board_id": board_id, "detail": "full"}
|
||||
)
|
||||
assert result.isError is False, f"deck_get_stacks(full) failed: {result.content}"
|
||||
payload = json.loads(result.content[0].text)
|
||||
|
||||
cards = [c for stack in payload["stacks"] for c in (stack.get("cards") or [])]
|
||||
card = next(c for c in cards if c["id"] == card_data["id"])
|
||||
assert "owner" in card
|
||||
assert "type" in card
|
||||
|
||||
|
||||
async def test_deck_get_board_overview_mcp(
|
||||
nc_mcp_client: ClientSession, temporary_board_with_card: tuple
|
||||
):
|
||||
"""deck_get_board_overview returns board title + stacks with compact rows."""
|
||||
board_data, stack_data, card_data = temporary_board_with_card
|
||||
board_id = board_data["id"]
|
||||
|
||||
result = await nc_mcp_client.call_tool(
|
||||
"deck_get_board_overview", {"board_id": board_id}
|
||||
)
|
||||
assert result.isError is False, f"board overview failed: {result.content}"
|
||||
payload = json.loads(result.content[0].text)
|
||||
|
||||
assert payload["board_id"] == board_id
|
||||
assert payload["title"] == board_data["title"]
|
||||
assert payload["total_cards"] >= 1
|
||||
|
||||
stack = next(s for s in payload["stacks"] if s["id"] == stack_data["id"])
|
||||
assert stack["card_count"] == len(stack["cards"])
|
||||
card_ids = [c["id"] for c in stack["cards"]]
|
||||
assert card_data["id"] in card_ids
|
||||
|
||||
Reference in New Issue
Block a user