{"id":51389668,"url":"https://github.com/zaydiscold/goodreads-cli-mcp-api","last_synced_at":"2026-07-03T22:31:21.198Z","repository":{"id":362234289,"uuid":"1246300588","full_name":"zaydiscold/goodreads-cli-mcp-api","owner":"zaydiscold","description":"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.","archived":false,"fork":false,"pushed_at":"2026-06-23T01:04:23.000Z","size":242,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T03:07:26.043Z","etag":null,"topics":["api","books","cli","goodreads","mcp","openapi","reverse-engineering","typescript"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zaydiscold.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-22T04:10:54.000Z","updated_at":"2026-06-23T01:04:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zaydiscold/goodreads-cli-mcp-api","commit_stats":null,"previous_names":["zaydiscold/goodreads-cli","zaydiscold/goodreads-cli-mcp-api"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zaydiscold/goodreads-cli-mcp-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaydiscold%2Fgoodreads-cli-mcp-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaydiscold%2Fgoodreads-cli-mcp-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaydiscold%2Fgoodreads-cli-mcp-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaydiscold%2Fgoodreads-cli-mcp-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zaydiscold","download_url":"https://codeload.github.com/zaydiscold/goodreads-cli-mcp-api/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zaydiscold%2Fgoodreads-cli-mcp-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35104113,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-03T02:00:05.635Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","books","cli","goodreads","mcp","openapi","reverse-engineering","typescript"],"created_at":"2026-07-03T22:31:20.313Z","updated_at":"2026-07-03T22:31:21.186Z","avatar_url":"https://github.com/zaydiscold.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Goodreads CLI (MCP + API)\n\n\u003e \"i made all this so I can have a cron job on the homelab setup that auto publishes my kindle highlights and notes :)\"\n\nAn unofficial **API map + CLI + MCP server** for the logged-in Goodreads web surface — shelves, books, ratings, reviews, quotes, and Kindle notes \u0026 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.**\n\n---\n\n## ⚠️ Disclaimer\n\n**This is an independent, unofficial project. It is NOT affiliated with, endorsed by, or approved by Goodreads or Amazon.**\n\n- **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.\n- **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.\n- **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).\n- **No warranty.** Provided \"as is\". See [LICENSE](LICENSE).\n\n---\n\n## Install\n\n```bash\ngit clone https://github.com/zaydiscold/goodreads-cli-mcp-api.git\ncd goodreads-cli-mcp-api\npnpm install\npnpm build\nnode cli/dist/index.js --help     # or link the bin: goodreads-cli --help\n```\n\nRequires **Node ≥ 20** and **pnpm**. The MCP server runs with `node mcp/dist/server.js`.\n\n## What it does\n\nFull read **and** write across Goodreads:\n\n- **Shelves** — discover your shelf inventory + counts; list and export shelves (HTML pagination or RSS), deduped by book with per-shelf membership.\n- **Books** — parse any public book page (JSON-LD + Next.js metadata).\n- **Kindle Notes \u0026 Highlights** — inspect notes metadata, plan + execute publicize/hide (gated), and join your current/read shelves to your notes index.\n- **Annotations** — per-highlight annotation metadata (visibility, spoiler, persist endpoints) without raw highlight text.\n- **Quotes** — add, remove, and reorder your quotes (up/down/top/bottom).\n- **Ratings \u0026 Reviews** — via the modern AppSync **GraphQL** ops (`RateBook`/`UnrateBook`) and the mapped review write routes.\n- **Comments \u0026 Messages** — inspect comment/message route + form shape without emitting bodies.\n- **Raw route driving** — plan or execute any mapped route directly.\n\nEverything 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.\n\n## CLI ↔ MCP parity — one engine, no drift\n\nThe 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**.\n\nThat 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.\n\nLive tool truth is always `tools/list` (currently **28 tools**), never a hardcoded number.\n\nFor cron-based automation on WSL, see [`wsl-sync.sh`](./wsl-sync.sh) — a daily sync script that pulls reading data to your Windows Desktop.\n\n## Command tour — what answers what\n\nAll reads run live and free. All writes default to a dry-run; the notes workflow needs the three explicit gates below.\n\n| Command | The question it answers |\n|---|---|\n| `api-map routes` / `api-map search \"\u003cq\u003e\"` | \"What can this drive?\" — the mapped Goodreads surface (89 routes) |\n| `api-map browser-routes` | \"What did the authenticated CDP capture see?\" — sanitized route templates |\n| `shelves discover` | \"What shelves do I have, and how many books in each?\" |\n| `books list --shelf \u003cs\u003e` | \"List one shelf\" — from authenticated HTML fixtures or public RSS |\n| `books export --fixture-dir \u003cd\u003e` | \"Export my shelves\" — deduped by book, with per-shelf membership + completeness flags |\n| `book show \u003cslug-or-id\u003e` | \"Parse this book page\" — JSON-LD + Next.js metadata |\n| `recent-reading list / notes` | \"Join my current/read shelves to my Kindle notes index\" |\n| `recent-reading publicize-plan / publicize` | \"Plan, then publicize, my recent books' highlights\" (gated) |\n| `notes inspect` | \"What's in this notes page?\" — counts + visibility, no highlight text |\n| `notes publicize-plan` | \"Build the verified plan for one book's notes\" |\n| `notes publicize` / `notes hide` | \"Make all highlights public / hidden for a book\" (gated) |\n| `annotations list / thoughts-plan` | \"Per-highlight annotation metadata; plan a per-note thought\" |\n| `quotes add / remove / reorder` | \"Manage my quotes\" (dry-run unless `--execute`) |\n| `comments list` / `messages folders` / `messages list` | \"Inspect comment/message page shape without bodies\" |\n| `write-plan books move` / `write-plan notes publicize` | \"Static dry-run mutation plans\" |\n| `request plan` / `request execute` | \"Drive any mapped route raw\" (execute is live-capable; pass `--dry-run` to preview) |\n\n## Safety model\n\n```bash\n# Reads: live and free\ngoodreads-cli shelves discover --fixture ./fixtures/shelf-read.html\ngoodreads-cli api-map search \"publicize notes\"\n\n# Quote writes: dry-run by default; --execute fires the live Rails-UJS POST\ngoodreads-cli quotes reorder --quote-id \u003cid\u003e --direction top            # dry-run plan\ngoodreads-cli quotes reorder --quote-id \u003cid\u003e --direction top --execute  # live\n\n# Notes publicize/hide: gated THREE ways — --execute + exact --approved-book-id + env flag\nGOODREADS_ALLOW_NOTES_PUBLICIZE=1 \\\nGOODREADS_COOKIE=\"session-id=...\" GOODREADS_CSRF_TOKEN=\"...\" \\\ngoodreads-cli notes publicize --book-id \u003cid\u003e --approved-book-id \u003cid\u003e --execute --json\n```\n\nEvery 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.\n\n## Use it from an agent (MCP)\n\n```bash\npnpm --filter @zaydiscold/goodreads-mcp build\n\n# Claude Code:\nclaude mcp add goodreads-cli -s user -- node /abs/path/to/goodreads-cli-mcp-api/mcp/dist/server.js\n# Hermes:\nhermes mcp add goodreads --command node --args /abs/path/to/goodreads-cli-mcp-api/mcp/dist/server.js\n```\n\nThe 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.\n\n## Example: agent-driven notes publicizing\n\nWhat 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:\n\n```bash\n$ goodreads-cli api-map search notes                       # 1. find the route\n$ goodreads-cli notes publicize-plan --book-id 218134959 \\  # 2. preflight counts\n    --detail-fixture ./fixtures/notes-218134959.html --approved-book-id 218134959 --json\n# =\u003e { \"detail\": { \"noteCount\": 47, \"visibleNoteCount\": 0, \"hiddenNoteCount\": 47 },\n#      \"action\": \"publicize-notes\", \"blockers\": [] }\n$ goodreads-cli notes publicize --book-id 218134959 --dry-run --json   # 3. dry-run shows the gates\n$ GOODREADS_ALLOW_NOTES_PUBLICIZE=1 goodreads-cli notes publicize \\    # 4. execute\n    --book-id 218134959 --approved-book-id 218134959 --execute --json\n# 5. reload /notes/{book_slug}/{user_slug} and verify visibleNoteCount === noteCount\n```\n\nThe agent never emits raw highlight text, never leaks cookies or tokens, and every write is gated even when driven autonomously.\n\n## The map is the point\n\nThe real artifact lives in [`api-map/`](./api-map/):\n\n- An **OpenAPI 3.1** spec of the undocumented Goodreads web surface.\n- **Per-endpoint Markdown** under [`api-map/markdown/`](./api-map/markdown/).\n- A **curl** reference so any of it is reproducible without this CLI.\n\nIt 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).\n\n## Architecture \u0026 extending\n\n```\napi-map/ ─ the mapped surface (the product)\n   │\ncli/src/engine.ts ─ THE SHARED ENGINE (every operation, enveloped output)\n   ├── cli/src/commands/*  ─ thin commander wrappers\n   └── mcp/src/server.ts   ─ thin MCP tool adapters\n```\n\nFound 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).\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/zaydiscold\"\u003e\u003cimg alt=\"GitHub\" src=\"https://img.shields.io/badge/GitHub-zaydiscold-181717?style=for-the-badge\u0026logo=github\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://twitter.com/ColdCooks\"\u003e\u003cimg alt=\"X / Twitter\" src=\"https://img.shields.io/badge/X-@ColdCooks-000000?style=for-the-badge\u0026logo=x\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://zayd.wtf\"\u003e\u003cimg alt=\"Website\" src=\"https://img.shields.io/badge/Web-zayd.wtf-FF4D8D?style=for-the-badge\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\nBuilt on the trio pattern (CLI + skill + MCP) pioneered by [Matt Van Horn's Printing Press](https://github.com/mvanhorn/cli-printing-press).\n\n\u003cp align=\"center\"\u003e\u003cstrong\u003eMapped \u0026amp; built by Zayd Khan // cold\u003c/strong\u003e (\u003ca href=\"https://twitter.com/ColdCooks\"\u003e@ColdCooks\u003c/a\u003e / \u003ca href=\"https://github.com/zaydiscold\"\u003ezaydiscold\u003c/a\u003e / \u003ca href=\"https://zayd.wtf\"\u003ezayd.wtf\u003c/a\u003e). MIT © Zayd Khan.\u003c/p\u003e\n\n---\n\n\u003e **`*** SYSTEM NOTIFICATION ***`**\n\u003e\n\u003e Congratulations, Reader. You reached the last page of the README — most dog-ear it and quit.\n\u003e\n\u003e *Achievement unlocked — \"Marginalia.\"* You now hold a typed, gated control plane for your own\n\u003e reading life: every shelf, every quote, every Kindle highlight you annotated at 2am. The System\n\u003e notes your `GOODREADS_ALLOW_NOTES_PUBLICIZE` flag is **unset.** Good — highlights stay yours\n\u003e until you say otherwise.\n\u003e\n\u003e *A library is only as private as the reader guarding it. You're the reader. Publicize on purpose.*\n\u003e\n\u003e **Loot dropped:** one (1) hand-mapped API, 28 MCP tools, and the receipts in `api-map/`.\n\u003e *Read deliberately. Ship the complete thing. Return your books on time.* 📚\n\n\u003c!-- Zayd Khan // cold // www.zayd.wtf --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzaydiscold%2Fgoodreads-cli-mcp-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzaydiscold%2Fgoodreads-cli-mcp-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzaydiscold%2Fgoodreads-cli-mcp-api/lists"}