fix(mcp): list tools return rows that say what a record is, not what it says (#4061)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 49s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m31s
CI & Build / Build & push image (push) Successful in 22s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 49s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m31s
CI & Build / Build & push image (push) Successful in 22s
list_tasks returned every row's to_dict(), body included: a project's todo list came to 93-165k characters, past what an MCP client accepts inline, so the list arrived as a file to page through (the #4045 failure, one call over). - notes.brief_row: id, title, type, project, tags, updated_at; for tasks, status, kind, priority, milestone id and title; description, parent and due date only when set. - milestones.titles_for: one query for the milestone titles a page of rows names. - Brief rows on list_tasks, list_notes, get_milestone's steps, get_system and list_system_records. get_task / get_note / get_snippet read a record in full, and each docstring says so. - tests/test_list_rows_brief.py pins the ceiling: 100 rows of ~5k-character step plans stay under 40k characters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
@@ -72,6 +72,26 @@ async def get_milestone(user_id: int, milestone_id: int) -> Milestone | None:
|
||||
return result.scalars().first()
|
||||
|
||||
|
||||
async def titles_for(milestone_ids: set[int]) -> dict[int, str]:
|
||||
"""{id: title} for the given milestones, for rows that name where a record sits.
|
||||
|
||||
No ownership filter, deliberately: the callers are listings whose rows the
|
||||
caller could already read, and a milestone title is part of "where does
|
||||
this record sit". Filtering here would blank the placement of a shared
|
||||
task in someone else's plan while still showing the task.
|
||||
"""
|
||||
ids = {i for i in milestone_ids if i}
|
||||
if not ids:
|
||||
return {}
|
||||
async with async_session() as session:
|
||||
rows = (await session.execute(
|
||||
select(Milestone.id, Milestone.title).where(
|
||||
Milestone.id.in_(ids), Milestone.deleted_at.is_(None),
|
||||
)
|
||||
)).all()
|
||||
return {mid: title for mid, title in rows}
|
||||
|
||||
|
||||
async def get_milestone_in_project(project_id: int, milestone_id: int) -> Milestone | None:
|
||||
"""Fetch a milestone by id within a project, without a user_id ownership check.
|
||||
Callers must verify project access separately before using this."""
|
||||
|
||||
Reference in New Issue
Block a user