Skip to main content
Glama
README.md
# gitbook-mcp

A **[Model Context Protocol](https://modelcontextprotocol.io) server for GitBook.**
It lets an MCP host (Claude Code, Claude Desktop, Cursor, …) **read** your GitBook
content and drive a **change-request write workflow**, over **stdio** or
**streamable HTTP**.

Built on the official [`@gitbook/api`](https://www.npmjs.com/package/@gitbook/api)
client and [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk) 1.x.

- **Full API coverage:** ~345 tools across 11 config-gated **toolsets**. The default (`core` + `change-requests`) keeps the surface small; write and destructive tools are gated by read-only mode plus an opt-in.
- **Resources:** pages addressable as `gitbook://{spaceId}/{pageId}`.
- **Two transports:** stdio (local) and streamable HTTP (multi-session, bearer-gated).
- **Resilient:** request timeouts, bounded retries with full-jitter backoff, rate-limit handling.
- **Safe:** read-only mode, env-only token, secret redaction, localhost-bound HTTP.

> **Status:** published as [`@hoyongjin/gitbook-mcp`](https://www.npmjs.com/package/@hoyongjin/gitbook-mcp)
> (v2.0.0), with the original 11 tools enabled by default. Covered by unit,
> MCP-protocol, and manifest-contract tests. The stdio transport suits local
> IDE/CLI use. The HTTP transport adds bearer auth, DNS-rebinding protection, a
> session cap with idle reaper, per-IP rate limiting, health/readiness probes, and
> Prometheus metrics for hosted use.

---

## ⚠️ What GitBook's API can and cannot write

GitBook's public API has **no direct "edit this page body" endpoint**
(`createPage`/`updatePage` exist only as the GitBook Assistant's _internal_ tool
names, not as REST methods). Content authoring goes through:

1. **Change requests**: open a draft (`gitbook_create_change_request`), review, then `gitbook_merge_change_request` to publish.
2. **Content import**: `gitbook_import_content` imports a **web page URL** into a space/change-request/page, AI-enhanced by default. This is the supported "write content" primitive. Imports are asynchronous, and GitBook exposes no status-poll endpoint.
3. **Git Sync**: for fine-grained, paragraph-level authoring, connect the space to a Git repo and edit markdown there. _(Out of scope for this server; it is the right path if you need precise prose edits.)_

So the server exposes many reads and a single coarse, review-gated write path. It
cannot rewrite a specific paragraph through the API.

---

## Install

```bash
# From source (local use):
cd gitbook-mcp && npm install && npm run build
# → run with:  node dist/index.js   (or `npm start`)

# Or, once published:
npx @hoyongjin/gitbook-mcp
```

> The package is **scoped** to `@hoyongjin/gitbook-mcp` (the unscoped `gitbook-mcp`
> on npm belongs to a different project). Make sure your npm account owns the
> `@hoyongjin` scope before publishing.

Create a token at **GitBook → Settings → Developer → Personal access tokens**.

## Configure in your MCP client

### Claude Code

```bash
# Local build:
claude mcp add gitbook \
  --env GITBOOK_TOKEN=gb_api_xxx \
  -- node /absolute/path/to/gitbook-mcp/dist/index.js
```

### `.mcp.json` / Claude Desktop config

```json
{
  "mcpServers": {
    "gitbook": {
      "command": "node",
      "args": ["/absolute/path/to/gitbook-mcp/dist/index.js"],
      "env": { "GITBOOK_TOKEN": "gb_api_xxx" }
    }
  }
}
```

(After publishing, replace `command`/`args` with
`"command": "npx", "args": ["@hoyongjin/gitbook-mcp"]`.)

Run read-only (recommended unless you need writes) by adding `"--read-only"` to
`args`, or `"GITBOOK_READONLY": "true"` to `env`.

## Configuration

All configuration is via environment variables (see [`.env.example`](./.env.example)).
The token is **never** taken from a CLI argument.

| Variable                               | Default                   | Description                                                                                                                            |
| -------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `GITBOOK_TOKEN`                        | —                         | **Required.** Personal access token (account-wide privileges).                                                                         |
| `GITBOOK_ENDPOINT`                     | `https://api.gitbook.com` | API base URL.                                                                                                                          |
| `GITBOOK_READONLY`                     | `false`                   | Hide all write/destructive tools when true.                                                                                            |
| `GITBOOK_TOOLSETS`                     | `core,change-requests`    | Comma-separated toolsets to enable, or `all`. Unset/empty → the default two. Unknown key fails closed. See [Toolsets](#tools).         |
| `GITBOOK_ALLOW_DESTRUCTIVE`            | `false`                   | Opt-in to register new destructive ops (deletes, unpublish, admin removals). The legacy `merge` tool is exempt.                        |
| `GITBOOK_TRANSPORT`                    | `stdio`                   | `stdio` or `http`.                                                                                                                     |
| `GITBOOK_HTTP_HOST`                    | `127.0.0.1`               | HTTP bind host.                                                                                                                        |
| `GITBOOK_HTTP_PORT`                    | `3000`                    | HTTP port.                                                                                                                             |
| `GITBOOK_HTTP_AUTH_TOKEN`              | —                         | Bearer token required on the HTTP transport.                                                                                           |
| `GITBOOK_HTTP_MAX_SESSIONS`            | `256`                     | Concurrent-session cap; new initializes past it get 503 (1–100000).                                                                    |
| `GITBOOK_HTTP_SESSION_TTL_MS`          | `300000`                  | Idle-session reaper TTL (1 000–86 400 000).                                                                                            |
| `GITBOOK_HTTP_SESSION_MAX_LIFETIME_MS` | `3600000`                 | Absolute session lifetime; reaped regardless of activity.                                                                              |
| `GITBOOK_HTTP_TRUST_PROXY`             | `false`                   | Trust `X-Forwarded-*` (enable only behind a trusted reverse proxy).                                                                    |
| `GITBOOK_HTTP_ALLOWED_HOSTS`           | —                         | Comma-separated hosts appended to the DNS-rebinding allow-list. **Required for proxied/non-loopback access** (e.g. `mcp.example.com`). |
| `GITBOOK_HTTP_ALLOWED_ORIGINS`         | —                         | Comma-separated origins appended to the allow-list (e.g. `https://mcp.example.com`).                                                   |
| `GITBOOK_HTTP_RATE_LIMIT_WINDOW_MS`    | `60000`                   | Rate-limit window per client IP.                                                                                                       |
| `GITBOOK_HTTP_RATE_LIMIT_MAX`          | `120`                     | Max `/mcp` requests per window per IP; `0` disables.                                                                                   |
| `GITBOOK_LOG_LEVEL`                    | `info`                    | `debug`/`info`/`warn`/`error` (logs → stderr).                                                                                         |
| `GITBOOK_TIMEOUT_MS`                   | `30000`                   | Per-request timeout (1 000–120 000).                                                                                                   |
| `GITBOOK_MAX_RETRIES`                  | `5`                       | Max retries on 429/5xx/transport errors (0–10).                                                                                        |
| `GITBOOK_MAX_CONCURRENCY`              | `8`                       | Max concurrent outbound GitBook HTTP requests per instance (1–256).                                                                    |

CLI flags: `--stdio` / `--http` (transport), `--read-only`, `--toolsets <csv|all>`,
`--allow-destructive`, `--port <n>`.

## Tools

The server covers the full GitBook API (~345 tools), organized into **toolsets**
you enable with `GITBOOK_TOOLSETS`. By default only `core` + `change-requests` are
on (~89 tools), so `tools/list` stays small and tool selection stays accurate.
Set `GITBOOK_TOOLSETS=all` for everything, or list the ones you need
(e.g. `GITBOOK_TOOLSETS=core,change-requests,sites`).

Every tool is one API endpoint with its own input schema. Read tools are always
available, write tools need `!readOnly`, and new destructive tools (deletes, unpublish,
admin removals) also need `GITBOOK_ALLOW_DESTRUCTIVE=true`.

| Toolset           | Default | Scope                                                                     |
| ----------------- | :-----: | ------------------------------------------------------------------------- |
| `core`            |   ✅    | Identity, orgs/spaces/pages reads, search, content + `import_content`.    |
| `change-requests` |   ✅    | Full CR loop: create, list/diff, reviews, reviewers, comments, **merge**. |
| `collections`     |         | Collections + collection permissions.                                     |
| `sites`           |         | Sites, structure, customization, redirects, share links, permissions.     |
| `site-ai`         |         | Ask AI, agent settings, questions, glossary-adjacent AI.                  |
| `site-insights`   |         | Analytics/insights, context connections, channels, site MCP servers.      |
| `integrations`    |         | Integration install/configure/token + space integrations.                 |
| `data`            |         | OpenAPI specs, translations, glossary, custom fonts, storage.             |
| `git`             |         | Git Sync: import/export, provider installations, repo/branch listing.     |
| `public`          |         | URL→content resolution, subdomains, custom hostnames.                     |
| `org-admin`       |         | **Org members, teams, invites, SSO, permissions** (highest privilege).    |

The **11 original tools keep their exact names** and live in `core` / `change-requests`,
so existing setups are unchanged. Per-tool parameters are delivered live via each tool's
MCP description + input schema.

**Excluded carve-outs:** 5 streaming (SSE) AI endpoints, 2 ephemeral PDF-URL generators,
and a few trivial auth/heartbeat ops are not exposed as tools (the non-streaming AI
equivalents are).

List tools (`*_list_*`, `search`) accept a `body` with `limit`/`page` and return
`nextCursor` when more results exist. Typical write flow:
`create_change_request` → `import_content` (target that CR) → review/diff → `merge_change_request`.

## Resources

Pages are exposed as resources at `gitbook://{spaceId}/{pageId}` (rendered as
markdown). Enumeration is intentionally not provided; discover page ids via
`gitbook_list_pages` / `gitbook_search`, then read by URI.

## Transports

- **stdio** (default): one server per process; the token stays in-process. Best
  for local IDE/CLI integrations.
- **streamable HTTP** (`--http`): one MCP session per `mcp-session-id`. Binds to
  `127.0.0.1` and enables DNS-rebinding protection (Host/Origin allow-lists). When
  `GITBOOK_HTTP_AUTH_TOKEN` is set, every request needs a constant-time-compared
  bearer token. Binding to a non-loopback host **without** an auth token is refused
  (fail-closed). Sessions are capped and idle-reaped; the `/mcp` endpoint is per-IP
  rate-limited. Operational endpoints: `GET /healthz` `/livez` `/readyz`
  (unauthenticated probes) and `GET /metrics` (Prometheus; bearer-gated when an auth
  token is set). Behind a TLS-terminating reverse proxy, set
  `GITBOOK_HTTP_TRUST_PROXY=true` **and** `GITBOOK_HTTP_ALLOWED_HOSTS=<your-public-host>`:
  DNS-rebinding protection matches the exact `Host` header, so a proxied or
  non-loopback deployment (including the Docker image, which binds `0.0.0.0`) must
  add its public host(s) or every request is rejected with 403 `Invalid Host`. A
  [`Dockerfile`](./Dockerfile) is provided.

```bash
GITBOOK_TOKEN=… GITBOOK_HTTP_AUTH_TOKEN=… gitbook-mcp --http --port 3000
# → POST/GET/DELETE http://127.0.0.1:3000/mcp   ·   GET /healthz   ·   GET /metrics
```

## Development

```bash
npm install
npm run typecheck   # tsc on src + test
npm run lint        # eslint
npm run format      # prettier --write   (format:check verifies)
npm test            # vitest: unit + protocol + manifest-contract tests
npm run generate:manifest  # regenerate src/tools/manifest.ts from @gitbook/api (CI checks drift)
npm run test:coverage  # + v8 coverage with an 80% gate
npm run build       # tsc → dist/ (+ chmod the bin)
```

CI (`.github/workflows/ci.yml`) runs `npm audit` → typecheck → lint → format:check
→ build → coverage on Node 20 & 22, then asserts a clean `git diff`. CodeQL and
Dependabot run separately.

## Architecture

```
src/
  index.ts            launcher: load config → pick transport → graceful shutdown
  config.ts           zod-validated env + CLI overrides (frozen Config)
  logger.ts           structured stderr logging + secret redaction + correlation
  request-context.ts  AsyncLocalStorage requestId/tool (log correlation)
  metrics.ts          in-process Prometheus registry (served at /metrics)
  limiter.ts          async concurrency semaphore (outbound rate control)
  server.ts           createServer(): McpServer factory (tools + resources)
  resources.ts        gitbook://{spaceId}/{pageId}
  version.ts          stable SERVER_NAME + runtime SERVER_VERSION (from package.json)
  gitbook/
    client.ts         typed wrapper over @gitbook/api (cursor pagination → {items,nextCursor})
    resilient-fetch.ts timeouts + retry/backoff + concurrency cap; injected as the client's fetch
    import-url.ts     URL guard for server-side-fetch ops (http(s) only, no credentials)
    errors.ts         HTTP/transport error classification → safe messages
  tools/
    manifest.ts       GENERATED tool surface (one row per API op; source of truth)
    toolsets.ts       toolset keys + defaults
    index.ts          manifest-driven registration under toolset/read-only/destructive gates
    factory.ts        generic handler factory (every non-bespoke op)
    bespoke.ts        the 11 grandfathered tools (hand-written handlers)
    shared.ts         ToolContext + result/guard helpers + annotation presets
  transports/
    stdio.ts http.ts  the two transports (http: sessions, rate limit, health, metrics)
```

### Notes / gotchas (pinned to @gitbook/api 0.183.0)

- **tsconfig uses `moduleResolution: "Bundler"`**, not `NodeNext`. `@gitbook/api`
  0.183 ships a `.d.ts` that re-exports with extensionless relative specifiers
  (`export * from './client'`); under NodeNext those don't resolve and the entire
  client surface (and the `GitBookAPI` namespace members) silently disappears.
  Bundler resolution accepts them, giving full typing with no facade. Internal
  imports use explicit `.js` extensions so the emitted ESM runs on Node.
- **Import runs live under the singular `client.org` namespace**, not the plural
  `client.orgs` (which holds list/search). They are different runtime objects.
- **`gitbook_get_page` markdown vs document**: `format=markdown` returns rendered
  markdown; `format=document` returns GitBook's structured Document JSON.
- Node ≥ 20 (global `fetch`, `AbortSignal.any`).

## License

[MIT](./LICENSE)

TDQS

B3.2/5.0

Scored across 89 tools

Disambiguation3/5

Many tools have similar names (e.g., list_*_backlinks for different scopes), but descriptions help differentiate. However, with 89 tools, the sheer volume makes it hard for an agent to quickly select the right one, especially without clear categorization.

Naming Consistency5/5

All tools adhere to a strict 'gitbook_verb_noun' pattern, with consistent use of prepositions like 'in', 'by', 'for'. No mixing of conventions, making the naming predictable across the entire set.

Tool Count2/5

89 tools is excessive for a documentation tool. Many tools handle granular sub-resources (e.g., backlinks, meta-links in specific contexts) that could be consolidated. This makes the surface feel bloated and hard to navigate.

Completeness3/5

Covers most read and manage operations for spaces, change requests, comments, and files. However, direct content creation/editing is limited to an asynchronous import tool; no create/update page or document body tools exist, leaving a notable gap.

Maintenance

ActivityStale
ResponsivenessNo issues