Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

reader

Read Claude on your e-reader — and tap back.

Add to Claude

Ask Claude for something long and it appears on your Kindle, Kobo, reMarkable, PocketBook or Boox in about three seconds — through the browser the device already has. No jailbreak, no email, no EPUB, no account, no app.

And it reads back: Claude can put tappable buttons under what it wrote, so you answer from the device and the conversation carries on.

Start

1.  Add the connector           https://reader.neves.cloud/mcp
2.  On the e-reader, open       reader.neves.cloud      → shows a 5-char code
3.  Tell Claude                 "send that to my reader, code 8427X"

The e-reader types nothing. It shows a code; you read it off the screen and say it out loud. That constraint drives most of the design below.

The tools

Tool What it does
send_to_reader Puts markdown on the screen. Optionally with tappable choices.
await_reader_choice Blocks until they tap — a choice, a quick action, or text they marked.
check_reader Is a device polling, what is it showing, what page are they on.
send_drill Hands a whole multiple-choice deck to the server, which runs the loop.
await_drill_report Per-item results once the deck clears.
resume_drill Puts the drill question back after an interruption.

Markdown, GFM tables and SVG (fenced ```svg or raw <svg>) render crisply in grayscale. mode: "append" streams a long piece in chunks without losing the reader's place.

Beyond the buttons Claude sends, the device always offers three of its own: ↻ simpler, → more, and ✎ explain — mark a word, sentence or block and Claude explains it in place.

How it works

                    "send that to my reader, code 8427X"
                                    │
                                 Claude
                                    │
              ┌─────────────────────┴──────────────────────┐
              ▼                                            ▼
   reader.neves.cloud/mcp                          mcp.neves.cloud/mcp
   anonymous · no sign-in                          GitHub OAuth · remembers
   calls the DO in-process                         your reader
              │                                            │
              │                                  Bearer READER_TOKEN
              │                                     → /_api/send
              └─────────────────────┬──────────────────────┘
                                    ▼
                    ┌───────────────────────────────┐
                    │  Durable Object "<code>"      │
                    │  { v, html, title } · 6h TTL  │
                    └───────────────────────────────┘
                        │                        ▲
       polls /s/<code>  │                        │  taps /c/<code>
       2.5s → 30s       ▼                        │  (also ws /w/<code>,
                    e-reader — stock browser, e-ink   one frame per tap)

Polling rather than WebSocket or SSE is deliberate: e-ink repaints in about a second and these devices' WS/SSE are unreliable, so a 2.5s short-poll with full-document replace is both the right granularity and the robust one.

Two front doors, same tools, same core operations (src/ops.ts) — so their append, render and delivery behaviour cannot drift apart:

  • reader.neves.cloud/mcp — anonymous, served by this Worker (src/mcp.ts), calling the Session Durable Objects in-process. No OAuth anywhere on this origin. The 5-character code is required on every call: with no accounts, there is nothing to hang a saved pairing on.
  • mcp.neves.cloud/mcp — GitHub sign-in buys a saved pairing (code becomes optional after the first send) and a pair_reader tool. It reaches this Worker's write API over a service binding.

Deck mode: a drill runs on the device, not through the model

A multiple-choice drill with an answer key is a deterministic loop, but run screen-by-screen through an agent it costs two round-trips per item — an 18-item deck is 36 — for work a state machine does.

send_drill hands the whole deck over: question, choices, answer index, optional feedback, plus a policy (requeue_until_correct, shuffle). From there the Durable Object scores each tap, renders the feedback screen and turns the page itself. Pages advance at device speed, with no model in the loop. await_drill_report returns per-item results at the end: what they tapped, first-try correctness, retries, seconds.

Its edges stay open. A quick action is the one tap that genuinely needs a model — it parks the drill and surfaces as an ordinary pending tap. Reply with mode: "append" and the next answer-tap resumes it on its own; resume_drill re-renders the question when you want a clean screen. A mode: "replace" send ends the drill, which is also how you cancel one.

The boundary is deliberate: deck mode is only for a deck authored up front with closed-form answers. Teaching, discussion, partial credit and adaptive re-explaining stay on send_to_reader / await_reader_choice. The calling agent picks the mode.

Security

The code is the whole capability, so the keyspace is the security parameter: 305 = 24,300,000 (base32 minus every character you could misread off e-ink — no I/L/O/U, no 0/1). Unthrottled, that is a search space rather than a secret, so two per-IP budgets sit in front of it:

  • a loose ceiling on every code-bearing device route, sized well above a real reader's 2.5s poll — it caps how fast one address can mint Durable Objects;
  • a tight budget spent only on first contact with a code. A device pays it once and is never fresh again; a guesser pays it on every guess. At 30/min, one address needs on the order of a year and a half to walk the keyspace.

The MCP endpoint gets only the loose ceiling on purpose: those requests arrive from the client's shared egress, so a tight per-IP budget there would make users throttle each other.

The write API (/_api/*) is a separate matter — shared-secret bearer, fail-closed: an unset secret rejects every write.

Treat the code like a password. Anyone holding it can see that screen and send to it. Open reader.neves.cloud/new for a fresh one.

Privacy

No accounts, no email, no analytics, no third-party sharing, nothing used for training. A session holds the document, your taps and your place in it, and deletes itself after 6 hours in which neither side touches it. One first-party cookie remembers your code for 48 hours.

Full policy: https://reader.neves.cloud/privacy — and privacyPage in src/pages.ts, so every claim in it is checkable against the code here.

Support

Open an issue: https://github.com/nevescloud/reader/issues

Develop

npm install
npm run dev        # wrangler dev — local Durable Objects, no Cloudflare auth needed
npm run typecheck
npm test           # markdown renderer, code-alphabet invariants, the drill machine

# write API — set READER_TOKEN in .dev.vars (e.g. "dev"); always required, fail-closed
curl -H "Authorization: Bearer dev" -X POST localhost:8787/_api/send \
  -d '{"code":"ABCDE","content":"# hi"}'

Deploy

wrangler secret put READER_TOKEN   # one-time: the shared secret the write API expects
npm run deploy                     # binds reader.neves.cloud (DNS auto-provisioned)

Layout

One Worker, served at the origin root.

File What lives there
src/index.ts Router: /mcp · /_api/* (write, token) · /s poll · /c tap · /w feed · /<code> reader · /privacy · / entry
src/ops.ts sendDoc / awaitChoice / readStatus / startDrill / awaitDrillReport / resumeDrill — the single source both front doors call
src/mcp.ts ReaderMcp — the anonymous Streamable-HTTP server and its six tools
src/session.ts One Durable Object per code: document + version, pending tap, parked waiters, drill state, hibernating feed sockets, 6h expiry
src/drill.ts Deck mode's rules, pure — validation, tap→next-state, the three screens, the report. No storage, no rendering, so it tests without a DO harness
src/limit.ts The two anti-enumeration budgets (see Security)
src/md.ts Markdown → clean reading HTML
src/pages.ts landingPage · privacyPage · readerPage (e-ink serif, paginated, ES5-only inline script for ~2012 WebKit)
src/util.ts Host, code alphabet, public-URL constants — single source

License

MIT — see LICENSE.

About

Read Claude on a Kindle or Kobo — and tap back. A live, two-way e-ink surface over MCP: no app, no email, no jailbreak.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages