# KDPBot AI — MCP Server

The official [Model Context Protocol](https://modelcontextprotocol.io) server for KDPBot AI. It gives any MCP client — Claude Desktop, Claude Code, Cursor, Windsurf, Zed, or your own agent — eleven native tools that plan, create, outline, write, track, cover and export full-length books for Amazon KDP. Chapter illustration is **not** among them: `POST /api/chapters/{id}/generate-image/` exists in the REST API but is not exposed over MCP.

- **Protocol:** MCP revision `2026-07-28` — the current one. The server is **dual-era**: modern clients declare their version in each request's `_meta` and probe with `server/discover`; clients still on the `initialize` handshake are served `2025-11-25`, `2025-06-18`, `2025-03-26` or `2024-11-05` unchanged.
- **Transport:** stdio — newline-delimited JSON-RPC 2.0
- **Package:** [`kdpbot`](https://www.npmjs.com/package/kdpbot) on npm — the same package as the CLI
- **Dependencies:** none. Node.js 18 or newer.
- **Capabilities advertised:** `tools` only. No resources, no prompts, no sampling.

Related: [quickstart](https://kdpbot.com/docs/quickstart.md) · [authentication](https://kdpbot.com/docs/authentication.md) · [errors](https://kdpbot.com/docs/errors.md) · [full API reference](https://kdpbot.com/docs/api.md) · [OpenAPI 3.1 spec](https://kdpbot.com/openapi.json)

---

## Install

There is nothing to install ahead of time. `npx` fetches the package on first run and caches it:

```bash
npx -y kdpbot mcp
```

That command is not meant to be typed at a prompt — it speaks JSON-RPC on stdin/stdout and waits for a client. Put it in your MCP client config instead (below). To pin it locally:

```bash
npm install -g kdpbot
kdpbot-mcp          # identical to `kdpbot mcp`
```

Both binaries ship in the same package, so one install gives you the CLI **and** the MCP server.

---

## Configure your client

### Claude Desktop

`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows:

```json
{
  "mcpServers": {
    "kdpbot": {
      "command": "npx",
      "args": ["-y", "kdpbot", "mcp"]
    }
  }
}
```

Restart Claude Desktop. The KDPBot tools appear in the tool menu.

### Claude Code

```bash
claude mcp add kdpbot -- npx -y kdpbot mcp
```

### Any other MCP client

The generic shape is the same everywhere — a command, its arguments, and an optional environment block:

```json
{
  "mcpServers": {
    "kdpbot": {
      "command": "npx",
      "args": ["-y", "kdpbot", "mcp"],
      "env": {
        "KDPBOT_TOKEN": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "KDPBOT_MCP_DOWNLOAD_DIR": "/Users/you/Books"
      }
    }
  }
}
```

---

## Authentication

The server reads credentials from the same places the CLI does, in this order:

1. **`--token <jwt>`** on the command line.
2. **`KDPBOT_TOKEN`** in the environment — the access token from `POST /api/auth/login/`. Best for a client config, and for CI.
3. **The stored CLI login** — `~/.config/kdpbot/config.json` (`$XDG_CONFIG_HOME/kdpbot/config.json`), written by:

   ```bash
   npx -y kdpbot login
   ```

   This is the friendliest setup: log in once in a terminal and the MCP server picks the session up, including the refresh token, so it renews itself instead of expiring after 24 hours. A `KDPBOT_TOKEN` has no refresh token behind it and stops working when it expires.

Two tools — `kdpbot_list_plans` and `kdpbot_get_demo_book` — need no credentials at all, so a client can explore KDPBot before anyone signs in.

If an authenticated tool is called with nothing signed in, it returns a tool error (not a crash) saying exactly that:

```
[AUTHENTICATION_REQUIRED] No KDPBot credentials are available to this MCP server.
Resolution: Sign in once with `kdpbot login` in a terminal (the token is stored in
~/.config/kdpbot/config.json and picked up here), or set KDPBOT_TOKEN=<access token>
in the "env" block of this server's MCP client configuration.
```

### Options and environment

| Flag | Environment variable | Default | Meaning |
| --- | --- | --- | --- |
| `--token <jwt>` | `KDPBOT_TOKEN` | stored login | Bearer access token |
| `--api <url>` | `KDPBOT_API_URL` | `https://bookgen-backend-5zuf.onrender.com/api` | API base URL |
| `--profile <name>` | `KDPBOT_PROFILE` | `default` | Named credential profile |
| `--download-dir <path>` | `KDPBOT_MCP_DOWNLOAD_DIR` | working directory | Where `kdpbot_export_book` saves files |
| `--timeout <seconds>` | — | `120` | Per-request network timeout |

Exports, outline generation and cover generation get longer budgets automatically (600 s and 300 s), because the server builds those synchronously.

---

## Tools

Eleven tools, in pipeline order. Every one returns two text blocks: a one-line summary with the recommended next step, and the full JSON payload.

### `kdpbot_list_plans` — *no credentials needed*

Every subscription plan with prices and per-plan limits (books per month, maximum words per book, unlocked export formats). Use it to answer "what does this cost" and "why is EPUB blocked". For what *this* account may do right now, use `kdpbot_check_quota`.

*No arguments.*

### `kdpbot_get_demo_book` — *no credentials needed*

The public demo book: a real, finished KDPBot book, returned without a login. The fastest way to see the exact shape of a book object before writing anything against it.

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `include_content` | boolean | no | Include the full text of every chapter. Default `false`; the response is large when on. |

### `kdpbot_check_quota`

Can this account start a book right now, and how long may it be? Returns `can_create` plus one option per balance the book could be charged to (subscription allowance, purchased credits), each with its own price, word ceiling and availability. Call it before creating or generating — it turns a 402 mid-pipeline into a decision made up front.

*No arguments.*

### `kdpbot_list_books`

Every book on the account, with id, title, genre, status, chapter counts and word count. The API returns a bare array (there is no pagination); the filters below are applied locally.

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | string | no | One of `draft`, `outline`, `generating`, `completed`, `published` |
| `limit` | integer | no | Return at most this many books (1–200) |

### `kdpbot_get_book`

One book in full: metadata plus every chapter with its number, title, summary, target and actual word count, and whether it has been written.

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `book_id` | integer | **yes** | Numeric book id |
| `include_content` | boolean | no | Include the written prose of every chapter. Default `false`, which returns each chapter's character count instead. |

A book owned by another account returns `NOT_FOUND`, not a permission error.

### `kdpbot_create_book`

Step 1. Stores the brief; writes no chapters and costs no generation quota. Returns the book including its `id` — every later call needs it.

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | string | **yes** | Book title, max 255 characters |
| `subtitle` | string | no | Printed under the title |
| `genre` | string | no | `fiction`, `non-fiction`, `mystery`, `romance`, `sci-fi`, `fantasy`, `biography`, `self-help`, `business`, `other`. Default `fiction`. |
| `target_audience` | string | no | Who the book is for, in prose. Steers tone and reading level. |
| `description` | string | no | A paragraph on what the book covers — the main brief the model writes from |
| `chapter_count` | integer | no | Default 10 |
| `target_word_count` | integer | no | Total words for the **whole book**. Default 40000. Capped by the plan. |
| `writing_style` | string | no | Free-text style guide. Use this field — `tone` and `writingStyle` are accepted by the API but not persisted. |
| `content_language` | string | no | `en`, `es`, `de`, `fr`, `it`, `pt`, `ru`, `uk`, `pl`, `zh`, `ja`, `ko`, `ar`, `hi`, `nl`, `sv`, `tr`, `id`. Default `en`. |
| `trim_size` | string | no | KDP trim size, e.g. `6x9` (default), `5x8`, `8.5x11` |

### `kdpbot_generate_outline` — ⚠️ destructive

Step 2. Generates the chapter outline — and **deletes every chapter the book already has**, including generated prose and manual edits. There is no undo.

It refuses to run unless `confirm` is exactly `true`, and the refusal happens before any network call, so a mistaken call changes nothing. Only set `confirm: true` after the user has agreed to lose the existing chapters. On a brand-new book there is nothing to lose, so this is simply the normal next step.

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `book_id` | integer | **yes** | Numeric book id |
| `confirm` | boolean | **yes** | Must be `true`. Acknowledges the permanent deletion of existing chapters. |

Synchronous; takes up to a minute.

### `kdpbot_start_generation`

Step 3, and the one that spends the book quota. Requires an outline. Returns immediately — generation runs in a background worker and a full-length book takes 10–40 minutes. Do not call it again while a run is in flight; that returns `GENERATION_IN_PROGRESS`.

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `book_id` | integer | **yes** | Numeric book id |

### `kdpbot_generation_status`

Step 4. Live progress: `generation_status` (`running` / `paused` / `stopped` / `completed` / `error`), percentage, completed vs total chapters, current step, timings, and a per-chapter snapshot. Poll every 15–30 seconds; chapters land minutes apart, so faster polling only burns rate limit.

A book that has never been generated reports `generation_status: "not_started"` — that is normal, not an error.

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `book_id` | integer | **yes** | Numeric book id |

### `kdpbot_export_book`

Step 5. Builds a KDP-ready file and **saves it to disk on the machine running the server**, returning the absolute path, the size and the media type. Export only once the status is `completed`; exporting mid-run produces a book with empty chapters.

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `book_id` | integer | **yes** | Numeric book id |
| `format` | string | **yes** | `pdf` (print interior), `docx` (editable manuscript), `epub` (ebook) |
| `output_dir` | string | no | Directory to save into. Defaults to `KDPBOT_MCP_DOWNLOAD_DIR`, else the server's working directory. |

DOCX and EPUB are plan-gated and the Free plan watermarks PDFs, so a 402 here means *upgrade*, not *retry*.

### `kdpbot_generate_cover`

Two AI cover variants, returned as image URLs. Spends image credits; blocked on the Free plan. It does **not** set the book's cover — the user picks a variant at `https://kdpbot.com/app/books/{id}`. Cover geometry comes from the book's `trim_size`, so set that at creation time.

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `book_id` | integer | **yes** | Numeric book id |
| `description` | string | no | Art direction: subject, mood, palette, style |
| `author_name` | string | no | Byline to print on the artwork |
| `show_title` | boolean | no | Render the title. Default `true`. |
| `show_subtitle` | boolean | no | Render the subtitle. Default `true`. |

---

## Errors

Two mechanisms, as the MCP specification prescribes.

**Protocol errors** (JSON-RPC `error`) are structural — an unknown tool, a method that does not exist, `arguments` that are not an object:

```json
{ "jsonrpc": "2.0", "id": 7, "error": { "code": -32602, "message": "Unknown tool: kdpbot_write_my_novel" } }
```

**Tool errors** (`"isError": true` inside a normal result) are everything a model can act on — missing credentials, a refused destructive call, a bad field value, and every API failure. They carry the KDPBot error code, the HTTP status, a resolution hint and the documentation link, so the model can correct itself instead of the client swallowing an exception:

```json
{
  "jsonrpc": "2.0",
  "id": 8,
  "result": {
    "isError": true,
    "content": [{
      "type": "text",
      "text": "[PLAN_UPGRADE_REQUIRED] EPUB export is not included in the Free plan.\nHTTP status: 402\nResolution: This action is not included in your plan — see https://kdpbot.com/pricing\nDetails: {\n  \"required_plan\": \"professional\"\n}\nDocumentation: https://kdpbot.com/docs/errors.md"
    }]
  }
}
```

Branch on the code in brackets, never on the prose. The full code list is in [errors.md](https://kdpbot.com/docs/errors.md).

---

## Worked example — a book end to end

What a capable agent does, in order, with the user's answers in between.

**1. Check there is allowance before promising anything**

```
kdpbot_check_quota {}
→ Yes — a book can be started now. 2 usable sources, best word ceiling 60000,
  suggested source "subscription".
```

**2. Create the book**

```
kdpbot_create_book {
  "title": "Mindful Leadership",
  "subtitle": "Building High-Performance Teams with Empathy",
  "genre": "business",
  "target_audience": "First-time engineering managers at 50-500 person companies",
  "description": "A practical guide to leading with empathy without losing velocity...",
  "chapter_count": 10,
  "target_word_count": 38000,
  "writing_style": "Warm, direct, second person. Short paragraphs, one worked example per chapter.",
  "trim_size": "6x9"
}
→ Created book 2001 "Mindful Leadership" (business, 10 chapters, 38000 target words,
  status draft). Next: kdpbot_generate_outline with book_id 2001 and confirm true.
```

**3. Outline — after saying out loud what it destroys**

The book is new, so nothing is lost; on an existing book, ask first.

```
kdpbot_generate_outline { "book_id": 2001, "confirm": true }
→ Outline ready for book 2001 "Mindful Leadership": 10 chapters.
  The chapters are empty until you call kdpbot_start_generation.
```

Show the chapter titles to the user and let them object before spending the quota.

**4. Write it**

```
kdpbot_start_generation { "book_id": 2001 }
→ Generation queued for book 2001 (mode: async). Poll kdpbot_generation_status
  with book_id 2001 every 15-30 seconds; a full-length book takes 10-40 minutes.
```

**5. Poll, patiently**

```
kdpbot_generation_status { "book_id": 2001 }
→ Book 2001: running, 30%, 3/10 chapters. Writing chapter 4 of 10.
  Still working — poll again in 15-30 seconds.

...later...

kdpbot_generation_status { "book_id": 2001 }
→ Book 2001: completed, 100%, 10/10 chapters. Finished in 22m 14s —
  next: kdpbot_export_book.
```

**6. Cover art (optional, spends image credits)**

```
kdpbot_generate_cover {
  "book_id": 2001,
  "description": "Minimal typographic cover, deep navy, single geometric mark, no photography",
  "author_name": "Igor Wise"
}
→ Two cover variants ready for book 2001. Variant 1: https://... · Variant 2: https://...
  The user selects one at https://kdpbot.com/app/books/2001.
```

**7. Export**

```
kdpbot_export_book { "book_id": 2001, "format": "pdf" }
→ Exported book 2001 as PDF — 2841203 bytes written to
  /Users/you/Books/mindful-leadership.pdf.
```

Hand the user the path. Upload that file to KDP.

---

## Notes for implementers

- **stdout carries only protocol frames.** Every diagnostic goes to stderr, and the server re-points `process.stdout.write` at stderr while it runs, so even a stray `console.log` from elsewhere in the process cannot corrupt the stream.
- **One frame per line.** `JSON.stringify` escapes newlines, so no frame ever contains a raw one.
- **No JSON-RPC batching.** It was removed from MCP in revision 2025-06-18; array messages are rejected with `-32600`.
- **Two protocol eras, decided per request.** A request whose `params._meta` carries `io.modelcontextprotocol/protocolVersion` — or any `server/discover` — is served under revision `2026-07-28`: statelessly, with `resultType: "complete"` and `io.modelcontextprotocol/serverInfo` on every result. Anything else goes to the `initialize` handshake path and behaves exactly as it did before. The two never mix.
- **`server/discover`** (a MUST in `2026-07-28`) returns `supportedVersions`, `capabilities`, `instructions` and the `ttlMs`/`cacheScope` caching hints. It is also the stdio backward-compatibility probe: a dual-era client sends it first, and a `DiscoverResult` tells it this server is modern.
- **Version negotiation.** Modern: a revision this server cannot serve is refused with `UnsupportedProtocolVersionError` (`-32022`) whose `data.supported` lists what it does speak — never a silent downgrade. A `_meta` block missing `io.modelcontextprotocol/protocolVersion` or `io.modelcontextprotocol/clientCapabilities` is `-32602`. Legacy: `initialize` echoes your `protocolVersion` when it is a handshake revision this server speaks, and otherwise answers with `2025-11-25`.
- **Requests are handled in arrival order.** Responses come back in the same order.
- **Reuses the CLI's HTTP client**, so bearer auth, the transparent refresh-and-retry on 401, error normalisation and safe `.part`-file downloads behave identically to `kdpbot` on the command line.

Source: [`cli/src/mcp/`](https://github.com/allidevelop/kdpbot_claude20/tree/main/cli/src/mcp) · Issues: <https://github.com/allidevelop/kdpbot_claude20/issues>
