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:
@@ -7,6 +7,7 @@ from sqlalchemy import func, or_, select, text
|
||||
|
||||
from scribe.models import async_session
|
||||
from scribe.models.note import Note, TaskKind, TaskPriority, TaskStatus
|
||||
from scribe.models.base import iso
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -1057,3 +1058,40 @@ async def get_note_for_user(
|
||||
async with async_session() as session:
|
||||
note = await session.get(Note, note_id)
|
||||
return (note, perm) if note else None
|
||||
|
||||
|
||||
# What a LISTING row carries (#4061): what the record is and where it sits,
|
||||
# never what it says. list_tasks once returned every row's to_dict(), body
|
||||
# included, and 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
|
||||
# same failure #4045 fixed for enter_project, one call over. get_task / get_note
|
||||
# read a record in full; a list is for choosing which one to open.
|
||||
#
|
||||
# A field most rows leave empty (a one-line description, a parent, a due date)
|
||||
# is attached only when set: a hundred rows of `null` are a hundred chances to
|
||||
# learn to skip the key (#2483), and the bytes are the thing being cut.
|
||||
def brief_row(note: Note, milestone_titles: dict[int, str] | None = None) -> dict:
|
||||
row = {
|
||||
"id": note.id,
|
||||
"title": note.title,
|
||||
"note_type": note.note_type or "note",
|
||||
"project_id": note.project_id,
|
||||
"tags": note.tags or [],
|
||||
"updated_at": iso(note.updated_at),
|
||||
}
|
||||
if note.description:
|
||||
row["description"] = note.description
|
||||
if note.is_task:
|
||||
row.update({
|
||||
"status": note.status,
|
||||
"task_kind": note.task_kind,
|
||||
"priority": note.priority,
|
||||
"milestone_id": note.milestone_id,
|
||||
})
|
||||
if milestone_titles is not None and note.milestone_id:
|
||||
row["milestone_title"] = milestone_titles.get(note.milestone_id)
|
||||
if note.parent_id:
|
||||
row["parent_id"] = note.parent_id
|
||||
if note.due_date:
|
||||
row["due_date"] = iso(note.due_date)
|
||||
return row
|
||||
|
||||
Reference in New Issue
Block a user