feat(mcp): the assistant can see where you are, and record where you got to - #601
Merged
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_readingtakes no arguments. That is the point: every other tool needs an id and nothing produced one. It answers overGET /me/library/shelves— both book kinds, titles, authors and progress already joined, and no MCP tool called it — plusGET /me/booksfor what the shelf caps out (including books never opened). Each row hands back the id the other tools actually take:bookIdfor an upload,editionIdandslugfor 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
UserBookService.UpsertProgressAsyncassigned 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.LocatorSpace.MayReplacereturning 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_bookdroppedchapterIdwhilesave_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.chapterSlugand threw it away —LibraryShelfItemDtohad nowhere to put it.Verification
Against the running bridge, not only the stubs: mint a key →
POST /mcp→get_my_readingreturns the shelf with titles →set_book_progressmoves the position to the next chapter → the nextget_my_readingshows it moved → an invented slug comes back as an error instead of a silent save.dotnet test tests/TextStack.UnitTests— 1235 passeddotnet 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 skippeddotnet build textstack.sln— cleanTool 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