Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 87 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# BookStack MCP Server

Connect BookStack to Claude and other AI assistants through the Model Context Protocol (MCP). This server exposes 59 tools, 5 resources and 6 resource templates covering the supported subset of the BookStack API — books, pages, chapters, shelves, search, users, roles, permissions, attachments, images, the recycle bin, the audit log and system info.
Connect BookStack to Claude and other AI assistants through the Model Context Protocol (MCP). This server exposes 71 tools, 5 resources and 6 resource templates covering the supported subset of the BookStack API — books, pages, chapters, shelves, search, users, roles, permissions, attachments, images, comments, ZIP imports, tags, the recycle bin, the audit log and system info.

This server supports two transport modes: **Streamable HTTP** (default) and **Stdio**.

Expand All @@ -10,16 +10,89 @@ This server supports two transport modes: **Streamable HTTP** (default) and **St
> ⚠️ **Looking for the HTTP endpoint?** The MCP endpoint is `POST /message` — not `/`.
> See [Transports](#-transports) and [HTTP endpoints](#http-endpoints) below.

## 🍴 About this fork

This is a fork of [pnocera/bookstack-mcp-server](https://github.com/pnocera/bookstack-mcp-server),
maintained at [cego/bookstack-mcp-server](https://github.com/cego/bookstack-mcp-server). Its
primary use is **per-user OAuth**: each person signs in to their AI assistant with your OIDC
provider, and BookStack sees *their* account, permissions and audit trail instead of one shared
API token.

What the fork adds on top of upstream:

- **Per-user OAuth** (`MCP_AUTH_MODE=oauth`): the HTTP transport is an OAuth resource server per
the MCP authorization spec. Access tokens must be issued for this server, and each one is
exchanged (RFC 8693) for a BookStack token, so the caller's own token is never forwarded.
See [Per-user OAuth](#per-user-oauth) for the full flow and requirements.
- **More tools**: comments, ZIP imports, tags and ZIP export.
- **Hardening** from a full security review: allowlisted upstream overrides, guarded and
size-limited uploads, linear-time page parsing, and no stack traces or paths in errors.
- **Container images** instead of npm releases.

### Run it with OAuth

Images are published as `ghcr.io/cego/bookstack-mcp-server:<version>-r<revision>`, where
`<version>` is the upstream version the fork builds on and `<revision>` counts the fork's
releases of it. See [Releases](https://github.com/cego/bookstack-mcp-server/releases) for the
current tag and digest, and pin the digest when you deploy.

```bash
# bookstack-mcp.env - keep it out of version control; it holds the client secret
BOOKSTACK_BASE_URL=https://bookstack.example.com/api
MCP_AUTH_MODE=oauth
MCP_OAUTH_ISSUER=https://idp.example.com
MCP_OAUTH_RESOURCE=https://bookstack-mcp.example.com/message
MCP_OAUTH_CLIENT_ID=bookstack-mcp-server
MCP_OAUTH_CLIENT_SECRET=<exchange client secret>
BOOKSTACK_OAUTH_AUDIENCE=bookstack-api
```

```bash
docker run -d --name bookstack-mcp -p 3000:3000 --env-file bookstack-mcp.env \
ghcr.io/cego/bookstack-mcp-server:<version>-r<revision>
```

Before it works end to end you need:

1. **A BookStack that accepts OIDC access tokens on its API** (`OIDC_API_ACCESS_TOKENS=true`,
from [BookStack PR 6237](https://codeberg.org/bookstack/bookstack/pulls/6237)), with
`OIDC_API_AUDIENCE` set to `BOOKSTACK_OAUTH_AUDIENCE` and `OIDC_API_ALLOWED_CLIENTS` set to
`MCP_OAUTH_CLIENT_ID`. Users must have logged in to BookStack once.
2. **Two OAuth clients at your provider**: one that users sign in with, whose access tokens carry
`MCP_OAUTH_RESOURCE` and `MCP_OAUTH_CLIENT_ID` (but not BookStack's audience) in `aud`, and
the confidential exchange client `MCP_OAUTH_CLIENT_ID`, held only by this server, allowed to
exchange those tokens for `BOOKSTACK_OAUTH_AUDIENCE`.
3. **The server reachable at `MCP_OAUTH_RESOURCE`**, including
`/.well-known/oauth-protected-resource/message` on the same origin.

Then connect your assistant with the **sign-in** client, never the exchange client:

```bash
# Claude Code
claude mcp add --transport http --client-id <sign-in client id> --client-secret \
--callback-port <port> bookstack https://bookstack-mcp.example.com/message
```

For a Claude custom connector, use `https://bookstack-mcp.example.com/message` as the URL and
the sign-in client's ID (and secret, if it is confidential) under *Advanced settings*.

The rest of this README is upstream's documentation, kept current for this fork. The
shared-token and stdio modes below still work.

## ✨ What You Get

- **BookStack Integration** - Access your books, pages, chapters, and content
- **59 MCP Tools, 5 Resources & 6 Resource Templates** - CRUD, search and export across the supported endpoint families
- **71 MCP Tools, 5 Resources & 6 Resource Templates** - CRUD, search and export across the supported endpoint families
- **Search & Export** - Find content and export in multiple formats
- **User Management** - Handle users, roles, and permissions
- **Production Ready** - Rate limiting, validation, error handling, and logging

## 🚀 Quick Start

> ℹ️ `bunx`/`bun add` below install **upstream's** npm release, which lacks this fork's
> changes. For the fork, use the container image ([Run it with OAuth](#run-it-with-oauth)) or
> run from a clone (`bun install && bun run start`).

> ⚠️ **Requires [Bun](https://bun.sh) 1.1.0 or newer. Node.js is not supported.**
> This package ships TypeScript source rather than a compiled bundle, and its
> executable starts with `#!/usr/bin/env bun` — Bun must be installed on the
Expand All @@ -44,7 +117,7 @@ bookstack-mcp-server

The two tokens are **not** interchangeable and must not be set to the same value:
`BOOKSTACK_API_TOKEN` is what the server presents to BookStack; `MCP_AUTH_TOKEN` is
what callers must present to `POST /message`, which dispatches all 59 tools with the
what callers must present to `POST /message`, which dispatches all 71 tools with the
authority of the BookStack account behind `BOOKSTACK_API_TOKEN`. Skip `MCP_AUTH_TOKEN`
only for [stdio](#-transports), which has no network surface and ignores it.

Expand Down Expand Up @@ -99,7 +172,7 @@ export MCP_TRANSPORT="http"
| `BOOKSTACK_API_TOKEN` | _(none — required, unless `MCP_AUTH_MODE=oauth`)_ | **Outbound** BookStack API token as `token_id:token_secret`. This is the credential the server spends on every tool call. Startup fails if unset. |
| `BOOKSTACK_TIMEOUT` | `30000` | BookStack request timeout in milliseconds. |
| `BOOKSTACK_ALLOWED_BASE_URLS` | _(none)_ | Comma-separated BookStack API base URLs a caller may name in the `x-bookstack-url` header, compared canonically. Unset refuses that header. See [per-request overrides](#per-request-overrides). |
| `BOOKSTACK_UPLOAD_ROOT` | _(none)_ | Directory the image/attachment tools' `file_path` may read from. Unset refuses `file_path` under every transport, stdio included; the path must resolve inside this directory. Must be unset with `MCP_AUTH_MODE=oauth`. |
| `BOOKSTACK_UPLOAD_ROOT` | _(none)_ | Directory the image, attachment and import tools' `file_path` may read from. Unset refuses `file_path` under every transport, stdio included; the path must resolve inside this directory. Must be unset with `MCP_AUTH_MODE=oauth`. |
| `SERVER_PORT` | `3000` | Port the HTTP transport listens on; if it is taken, startup fails with exit code `1`. Ignored in stdio mode. |
| `HTTP_BODY_LIMIT` | `73400320` (70 MiB) | Maximum accepted `POST /message` body, in bytes. Sized for the largest inline base64 upload the image/attachment tools advertise (50,000 KB). Express's own default is ~100 KB, which would reject real uploads with a `413`. Lower it if untrusted callers can reach the port. |
| `SERVER_NAME` | `bookstack-mcp-server` | Server name reported over MCP and by `GET /`. |
Expand Down Expand Up @@ -178,7 +251,7 @@ with the failing check named:
"status": "unhealthy",
"checks": [
{ "name": "bookstack_connection", "healthy": false, "message": "BookStack API connection" },
{ "name": "tools_loaded", "healthy": true, "message": "59 tools loaded" },
{ "name": "tools_loaded", "healthy": true, "message": "71 tools loaded" },
{ "name": "resources_loaded", "healthy": true, "message": "5 resources and 6 resource templates loaded" }
]
}
Expand Down Expand Up @@ -301,9 +374,9 @@ MCP clients pipe over stdin/stdout. For stdio you also don't need `-p 3000:3000`

## 🛠️ Available Tools

**59 tools across 13 categories:**
**71 tools across 16 categories:**

- **📚 Books** (6) - Create, read, update, delete, and export books
- **📚 Books** (6) - Create, read, update, delete, and export books (html, pdf, plaintext, markdown, zip)
- **📄 Pages** (9) - Manage pages with HTML/Markdown content, including [partial editing](#-partial-page-editing)
- **📑 Chapters** (6) - Organize pages within books
- **📚 Shelves** (5) - Group books into collections
Expand All @@ -315,10 +388,12 @@ MCP clients pipe over stdin/stdout. For stdio you also don't need `-p 3000:3000`
- **🗑️ Recycle Bin** (3) - Deleted item recovery
- **📎 Attachments** (5) - File attachments
- **🖼️ Images** (5) - Image gallery
- **💬 Comments** (5) - Page comments, replies and archiving
- **📦 Imports** (5) - Upload and run BookStack ZIP imports
- **🏷️ Tags** (2) - Tag names and values in use
- **🧭 Meta** (5) - Ask the server about its own tools and conventions

Not exposed (no tools): comments, imports, tag-name listings, the image-gallery
`data` endpoints, and `zip` export.
Not exposed (no tools): the image-gallery `data` endpoints.

> 📖 See the complete [Tools Overview](docs/tools-overview.md) for detailed documentation

Expand Down Expand Up @@ -517,8 +592,9 @@ This project is part of the BookStack ecosystem! Check out other API-based tools
## 🆘 Support

- **📚 Documentation**: Complete guides in the [docs/](docs/) folder
- **🐛 Issues**: [GitHub Issues](https://github.com/pnocera/bookstack-mcp-server/issues)
- **💬 Discussions**: [GitHub Discussions](https://github.com/pnocera/bookstack-mcp-server/discussions)
- **🐛 Issues with this fork** (OAuth mode, images, fork-only tools): [cego/bookstack-mcp-server issues](https://github.com/cego/bookstack-mcp-server/issues)
- **🐛 Upstream issues**: [pnocera/bookstack-mcp-server issues](https://github.com/pnocera/bookstack-mcp-server/issues)
- **💬 Upstream discussions**: [GitHub Discussions](https://github.com/pnocera/bookstack-mcp-server/discussions)

---

Expand Down
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ services:
start_period: 30s

bookstack:
# Pinned, not :latest. The tool contract in this repo (59 tools, field shapes,
# Pinned, not :latest. The tool contract in this repo (71 tools, field shapes,
# error codes) was verified against BookStack 26.05.2; a floating tag silently
# re-points at a future release and turns a BookStack change into a mystery
# failure in this suite. Currently resolves to v26.05.2-ls274.
Expand Down Expand Up @@ -80,7 +80,7 @@ services:
DB_USERNAME: bookstack
DB_PASSWORD: bookstack_secret
# BookStack throttles its REST API per user (default 180/min). The
# integration suites drive all 59 tools and share one admin token, so the
# integration suites drive the tools and share one admin token, so the
# default becomes the bottleneck. This is a throwaway test instance, so
# raise it. NOTE: real deployments keep the 180 default — that is why the
# client retries on 429 and the test harness rides out throttling.
Expand All @@ -105,7 +105,7 @@ services:
BOOKSTACK_API_TOKEN: ${BOOKSTACK_API_TOKEN:-}
# INBOUND authentication for POST /message - unrelated to the token above,
# and required: the container refuses to start without it, because
# /message dispatches all 59 tools (permanent-delete, users, roles,
# /message dispatches all 71 tools (permanent-delete, users, roles,
# permissions) using BOOKSTACK_API_TOKEN. Generate with `openssl rand -hex 32`.
MCP_AUTH_TOKEN: ${MCP_AUTH_TOKEN:-}
# Per-user OAuth instead of the two tokens above; see .env.example.
Expand Down
146 changes: 140 additions & 6 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
- [Roles](#roles-api)
- [Attachments](#attachments-api)
- [Images](#images-api)
- [Comments](#comments-api)
- [Imports](#imports-api)
- [Tags](#tags-api)
- [Search](#search-api)
- [Recycle Bin](#recycle-bin-api)
- [Permissions](#permissions-api)
Expand All @@ -29,13 +32,13 @@ The BookStack MCP Server provides comprehensive access to the BookStack knowledg

### Key Features

- **API Coverage**: 59 tools, 5 resources and 6 resource templates across 13 categories, covering the supported subset of the BookStack API (comments, imports, tag listings, image-gallery `data` endpoints and ZIP export are not exposed)
- **API Coverage**: 71 tools, 5 resources and 6 resource templates across 16 categories, covering the supported subset of the BookStack API (the image-gallery `data` endpoints are not exposed)
- **Type Safety**: Full TypeScript interfaces for all operations
- **Robust Error Handling**: Comprehensive error mapping and recovery guidance
- **Rate Limiting**: Token bucket algorithm with configurable limits
- **Validation**: Zod-based parameter validation, strict by default (`VALIDATION_STRICT_MODE=true`)
- **Retry Logic**: Automatic retry with exponential backoff
- **Export Capabilities**: Multi-format export (HTML, PDF, Markdown, Plain Text)
- **Export Capabilities**: Multi-format export (HTML, PDF, Markdown, Plain Text, ZIP)

### Architecture

Expand Down Expand Up @@ -275,13 +278,15 @@ interface BookWithContents extends Book {
// Tool: bookstack_books_export
interface ExportParams {
id: number;
format: 'html' | 'pdf' | 'plaintext' | 'markdown';
format: 'html' | 'pdf' | 'plaintext' | 'markdown' | 'zip';
}

interface ExportResult {
content: string; // Base64 encoded for binary formats
filename: string; // Suggested filename
mime_type: string; // MIME type for the content
content: string; // The text, or base64 of the bytes for pdf and zip
encoding: 'utf8' | 'base64'; // base64 for pdf and zip
byte_length: number; // Size of the exported file in bytes
filename: string; // Suggested filename
mime_type: string; // e.g. application/zip for zip
}
```

Expand Down Expand Up @@ -779,6 +784,135 @@ interface UpdateImageParams {
// Permanently deletes image
```

### Comments API

Comments on pages. `local_id` numbers a comment within its page, and `parent_id` and
`reply_to` refer to that page-scoped number rather than the global `id`.

#### List Comments
```typescript
// Tool: bookstack_comments_list
// Entries carry neither html nor archived; read a comment for those
interface CommentsListParams extends PaginationParams {
filter?: {
commentable_id?: number; // Page ID
commentable_type?: 'page';
parent_id?: number; // local_id of the parent comment
local_id?: number;
content_ref?: string;
created_by?: number;
updated_by?: number;
};
}
```

#### Create Comment
```typescript
// Tool: bookstack_comments_create
// Needs the comment-create-all permission
interface CreateCommentParams {
page_id: number; // Required
html: string; // Required, not blank
reply_to?: number; // local_id of the comment to reply to
content_ref?: string; // 'bkmrk-<element id>:<hash>:<start>-<end>', max 255 chars
}
```

#### Read Comment
```typescript
// Tool: bookstack_comments_read
// Returns the comment with html, archived and its direct replies
```

#### Update Comment
```typescript
// Tool: bookstack_comments_update
interface UpdateCommentParams {
id: number;
html?: string; // Replaces the content
archived?: boolean; // Top-level comments only
}
```

#### Delete Comment
```typescript
// Tool: bookstack_comments_delete
// Permanently deletes the comment
```

### Imports API

Imports of BookStack's portable ZIP format. All import tools need the `content-import`
permission.

#### List Imports
```typescript
// Tool: bookstack_imports_list
interface ImportsListParams extends PaginationParams {
filter?: {
name?: string;
size?: number; // Bytes
type?: 'book' | 'chapter' | 'page';
created_by?: number;
};
}
```

#### Create Import
```typescript
// Tool: bookstack_imports_create
// Uploads and validates the ZIP as multipart; nothing is created until it is run
type CreateImportParams =
| { file: string } // Base64 encoded ZIP
| { file_path: string }; // Server-local path inside BOOKSTACK_UPLOAD_ROOT
```

#### Read Import
```typescript
// Tool: bookstack_imports_read
// Returns the pending import with `details` describing its content
```

#### Run Import
```typescript
// Tool: bookstack_imports_run
// Returns the created book, chapter or page, and deletes the import
interface RunImportParams {
id: number;
parent_type?: 'book' | 'chapter'; // Required with parent_id for chapter and page imports
parent_id?: number;
}
```

#### Delete Import
```typescript
// Tool: bookstack_imports_delete
// Permanently deletes a pending import without running it
```

### Tags API

Read-only listings of the tags on content visible to the authenticated user.

#### List Tag Names
```typescript
// Tool: bookstack_tags_list_names
interface TagNamesListParams extends PaginationParams {
filter?: { name?: string };
}
// Entries: { name, values, usages, page_count, chapter_count, book_count, shelf_count }
```

#### List Tag Values
```typescript
// Tool: bookstack_tags_list_values
interface TagValuesListParams extends PaginationParams {
name: string; // Required tag name
filter?: { value?: string };
}
// Entries: { name, value, usages, page_count, chapter_count, book_count, shelf_count }
```

### Search API

Universal search across all content types.
Expand Down
2 changes: 1 addition & 1 deletion docs/examples-and-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ curl http://localhost:3000/health
"status": "healthy",
"checks": [
{ "name": "bookstack_connection", "healthy": true, "message": "BookStack API connection" },
{ "name": "tools_loaded", "healthy": true, "message": "59 tools loaded" },
{ "name": "tools_loaded", "healthy": true, "message": "71 tools loaded" },
{ "name": "resources_loaded", "healthy": true, "message": "5 resources and 6 resource templates loaded" }
]
}
Expand Down
2 changes: 1 addition & 1 deletion docs/integration-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ three services; `mcp` is not needed to run these tests.
| `mcp` | built from `./Dockerfile` | Our MCP server. **Not required** by the integration suite. |

The BookStack tag is **pinned, not `:latest`**. The whole tool contract in this repo —
59 tools, field shapes, error codes — was verified against v26.05.2, and a floating tag
71 tools, field shapes, error codes — was verified against v26.05.2, and a floating tag
would silently re-point at a future release, turning an upstream change into a mystery
failure here. Bump it deliberately in `docker-compose.yml`, then re-run the suite.

Expand Down
Loading
Loading