fix: wrap raw list returns in response models to produce single TextContent block

MCP tools returning raw lists caused FastMCP's _convert_to_content() to create
one TextContent block per element. Most MCP clients only read content[0], so
they saw a single result instead of the full list.

Wrapped 9 tool functions in proper response objects:
- deck: deck_get_boards, deck_get_stacks, deck_get_cards, deck_get_labels
- calendar: nc_calendar_list_events, nc_calendar_get_upcoming_events
- contacts: nc_contacts_list_addressbooks, nc_contacts_list_contacts
- tables: nc_tables_list_tables

Closes #568

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-02-20 09:22:16 +01:00
co-authored by Claude Opus 4.6
parent 39d160ce48
commit 8887aa241a
7 changed files with 141 additions and 55 deletions
@@ -78,8 +78,10 @@ async def test_deck_board_view_permissions(
if not result.isError:
response_data = json.loads(result.content[0].text)
# The response is directly a list of boards
if not isinstance(response_data, list):
# Response is a ListBoardsResponse with a "boards" field
if isinstance(response_data, dict) and "boards" in response_data:
response_data = response_data["boards"]
elif not isinstance(response_data, list):
response_data = [response_data] if response_data else []
board_ids = [b["id"] for b in response_data]
logger.info(f"Bob can see {len(response_data)} boards: {board_ids}")
@@ -98,8 +100,10 @@ async def test_deck_board_view_permissions(
if not result.isError:
response_data = json.loads(result.content[0].text)
# The response is directly a list of boards
if not isinstance(response_data, list):
# Response is a ListBoardsResponse with a "boards" field
if isinstance(response_data, dict) and "boards" in response_data:
response_data = response_data["boards"]
elif not isinstance(response_data, list):
response_data = [response_data] if response_data else []
board_ids = [b["id"] for b in response_data]
logger.info(f"Diana can see {len(response_data)} boards")
@@ -313,8 +317,10 @@ async def test_deck_user_isolation(nc_client, alice_mcp_client, bob_mcp_client):
if not result.isError:
response_data = json.loads(result.content[0].text)
# The response is directly a list of boards
if not isinstance(response_data, list):
# Response is a ListBoardsResponse with a "boards" field
if isinstance(response_data, dict) and "boards" in response_data:
response_data = response_data["boards"]
elif not isinstance(response_data, list):
response_data = [response_data] if response_data else []
board_ids = [b["id"] for b in response_data]
logger.info(f"Alice can see boards: {board_ids}")
@@ -332,8 +338,10 @@ async def test_deck_user_isolation(nc_client, alice_mcp_client, bob_mcp_client):
if not result.isError:
response_data = json.loads(result.content[0].text)
# The response is directly a list of boards
if not isinstance(response_data, list):
# Response is a ListBoardsResponse with a "boards" field
if isinstance(response_data, dict) and "boards" in response_data:
response_data = response_data["boards"]
elif not isinstance(response_data, list):
response_data = [response_data] if response_data else []
board_ids = [b["id"] for b in response_data]
logger.info(f"Bob can see boards: {board_ids}")