Skip to content

zaydiscold/goodreads-cli-mcp-api

Repository files navigation

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.

Install

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). 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 — 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 — 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 "<q>" "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 <s> "List one shelf" — from authenticated HTML fixtures or public RSS
books export --fixture-dir <d> "Export my shelves" — deduped by book, with per-shelf membership + completeness flags
book show <slug-or-id> "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

# 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 <id> --direction top            # dry-run plan
goodreads-cli quotes reorder --quote-id <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 <id> --approved-book-id <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)

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:

$ 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/:

  • An OpenAPI 3.1 spec of the undocumented Goodreads web surface.
  • Per-endpoint Markdown under 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.

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. Operating guide for agents: SKILL.md.


GitHub X / Twitter Website


Built on the trio pattern (CLI + skill + MCP) pioneered by Matt Van Horn's 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. 📚

About

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.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors