Skip to content

feat(mcp): the assistant can see where you are, and record where you got to - #601

Merged
mrviduus merged 2 commits into
mainfrom
feat/read-tools
Sep 11, 2026
Merged

feat(mcp): the assistant can see where you are, and record where you got to#601
mrviduus merged 2 commits into
mainfrom
feat/read-tools

Conversation

@mrviduus

Copy link
Copy Markdown
Owner

Thirteen tools could read any book in the library and write conclusions back into it. Not one could answer where the reader is. The position has been on the server all along; the only way it ever surfaced was ask_book's spoiler refusal, and that tool is gone.

Three tools, no migration.

get_my_reading takes no arguments. That is the point: every other tool needs an id and nothing produced one. It answers over GET /me/library/shelves — both book kinds, titles, authors and progress already joined, and no MCP tool called it — plus GET /me/books for what the shelf caps out (including books never opened). Each row hands back the id the other tools actually take: bookId for an upload, editionId and slug for a catalog book.

get_book_progress — the chapter you stopped in, how far in, whether you finished. A book never opened 404s upstream, which is an answer, not a failure.

set_book_progress — "I listened to chapter two in Spotify". Takes a chapter slug; finishing a chapter resumes the reader at the start of the next one, using the app's own sentinels ({"type":"start"}, and {"type":"end"} on the last chapter — what mark-as-read writes). Progress becomes chapters-finished over chapters-total, declared as a book fraction because the catalog path assigns that column.

Four defects fixed, because the tools cannot work around them

  • A chapter slug was stored unvalidatedUserBookService.UpsertProgressAsync assigned it raw, so an invented slug was stored verbatim and every later read resolved it to nothing. The bookmark path in the same file has always checked.
  • A refusal reported successLocatorSpace.MayReplace returning false dropped the whole write and answered (true, null). Defensible while callers were readers' apps; not once an assistant is a caller and reports a 200 to a person as "recorded".
  • get_book dropped chapterId while save_highlight's own description tells the model to take it "from get_book". It is also what lets the catalog write resolve a slug to the GUID the route requires.
  • The shelf selected chapterSlug and threw it awayLibraryShelfItemDto had nowhere to put it.

Verification

Against the running bridge, not only the stubs: mint a key → POST /mcpget_my_reading returns the shelf with titles → set_book_progress moves the position to the next chapter → the next get_my_reading shows it moved → an invented slug comes back as an error instead of a silent save.

  • dotnet test tests/TextStack.UnitTests — 1235 passed
  • dotnet test tests/TextStack.Ai.Mcp.Tests — 29 passed (11 new over-the-wire, incl. both negative paths)
  • dotnet test tests/TextStack.IntegrationTests — 164 passed, 49 skipped
  • dotnet build textstack.sln — clean

Tool count 13 → 16 in the four places that assert it; every description mirrored character-for-character into Contracts/Mcp/McpManifest.cs, which the drift test compares.

🤖 Generated with Claude Code

https://claude.ai/code/session_011rgEMvYYi4Egj99dVtvm6E

mrviduus and others added 2 commits September 10, 2026 20:16
…got to

Thirteen tools could read any book and write conclusions back into it. None could
answer where the reader is — the position only ever surfaced as ask_book's spoiler
refusal, and that tool is gone.

- get_my_reading takes no arguments: the shelf over the existing
  GET /me/library/shelves, plus GET /me/books for what the shelf caps out. Each
  row hands back the id the other tools take (bookId, or editionId AND slug).
- get_book_progress: the chapter you stopped in. Never opened is an answer, not
  an error.
- set_book_progress: a chapter finished on another medium. Resumes at the START
  of the next chapter using the app's own sentinels, percent as chapters-done
  over chapters-total declared as a book fraction.

Four defects fixed because the tools cannot work around them:

- UserBookService stored an unvalidated chapter slug; an invented one resolved to
  nothing on every later read. The bookmark path in the same file always checked.
- LocatorSpace.MayReplace refusal returned (true, null) — 200 having stored
  nothing, which an assistant reports to a person as recorded.
- get_book dropped chapterId while save_highlight's description tells the model
  to take it "from get_book".
- The shelf selected chapterSlug and had nowhere to put it.

Tool count 13 → 16 in four asserting places; descriptions mirrored verbatim into
McpManifest. Verified against the running bridge, not only the stubs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011rgEMvYYi4Egj99dVtvm6E
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011rgEMvYYi4Egj99dVtvm6E
@mrviduus
mrviduus merged commit e995797 into main Sep 11, 2026
10 checks passed
@mrviduus
mrviduus deleted the feat/read-tools branch September 11, 2026 00:41
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.

1 participant