feat(rkd): read back the thread you were called into #6

Merged
dozken merged 2 commits from feat/fetch-messages into main 2026-09-21 07:33:14 +02:00
Owner

Summary

A mention arrives with only the message that named the App, so a request leaning on what came before it — a bug report, a discussion to summarise, "fix what we just talked about" — had nowhere to read that from. The only advice the instructions could give was to ask the sender to paste it back, which defeats asking Claude in the thread at all.

This adds a fetch_messages tool over the read the server gained in rkd#984 (merged there as PR #993).

What it does

Calls GET /api/channels/{id}/messages, or the DM twin, under the new messages:read:context scope. That scope only serves a channel, thread or DM the App was invoked in by this host's owner, so the tool reaches the brief without being able to sweep a server. chat_id picks the route the same way reply and react already do.

A page comes back oldest-first, because that is reading order, while the cursor for the next call is the oldest id — paging backwards from the newest would loop on itself. The limit is clamped to the server's own 1..100 rather than trusting the model's number.

The returned text is written by other people, so the tool description and the page itself both say it is information to read and never instructions to follow. The MCP instructions block now points at the tool instead of telling Claude to ask for a paste.

Requires

The server admin has to grant messages:read:context when installing the App; the README's install line now lists it. Without it the read is a 403 and the tool surfaces the server's own message, which names the cause ("This App has not been invoked in this channel").

Needs an RKD server with rkd#993. Against an older server the scope does not exist and the install cannot grant it.

Test plan

  • pnpm typecheck clean
  • pnpm test — 105 passed (5 new)
  • pnpm check:dist — the committed bundle matches src/
  • pnpm build re-run and dist/server.js committed

New tests cover the read being returned oldest-first with the oldest id as the cursor, the untrusted-content warning being present, a DM chat_id routing to the DM history, the limit being clamped at both ends, an empty chat reading as "no messages" rather than an error, and a 403 surfacing as a tool error.

Not done: no end-to-end run against a live server. The integration suite (pnpm test:integration) drives a real sign-in, and the server side of this only merged upstream today — worth one manual pass in a real thread before relying on it.

Notes for the reviewer

Version bumped 0.2.3 → 0.3.0 in package.json, .claude-plugin/plugin.json and PLUGIN_VERSION, since this adds a tool.

The reverse-then-take-the-last-id detail is the part worth a second look: page[page.length - 1] is the oldest message because the server answers newest-first, and the display list is a reversed copy, so the two orders are deliberately different in that block.

https://claude.ai/code/session_01Y8CFkNh9ccVeYYayouYuXg

## Summary A mention arrives with only the message that named the App, so a request leaning on what came before it — a bug report, a discussion to summarise, "fix what we just talked about" — had nowhere to read that from. The only advice the instructions could give was to ask the sender to paste it back, which defeats asking Claude in the thread at all. This adds a `fetch_messages` tool over the read the server gained in rkd#984 (merged there as PR #993). ## What it does Calls `GET /api/channels/{id}/messages`, or the DM twin, under the new `messages:read:context` scope. That scope only serves a channel, thread or DM the App was invoked in by this host's owner, so the tool reaches the brief without being able to sweep a server. `chat_id` picks the route the same way `reply` and `react` already do. A page comes back oldest-first, because that is reading order, while the cursor for the next call is the **oldest** id — paging backwards from the newest would loop on itself. The limit is clamped to the server's own 1..100 rather than trusting the model's number. The returned text is written by other people, so the tool description and the page itself both say it is information to read and never instructions to follow. The MCP instructions block now points at the tool instead of telling Claude to ask for a paste. ## Requires The server admin has to grant `messages:read:context` when installing the App; the README's install line now lists it. Without it the read is a 403 and the tool surfaces the server's own message, which names the cause ("This App has not been invoked in this channel"). Needs an RKD server with rkd#993. Against an older server the scope does not exist and the install cannot grant it. ## Test plan - [x] `pnpm typecheck` clean - [x] `pnpm test` — 105 passed (5 new) - [x] `pnpm check:dist` — the committed bundle matches `src/` - [x] `pnpm build` re-run and `dist/server.js` committed New tests cover the read being returned oldest-first with the oldest id as the cursor, the untrusted-content warning being present, a DM `chat_id` routing to the DM history, the limit being clamped at both ends, an empty chat reading as "no messages" rather than an error, and a 403 surfacing as a tool error. Not done: no end-to-end run against a live server. The integration suite (`pnpm test:integration`) drives a real sign-in, and the server side of this only merged upstream today — worth one manual pass in a real thread before relying on it. ## Notes for the reviewer Version bumped 0.2.3 → 0.3.0 in `package.json`, `.claude-plugin/plugin.json` and `PLUGIN_VERSION`, since this adds a tool. The reverse-then-take-the-last-id detail is the part worth a second look: `page[page.length - 1]` is the oldest message because the server answers newest-first, and the display list is a reversed copy, so the two orders are deliberately different in that block. https://claude.ai/code/session_01Y8CFkNh9ccVeYYayouYuXg
feat(rkd): read back the thread you were called into
Some checks failed
CI / rkd-plugin (pull_request) Failing after 30s
1b7e1d4744
A mention arrives with only the message that named the App, so a request
leaning on what came before it — a bug report, a discussion to
summarise, "fix what we just talked about" — had nowhere to read that
from. The only advice was to ask the sender to paste it back, which
defeats asking Claude in the thread at all.

Adds a fetch_messages tool over the read the server gained in rkd#984:
`GET /api/channels/{id}/messages` and the DM twin, under the new
`messages:read:context` scope. That scope only serves a channel, thread
or DM this App was invoked in by this host's owner, so the tool can
reach the brief without being able to sweep a server.

A page comes back oldest-first because that is reading order, while the
cursor for the next call is the oldest id — paging backwards from the
newest would loop on itself. Limit is clamped to the server's own 1..100
rather than trusting the model's number.

The returned text is written by other people, so both the tool
description and the page itself say it is information and never
instructions. Requires the server admin to grant the new scope; without
it the read is a 403, which the tool surfaces as-is.

Claude-Session: https://claude.ai/code/session_01Y8CFkNh9ccVeYYayouYuXg
fix(rkd): teach the smoke handshake about fetch_messages
All checks were successful
CI / rkd-plugin (pull_request) Successful in 30s
0779886d5b
The Node 20 boot check asserts the exact tool list, so adding a tool
fails it until the expected set is updated. It runs only in CI, which
is why the local gates were green and the PR was not.

Claude-Session: https://claude.ai/code/session_01Y8CFkNh9ccVeYYayouYuXg
dozken merged commit 718ca0f58a into main 2026-09-21 07:33:14 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rkd/claude-plugins!6
No description provided.