https://github.com/zaydiscold/goodreads-cli-mcp-api
Goodreads CLI (MCP + API) — unofficial hand-mapped Goodreads web surface, driven by a TypeScript CLI + MCP server over one shared engine (full CLI↔MCP parity). The API map is the product.
https://github.com/zaydiscold/goodreads-cli-mcp-api
api books cli goodreads mcp openapi reverse-engineering typescript
Last synced: 2 days ago
JSON representation
Goodreads CLI (MCP + API) — unofficial hand-mapped Goodreads web surface, driven by a TypeScript CLI + MCP server over one shared engine (full CLI↔MCP parity). The API map is the product.
- Host: GitHub
- URL: https://github.com/zaydiscold/goodreads-cli-mcp-api
- Owner: zaydiscold
- License: mit
- Created: 2026-05-22T04:10:54.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-23T01:04:23.000Z (13 days ago)
- Last Synced: 2026-06-23T03:07:26.043Z (13 days ago)
- Topics: api, books, cli, goodreads, mcp, openapi, reverse-engineering, typescript
- Language: TypeScript
- Size: 236 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Agents: AGENTS.md
Awesome Lists containing this project
README
# Goodreads CLI (MCP + API)
> "i made all this so I can have a cron job on the homelab setup that auto publishes my kindle highlights and notes :)"
An unofficial **API map + CLI + MCP server** for the logged-in Goodreads web surface — shelves, books, ratings, reviews, quotes, and Kindle notes & highlights — driven from the terminal or from your agents, without ever opening the website. Amazon closed the public Goodreads API to new keys in December 2020, so this drives the web surface from a hand-mapped OpenAPI spec: a TypeScript CLI **and** an MCP server sharing one engine. **The map is the headline; the CLI and MCP are the proof it's real.**
---
## ⚠️ Disclaimer
**This is an independent, unofficial project. It is NOT affiliated with, endorsed by, or approved by Goodreads or Amazon.**
- **Unofficial surface.** Amazon closed the public Goodreads API to new keys in December 2020. This tool drives the *logged-in web surface* (HTML pages, RSS, CSV exports, Rails-UJS form POSTs, and the newer AppSync GraphQL ops) mapped by hand. Goodreads can rename or rotate any of it without notice — trust live reads over memory.
- **Your own account, at your own risk.** It acts on the account you're already logged into, using your own browser cookie + CSRF token. Automated/non-browser access may be against Goodreads' Terms of Service. Use it on your own account and understand the risk.
- **Writes can change your account.** Publicizing/hiding notes, moving shelves, and quote edits mutate your real account. Every write defaults to a dry-run; the notes workflow is gated three ways (below).
- **No warranty.** Provided "as is". See [LICENSE](LICENSE).
---
## Install
```bash
git clone https://github.com/zaydiscold/goodreads-cli-mcp-api.git
cd goodreads-cli-mcp-api
pnpm install
pnpm build
node cli/dist/index.js --help # or link the bin: goodreads-cli --help
```
Requires **Node ≥ 20** and **pnpm**. The MCP server runs with `node mcp/dist/server.js`.
## What it does
Full read **and** write across Goodreads:
- **Shelves** — discover your shelf inventory + counts; list and export shelves (HTML pagination or RSS), deduped by book with per-shelf membership.
- **Books** — parse any public book page (JSON-LD + Next.js metadata).
- **Kindle Notes & Highlights** — inspect notes metadata, plan + execute publicize/hide (gated), and join your current/read shelves to your notes index.
- **Annotations** — per-highlight annotation metadata (visibility, spoiler, persist endpoints) without raw highlight text.
- **Quotes** — add, remove, and reorder your quotes (up/down/top/bottom).
- **Ratings & Reviews** — via the modern AppSync **GraphQL** ops (`RateBook`/`UnrateBook`) and the mapped review write routes.
- **Comments & Messages** — inspect comment/message route + form shape without emitting bodies.
- **Raw route driving** — plan or execute any mapped route directly.
Everything is **redaction-first**: output carries counts, status, timing, link shapes, and route metadata — never raw highlight text, comment bodies, cookies, CSRF tokens, or private URLs.
## CLI ↔ MCP parity — one engine, no drift
The thing that makes this more than a script: **the CLI and the MCP server share a single engine** ([`cli/src/engine.ts`](./cli/src/engine.ts)). Every command is a thin wrapper that calls an engine function; every MCP tool is the same. They emit the **identical** enveloped JSON, so an agent and a human get the same answer the same way — and the two surfaces **cannot drift**.
That invariant is enforced by code, not vigilance: a `CAPABILITIES` registry in the engine is checked **in both directions** by [`cli/test/parity.test.ts`](./cli/test/parity.test.ts) — every capability must have a CLI command **and** an MCP tool, with no orphans on either side. Add a command without its MCP twin and CI goes red.
Live tool truth is always `tools/list` (currently **28 tools**), never a hardcoded number.
For cron-based automation on WSL, see [`wsl-sync.sh`](./wsl-sync.sh) — a daily sync script that pulls reading data to your Windows Desktop.
## Command tour — what answers what
All reads run live and free. All writes default to a dry-run; the notes workflow needs the three explicit gates below.
| Command | The question it answers |
|---|---|
| `api-map routes` / `api-map search ""` | "What can this drive?" — the mapped Goodreads surface (89 routes) |
| `api-map browser-routes` | "What did the authenticated CDP capture see?" — sanitized route templates |
| `shelves discover` | "What shelves do I have, and how many books in each?" |
| `books list --shelf ` | "List one shelf" — from authenticated HTML fixtures or public RSS |
| `books export --fixture-dir ` | "Export my shelves" — deduped by book, with per-shelf membership + completeness flags |
| `book show ` | "Parse this book page" — JSON-LD + Next.js metadata |
| `recent-reading list / notes` | "Join my current/read shelves to my Kindle notes index" |
| `recent-reading publicize-plan / publicize` | "Plan, then publicize, my recent books' highlights" (gated) |
| `notes inspect` | "What's in this notes page?" — counts + visibility, no highlight text |
| `notes publicize-plan` | "Build the verified plan for one book's notes" |
| `notes publicize` / `notes hide` | "Make all highlights public / hidden for a book" (gated) |
| `annotations list / thoughts-plan` | "Per-highlight annotation metadata; plan a per-note thought" |
| `quotes add / remove / reorder` | "Manage my quotes" (dry-run unless `--execute`) |
| `comments list` / `messages folders` / `messages list` | "Inspect comment/message page shape without bodies" |
| `write-plan books move` / `write-plan notes publicize` | "Static dry-run mutation plans" |
| `request plan` / `request execute` | "Drive any mapped route raw" (execute is live-capable; pass `--dry-run` to preview) |
## Safety model
```bash
# Reads: live and free
goodreads-cli shelves discover --fixture ./fixtures/shelf-read.html
goodreads-cli api-map search "publicize notes"
# Quote writes: dry-run by default; --execute fires the live Rails-UJS POST
goodreads-cli quotes reorder --quote-id --direction top # dry-run plan
goodreads-cli quotes reorder --quote-id --direction top --execute # live
# Notes publicize/hide: gated THREE ways — --execute + exact --approved-book-id + env flag
GOODREADS_ALLOW_NOTES_PUBLICIZE=1 \
GOODREADS_COOKIE="session-id=..." GOODREADS_CSRF_TOKEN="..." \
goodreads-cli notes publicize --book-id --approved-book-id --execute --json
```
Every live mutation prints a `[WRITES TO LIVE GOODREADS]` warning to stderr, and the rule is **verify after every write** — never trust an HTTP 200; reload the notes page and confirm the visible count.
## Use it from an agent (MCP)
```bash
pnpm --filter @zaydiscold/goodreads-mcp build
# Claude Code:
claude mcp add goodreads-cli -s user -- node /abs/path/to/goodreads-cli-mcp-api/mcp/dist/server.js
# Hermes:
hermes mcp add goodreads --command node --args /abs/path/to/goodreads-cli-mcp-api/mcp/dist/server.js
```
The 28 MCP tools surface as `mcp__goodreads-cli__*` and inherit the **same** engine, auth, route map, and write gates as the CLI — so `goodreads_notes_publicize` runs the exact same gated workflow as `notes publicize`. Pass `GOODREADS_COOKIE`, `GOODREADS_CSRF_TOKEN`, and `GOODREADS_ALLOW_NOTES_PUBLICIZE=1` in the server's environment for live writes.
## Example: agent-driven notes publicizing
What it looks like to ask an agent to make a book's Kindle highlights public — discover the route, check counts, plan, then execute behind the gates:
```bash
$ goodreads-cli api-map search notes # 1. find the route
$ goodreads-cli notes publicize-plan --book-id 218134959 \ # 2. preflight counts
--detail-fixture ./fixtures/notes-218134959.html --approved-book-id 218134959 --json
# => { "detail": { "noteCount": 47, "visibleNoteCount": 0, "hiddenNoteCount": 47 },
# "action": "publicize-notes", "blockers": [] }
$ goodreads-cli notes publicize --book-id 218134959 --dry-run --json # 3. dry-run shows the gates
$ GOODREADS_ALLOW_NOTES_PUBLICIZE=1 goodreads-cli notes publicize \ # 4. execute
--book-id 218134959 --approved-book-id 218134959 --execute --json
# 5. reload /notes/{book_slug}/{user_slug} and verify visibleNoteCount === noteCount
```
The agent never emits raw highlight text, never leaks cookies or tokens, and every write is gated even when driven autonomously.
## The map is the point
The real artifact lives in [`api-map/`](./api-map/):
- An **OpenAPI 3.1** spec of the undocumented Goodreads web surface.
- **Per-endpoint Markdown** under [`api-map/markdown/`](./api-map/markdown/).
- A **curl** reference so any of it is reproducible without this CLI.
It covers the read surface (HTML pages, RSS, CSV exports) and the write endpoints (Rails-UJS form POSTs captured from `data-remote` actions), plus the **AppSync GraphQL** ops for the modern book/rating/feed widgets. A 2026-06-08 hardening pass live-tested every read route and fire-tested the reversible writes, and closed the quote write surface (add/remove/reorder). See [`docs/write-operations.md`](./docs/write-operations.md).
## Architecture & extending
```
api-map/ ─ the mapped surface (the product)
│
cli/src/engine.ts ─ THE SHARED ENGINE (every operation, enveloped output)
├── cli/src/commands/* ─ thin commander wrappers
└── mcp/src/server.ts ─ thin MCP tool adapters
```
Found an endpoint I missed? Add it to the OpenAPI spec + a Markdown page under `api-map/`, then wire **one engine function + a `CAPABILITIES` entry**, and add the matching CLI command and MCP tool. The parity test will tell you if you forgot one. Full developer runbook: [`AGENTS.md`](./AGENTS.md). Operating guide for agents: [`SKILL.md`](./SKILL.md).
---
---
Built on the trio pattern (CLI + skill + MCP) pioneered by [Matt Van Horn's Printing Press](https://github.com/mvanhorn/cli-printing-press).
Mapped & built by Zayd Khan // cold (@ColdCooks / zaydiscold / zayd.wtf). MIT © Zayd Khan.
---
> **`*** SYSTEM NOTIFICATION ***`**
>
> Congratulations, Reader. You reached the last page of the README — most dog-ear it and quit.
>
> *Achievement unlocked — "Marginalia."* You now hold a typed, gated control plane for your own
> reading life: every shelf, every quote, every Kindle highlight you annotated at 2am. The System
> notes your `GOODREADS_ALLOW_NOTES_PUBLICIZE` flag is **unset.** Good — highlights stay yours
> until you say otherwise.
>
> *A library is only as private as the reader guarding it. You're the reader. Publicize on purpose.*
>
> **Loot dropped:** one (1) hand-mapped API, 28 MCP tools, and the receipts in `api-map/`.
> *Read deliberately. Ship the complete thing. Return your books on time.* 📚