Skip to content

docs(mcp): disclose what read_chat/list_chats drop and point at the raw JSONL (#615) - #625

Merged
edspencer merged 1 commit into
mainfrom
fix/615-mcp-read-tool-descriptions
Aug 2, 2026
Merged

docs(mcp): disclose what read_chat/list_chats drop and point at the raw JSONL (#615)#625
edspencer merged 1 commit into
mainfrom
fix/615-mcp-read-tool-descriptions

Conversation

@edspencer

Copy link
Copy Markdown
Owner

Closes #615.

The self-MCP read tools describe what they return but not what they silently discard. So callers reach for read_chat to audit a chat and trust what comes back — which is mostly blank.

The gap

read_chat renders every tool call as a role: "tool" entry with empty text — no name, no input, no output — and those blanks still spend the caller's limit. On a real agentic transcript they are the majority of the payload. Thinking blocks, attachments and sub-agent transcripts are dropped outright. None of that was written down anywhere: not in the tool description, not in the public reference page (which listed tool as a valid role without mentioning those entries are empty).

Observed downstream on the nightly Night-Watch reviewer:

  • It reported "no API errors, crashes, stalls or loops" for a window in which the chats it read held 31 is_error tool results — precisely the class of record this path deletes.
  • It mistyped one session id, got total: 0 and no error, and published "1 empty child-worktree chat" about a 1.5 MB conversation it had never opened.

This is also a repeat request. Ed asked for exactly this on 2026-07-21: "ideally they would also prompt you in such a way that it tells you, 'Hey, you can use these, but also here is how you go find the actual real chats.'" A day later it became a standing manual instruction: "don't use the read chat MCP tool. It's not very good." That instruction lived nowhere in the product, so every new agent had to be told by hand — and the ones that weren't, like Night-Watch, silently did the wrong thing.

Changes

READ_CHAT_DESC — states the blank-tool behaviour and that it consumes limit; names what else is dropped; scopes the tool to "what is this chat about / what was decided" and explicitly not "how did this chat go"; gives the transcript path (<paddock-data-dir>/projects/<slug>/.chats/<sessionId>.jsonl, sub-agents under <sessionId>/subagents/agent-*.jsonl); warns that an unknown session_id means NOT FOUND rather than "empty chat".

LIST_CHATS_DESC — says name degrades to an 8-character sessionId prefix, that this means untitled, and that it is not a usable id.

Public reference page (website/src/content/docs/reference/self-mcp.md) — mirrors both, since it had the identical gap.

Test — pins the disclosures so a future rewrite can't quietly drop them.

Verification

  • self-mcp.test.ts 80 passed, mcp-transport.test.ts 10 passed
  • Full server suite: 1440 passed / 115 files
  • npm run typecheck: clean (server + web)
  • Docs site npm run build: clean, 47 pages; confirmed the callout renders as a Starlight caution block and no literal ::: leaked into the HTML
  • Confirmed the new test fails without the description change (AssertionError: expected 'Read a trimmed tail of a chat's tran…' to match /empty/i) rather than passing vacuously

Changeset included (@paddock/server patch) — it's docs-adjacent, but these strings ship into every agent's context, so it seemed worth a release note. Happy to drop it if you'd rather treat it as docs-only.

Scope notes

Description-only; no behaviour change. Two things I deliberately left out:

Companions: #613 (the underlying fidelity defect — toolCall is discarded in management-ops.ts:218-222) and #614 (naming). This PR just makes today's behaviour honest in the meantime; if #613 lands, the first paragraph of READ_CHAT_DESC should be revisited.

…aw JSONL (#615)

The self-MCP read tools describe what they return but not what they
silently discard, so callers use `read_chat` for analysis it cannot
support and trust the result.

`read_chat` renders every tool call as a `role: "tool"` entry with EMPTY
text — no name, no input, no output — and those blanks still spend the
caller's `limit`. On a real agentic transcript they are the majority of
the payload. Thinking blocks, attachments and sub-agent transcripts are
dropped outright. None of that was written down anywhere, in the tool
description or the public reference.

Consequences observed on the nightly Night-Watch reviewer: it reported
"no API errors, crashes, stalls or loops" for a window in which the
chats it read held 31 is_error tool results, because that is precisely
the class of record this path deletes. It also mistyped one session id,
got `total: 0` and no error back, and published "1 empty child-worktree
chat" about a 1.5 MB conversation it never opened.

- `READ_CHAT_DESC`: state the blank-tool behaviour and that it consumes
  `limit`; name what else is dropped; say the tool answers "what is this
  chat about / what was decided" and NOT "how did this chat go"; give
  the transcript path (incl. sub-agents) for the lossless view; warn
  that an unknown session_id means NOT FOUND, not "empty chat".
- `LIST_CHATS_DESC`: say `name` degrades to an 8-char sessionId prefix,
  that this means untitled, and that it is not a usable id.
- Mirror both in the public reference page, which had the same gap — it
  listed `tool` as a role without saying those entries are empty.
- Pin the disclosures with a unit test; verified it fails without the
  description change rather than passing vacuously.

Descriptions and docs only — no behaviour change. The underlying
fidelity defect is #613 and the naming defect is #614; this makes the
current behaviour honest in the meantime.

Co-Authored-By: Claude <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying paddock with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5d59081
Status: ✅  Deploy successful!
Preview URL: https://b37c1a9a.paddock-7u2.pages.dev
Branch Preview URL: https://fix-615-mcp-read-tool-descri.paddock-7u2.pages.dev

View logs

@edspencer

Copy link
Copy Markdown
Owner Author

⚠️ Collides with #580 (fix(server): make the self-MCP surface address the root workspace), which is open and touches the same three files: self-mcp-descriptions.ts, test/unit/self-mcp.test.ts, website/src/content/docs/reference/self-mcp.md.

The overlap is textual, in two hunks — whichever of us merges second will need to resolve it:

No opinion on merge order from me; they're independent and both small. Flagging it so whoever hits the conflict knows the intended end state rather than picking a side.

@edspencer

Copy link
Copy Markdown
Owner Author

Correction — no conflict after all. #580 merged as a8c37d8, and I said above that READ_CHAT_DESC would collide on the same line and need manual resolution. That was wrong: I predicted it from reading the two diffs instead of computing the merge.

Actually computing it (git merge-tree --write-tree origin/main <this branch>) gives a clean auto-merge that lands on exactly the intended end state, with no help needed:

GitHub agrees: this PR still reads MERGEABLE / CLEAN against the new main. Sorry for the noise — nothing to do here.

@edspencer
edspencer merged commit a18ceb2 into main Aug 2, 2026
5 checks passed
@edspencer
edspencer deleted the fix/615-mcp-read-tool-descriptions branch August 2, 2026 18:29
@github-actions github-actions Bot mentioned this pull request Aug 2, 2026
edspencer added a commit that referenced this pull request Aug 2, 2026
…rt (#633)

* chore(docs): start v0.54/v0.55 docs pass

* docs(website): What's New entries for 0.54 and 0.55

0.55 leads on importing existing Claude Code CLI chats (#621) — the
release's headline — plus the health-path auth fix (#579, which carries
an operator action), the two measured memory fixes (#602), the
sub-agent bar rehydration fix (#623) and the self-MCP honesty and
root-workspace work (#625, #580).

0.54 leads on retiring the 'keeper' persona for Claude (#598), with the
breaking env/config rename in a table, and Home leading with running
then unread chats (#607) — noting that this is what fixed the in-flight
badge gap the 0.53 entry describes. Also the foreground sub-agent
duplication fix (#595), the live sub-agent bar (#605) and the sweeper
working-directory fix (#581).

Frontmatter description and the editorial arc paragraph updated: the new
arc is Paddock ceasing to be a walled garden.

* docs(website): a recorded demo of chat import, plus two screenshots

Converts whats-new.md to .mdx so it can use the repo's own DemoVideo
component, which is built for exactly this: an MP4 configured to behave
like a GIF (muted/autoplay/loop) but with controls, a poster frame and
prefers-reduced-motion handling. The component's header documents the
measurement behind that choice; here it is 274 KB of MP4 against 2.0 MB
for the equivalent GIF, and the GIF loses the small UI text to dithering.

The clip shows the 0.55 headline end to end: a newly added project with
no chats, an 'Import 7 native chats' button, and seven terminal sessions
arriving with their Imported badges and their ORIGINAL dates — which is
the claim in the prose, now visible.

Two stills: the root Home leading with Running then Unread across every
project (0.54), and an imported chat open with the composer reading
'Message Claude…' (0.54's rename).

Shot against a disposable rig holding a scrubbed copy of real data, so
the volume and timestamps are genuine. Every chat title on camera was
replaced with a fictional equivalent first, and the rig's data-directory
footer is hidden before each frame. The page URL is unchanged: Starlight
routes .md and .mdx to the same slug.

* docs: runbook — recording video, and building a rig from a copy of production

New material from the 0.55 pass:

- The video-production harness already exists in video/ (#584). Reuse it;
  a new film is a directory under videos/, not a fork of lib/.
- Ship MP4 through the repo's DemoVideo component rather than a GIF —
  274 KB vs 2.0 MB on this clip, and the GIF loses small UI text. Records
  the two mechanical consequences: the page must be .mdx, and MDX rejects
  a JSX block placed directly after a list item.
- Where media actually goes: video in public/demo/, stills in
  src/assets/whats-new/. A video in src/assets/ does not work.
- Seeding a rig from a COPY of production, with the rsync excludes and the
  sidecar list, and the finding that CLAUDE_HOME does work — an older
  launcher's comment claiming otherwise is stale.
- Scan the copy for secrets BEFORE recording: this pass found 16 files
  with live tokens, including the working session's own transcript. Both
  traps written down — grep -E with escaped braces silently matches
  nothing, and cat-ing a rig launcher copies a token into your transcript.
- Fictionalise chat titles via PATCH before shooting rather than leaving
  the judgement to the reviewer.
- What chat-import detection actually requires, traced from source.
- Serve the built site under pm for review; Astro's dev server 403s the
  dev subdomain.

* docs: genericise the domain in the runbook's leak-check example

The example pattern named this box's private dev domain — the exact
mistake the paragraph above it warns about.

* docs(website): drop the What's New preamble

Four paragraphs and an aside of throat-clearing before any actual
release note. The page is a reverse-chronological list of what changed;
it now starts with the newest entry.

Removes the 'Reading older entries' aside along with it, since it sat
inside the deleted range.

---------

Co-authored-by: HomeLab Agent <homelab-infra@valfenda.net>
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.

read_chat/list_chats descriptions don't disclose what they drop or point at the raw JSONL — agents keep using read_chat for analysis it can't support

1 participant