hardcover-oauth-mcp

A remote MCP server for Hardcover, the book tracking site. It lets Claude and other MCP clients read and manage your library, reading progress, reviews, journal, lists, goals and more.

Public instance: https://hardcover.benjuntilla.com/mcp. Anyone with a Hardcover account can connect; see Connect a client and the privacy notice. Or deploy your own.

Built with Effect 4 (including Effect’s own MCP server), TypeScript 7, Bun, mise and Astro (for this page and the privacy notice).

How it works

┌──────────────┐  MCP + OAuth 2.1   ┌──────────────────────────────┐
│ MCP client   │ ─────────────────▶ │ Worker                       │
│ (Claude, …)  │ ◀───────────────── │  OAuthProvider, consent page │
└──────────────┘  MCP access token  │  auth + rate limits on /mcp  │
                                    └──────────────┬───────────────┘
                                                   │ one per connection
                                    ┌──────────────▼───────────────┐
                                    │ HardcoverConnection (DO)     │
                                    │  Hardcover tokens            │
                                    │  Effect McpServer + tools    │
                                    └──────────────┬───────────────┘
                                                   │ OAuth + GraphQL
                                    ┌──────────────▼───────────────┐
                                    │ Hardcover API                │
                                    └──────────────────────────────┘

The Worker is an OAuth authorization server for MCP clients, built on @cloudflare/workers-oauth-provider. It supports dynamic client registration and Client ID Metadata Documents.

Connecting works like this:

  1. Your MCP client opens /authorize, which shows a consent page for choosing permissions.
  2. The page hands you to Hardcover to sign in.
  3. Hardcover returns to /callback.
  4. The MCP client receives its own short-lived token for /mcp.

Hardcover’s tokens never reach the client. Each connection’s Hardcover tokens live in a HardcoverConnection Durable Object. It refreshes them single-flight, because Hardcover rotates refresh tokens and revokes the whole chain if one is reused. It also revokes them after 150 days without use.

The Worker checks each /mcp request (token, allowlist, rate limit, scopes) and forwards it to the connection’s Durable Object. The object runs Effect’s MCP server, which speaks the sessionless 2026-07-28 revision as well as 2025-11-25, 2025-06-18 and 2025-03-26. Sessions for the 2025 revisions live in the object’s memory. When Cloudflare evicts an idle object, its sessions end: the client gets a 404 and, as the protocol requires, starts a new session.

The landing page (this README) and the privacy notice (PRIVACY.md) are prerendered by Astro and served as static assets.

Tools

Kinds: read, write (adds or changes data), delete. Each tool only appears if your connection was granted its scopes.

All 53 tools
Tool What it does Kind Scopes Needs
hardcover_me Who am I on Hardcover read read:me:content
hardcover_search Search Hardcover read read:catalog
hardcover_get_book Get a book read read:catalog
hardcover_get_editions Get editions read read:catalog
hardcover_get_author Get an author read read:catalog
hardcover_get_series Get a series read read:catalog
hardcover_trending_books Trending books read read:catalog
hardcover_list_library List my library read read:library
hardcover_currently_reading Currently reading read read:library
hardcover_list_reviews List my reviews read read:library
hardcover_get_library_entry Get my entry for a book read read:library
hardcover_set_status Set reading status write write:library, read:library
hardcover_update_progress Update reading progress write write:library, read:library
hardcover_mark_finished Mark a book finished write write:library, read:library
hardcover_rate_book Rate a book write write:library, read:library
hardcover_write_review Write a review write write:library, write:reviews, read:library
hardcover_delete_review Delete a review delete write:library, write:reviews, read:library
hardcover_update_library_entry Edit my entry for a book write write:library, read:library
hardcover_set_owned Mark an edition owned write write:library, read:lists, write:lists
hardcover_remove_from_library Remove a book from my library delete write:library, read:library
hardcover_add_read Add a reading session write write:library, read:library
hardcover_update_read Edit a reading session write write:library, read:library
hardcover_delete_read Delete a reading session delete write:library, read:library
hardcover_list_journal List journal entries read read:journal
hardcover_add_journal_entry Add a journal entry write write:library
hardcover_update_journal_entry Edit a journal entry write write:library
hardcover_delete_journal_entry Delete a journal entry delete write:library
hardcover_list_lists List my lists read read:lists
hardcover_get_list Get a list read read:lists, read:catalog
hardcover_create_list Create a list write write:lists
hardcover_update_list Edit a list write write:lists, read:lists
hardcover_delete_list Delete a list delete write:lists, read:lists
hardcover_add_to_list Add a book to a list write write:lists
hardcover_remove_from_list Remove a book from a list delete write:lists, read:lists
hardcover_reorder_list Reorder a list write write:lists, read:lists
hardcover_list_goals List my reading goals read read:goals
hardcover_create_goal Create a reading goal write write:goals
hardcover_update_goal Edit a reading goal write write:goals, read:goals
hardcover_delete_goal Delete a reading goal delete write:goals, read:goals
hardcover_get_user Look up a reader read read:users
hardcover_list_follows List follows read read:social
hardcover_follow_user Follow a reader write write:social, read:users
hardcover_unfollow_user Unfollow a reader delete write:social, read:users
hardcover_list_notifications List notifications read read:notifications
hardcover_mark_notifications_read Mark notifications read write write:notifications
hardcover_list_vibes List my vibes read read:vibes
hardcover_create_vibe Create a vibe write write:vibes Supporter
hardcover_find_operations Find Hardcover API operations read none
hardcover_describe_type Describe a Hardcover GraphQL type read none
hardcover_query Run a Hardcover GraphQL query read none
hardcover_mutate Run a Hardcover GraphQL mutation write any write:* scope Supporter for write:vibes
hardcover_delete Run a Hardcover GraphQL deletion delete any write:* scope Supporter for write:vibes
hardcover_catalog_edit Add to or edit the Hardcover catalog write any write:* catalog scope Librarian for write:catalog:edit, Librarian (book_mapper) for write:catalog:map

Supporter means a paid Hardcover membership. Librarian means a Hardcover librarian role: write:catalog:edit needs one, and book mappings (write:catalog:map) need book_mapper. Anyone may add catalog records and fill empty fields with write:catalog:append. Hardcover enforces all of this itself. This server labels the requirements (hardcover-oauth-mcp/requires and hardcover-oauth-mcp/scope-requires in each tool’s _meta), and drops edit and map at sign-in when your account lacks the role. Updating a vibe’s signals also needs Supporter; for non-Supporters Hardcover ignores those changes silently.

The raw tools (hardcover_query, hardcover_mutate, hardcover_delete, hardcover_catalog_edit) check each document before sending it, so mistakes come back as clear errors instead of costing rate limit. The checks are:

hardcover_find_operations and hardcover_describe_type read an index of the schema generated from Hardcover’s API docs (mise run schema:sync).

Hardcover’s rate limits are per user and shared across every app and token: 60 requests a minute, a burst of 10 (15 for Supporters), and 5,000 a day (50,000). Every top-level field costs one request.

Deploy your own

You need a Cloudflare account, a Hardcover account and mise (or Bun 1.3.10). A custom domain is optional; without one the server lives at https://hardcover-oauth-mcp.<your-subdomain>.workers.dev.

1. Install

git clone https://github.com/benjuntilla/hardcover-oauth-mcp
cd hardcover-oauth-mcp
mise install        # Bun, pinned in mise.toml
mise run install    # bun install
cp .env.example .env

2. Register a Hardcover app

Create one at hardcover.app/account/developer-apps/new:

Put the client id and secret into .env as HARDCOVER_CLIENT_ID and HARDCOVER_CLIENT_SECRET. The secret is shown only once.

3. Configure

In .env:

4. Sign Alchemy in to Cloudflare

bun alchemy profile edit --add Cloudflare   # opens a browser to authorize; pick the account

Or set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID in .env. The token needs Workers Scripts, Workers KV Storage and Workers Routes edit permissions, plus DNS edit on the zone if you use a custom domain.

5. Deploy

mise run deploy --stage prod

This builds the static pages with Astro and creates the KV namespace, the Durable Object, the Worker with its secrets and static assets, the rate limiters, the daily cleanup cron and the custom domain. It then prints the URL. Run it again after changes. mise run logs --stage prod tails logs. mise run destroy --stage prod removes everything.

Alchemy keeps deployment state in .alchemy/. Set ALCHEMY_STATE=cloudflare so the state lives in your Cloudflare account instead; then any machine, including CI, can redeploy or destroy.

6. Connect a client

The MCP URL is https://<your-domain>/mcp.

Your client opens the consent page, then Hardcover. Approve both and the tools appear.

Run locally

Hardcover accepts http://localhost redirect URIs, so the whole flow works on your machine:

  1. Register a separate Hardcover app for development, the same as above but with the redirect URI http://localhost:1337/callback.
  2. Fill in .env with that app’s credentials. HARDCOVER_MCP_DOMAIN is ignored locally.
  3. Run mise run dev. It serves the Worker in workerd at http://localhost:1337, with local KV and Durable Objects, and reloads on code changes. The static pages (README, PRIVACY.md, site/) are built once at start, so restart it to see changes to them. No Cloudflare account is needed.
  4. Connect a client on the same machine to http://localhost:1337/mcp, for example claude mcp add --transport http hardcover-local http://localhost:1337/mcp.

Remote clients such as Claude on the web can’t reach localhost; they need a deployment.

Development

mise run check      # TypeScript 7 typecheck + unit tests
mise run test:e2e   # the Worker in workerd, a mock Hardcover, and a scripted MCP client

Layout:

Path What
alchemy.run.ts Infrastructure: Worker, KV, Durable Object, rate limiters, static site build, secrets, cron, domain
src/worker.ts Entry point and per-IP rate limits
src/oauth/ MCP-facing authorization server: provider config, consent page, Hardcover callback
src/connection/ HardcoverConnection Durable Object: token storage and refresh, and the connection’s MCP server
src/hardcover/ Hardcover OAuth client, GraphQL executor, guard, scopes, schema index
src/mcp/ Effect MCP server, the /mcp auth wrapper, tools
site/, astro.config.mjs Astro pages for README.md and PRIVACY.md, and the shared stylesheet
scripts/ Schema sync and the README tool table

Security notes

License

MIT, © 2026 Ben Juntilla. src/generated/hardcover-api.json and test/fixtures/hardcover-schema.graphql are derived from hardcoverapp/hardcover-docs (MIT, © Hardcover LLC). This project isn’t affiliated with Hardcover.