- Overview
- Authentication & Configuration
- Rate Limiting & Error Handling
- API Endpoints
- TypeScript Interfaces
- Error Codes
- Examples
The BookStack MCP Server provides comprehensive access to the BookStack knowledge management system through the Model Context Protocol (MCP). This API wrapper enables seamless integration with Claude and other LLMs for documentation management tasks.
- API Coverage: 71 tools, 5 resources and 6 resource templates across 16 categories, covering the supported subset of the BookStack API (the image-gallery
dataendpoints 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, ZIP)
BookStack Instance → API Token → MCP Server → Claude/LLM
# Required
BOOKSTACK_BASE_URL=https://your-bookstack.com/api
BOOKSTACK_API_TOKEN=your_api_token_here
# Required for the HTTP transport (the default), ignored by stdio: the inbound
# bearer secret callers must present on POST /message. Distinct from
# BOOKSTACK_API_TOKEN above. Without it the HTTP transport refuses to start.
# Generate with: openssl rand -hex 32
MCP_AUTH_TOKEN=
# Optional - Transport
MCP_TRANSPORT=http # http (default) | stdio
# Optional - Server Configuration
SERVER_NAME=bookstack-mcp-server
SERVER_VERSION=1.0.0
SERVER_PORT=3000
# Optional - Rate Limiting
RATE_LIMIT_REQUESTS_PER_MINUTE=60
RATE_LIMIT_BURST_LIMIT=10
# Optional - Validation
VALIDATION_ENABLED=true
VALIDATION_STRICT_MODE=true # default: true (reject invalid params at the boundary)
# Optional - Logging
LOG_LEVEL=info # error | warn | info | debug
LOG_FORMAT=pretty # pretty | jsonThe HTTP transport applies no CORS headers and no Helmet hardening. Put it behind a reverse proxy if you need either.
Your BookStack API token must have appropriate permissions for the operations you want to perform:
- Read Operations: Requires view permissions on target content
- Write Operations: Requires create/update permissions
- Delete Operations: Requires delete permissions
- User Management: Requires admin-level permissions
- System Operations: Requires admin-level permissions
The server uses Zod for configuration validation:
interface Config {
bookstack: {
baseUrl: string; // BookStack API base URL
apiToken: string; // API authentication token
timeout: number; // Request timeout (default: 30000ms)
};
server: {
name: string; // Server identifier
version: string; // Server version
port: number; // Server port (default: 3000)
};
rateLimit: {
requestsPerMinute: number; // Rate limit (default: 60)
burstLimit: number; // Burst capacity (default: 10)
};
validation: {
enabled: boolean; // Enable parameter validation
strictMode: boolean; // Strict validation mode
};
logging: {
level: 'error' | 'warn' | 'info' | 'debug'; // Severity threshold
format: 'json' | 'pretty'; // Output shape
};
}All log output — every level, both formats — goes to stderr. The stdio transport carries the MCP protocol on stdout, so logging there would corrupt it.
The server implements a token bucket rate limiter:
- Default Rate: 60 requests per minute
- Burst Capacity: 10 requests
- Algorithm: Token bucket with linear refill
- Behavior: Automatic queuing when limits exceeded
Comprehensive error mapping from HTTP status codes to MCP errors:
| HTTP Status | Error Type | Description | Recovery |
|---|---|---|---|
| 400 | validation_error |
Invalid request parameters | Check parameter format and requirements |
| 401 | authentication_error |
Invalid/missing token | Verify BOOKSTACK_API_TOKEN |
| 403 | permission_error |
Insufficient permissions | Check user permissions in BookStack |
| 404 | not_found_error |
Resource not found | Verify resource ID exists |
| 422 | validation_error |
Validation failed | Check required fields and constraints |
| 429 | rate_limit_error |
Rate limit exceeded | Wait and retry, or reduce request frequency |
| 500+ | server_error |
Server-side error | Check BookStack server status |
| — | network_error |
BookStack could not be reached (connection refused, reset or dropped before a response) | Check BookStack is up and BOOKSTACK_BASE_URL is right |
| — | timeout_error |
BookStack did not respond within BOOKSTACK_TIMEOUT ms |
Check BookStack load, or raise BOOKSTACK_TIMEOUT |
Transient failures are retried automatically:
- Retryable Status Codes: 429, 500, 502, 503, 504
- Retryable connection failures: connection refused, reset or dropped before a
response (
network_error). Timeouts (timeout_error) are never retried. - Max Attempts: 4 (the initial request plus up to 3 retries)
- Which requests are replayed: a
429is retried for any method — BookStack rejects a throttled request before it executes, so replaying it is safe. The5xxcodes and connection failures are only retried forGET,HEADandOPTIONS, because a write may have partially applied. - Backoff: a server-directed wait wins when present —
Retry-After, elseX-RateLimit-Reset. Otherwise exponential from 500ms, doubling to an 8s cap, with up to 25% jitter. - Total wait budget: 30 seconds across all retries. A wait that would exceed the budget is not taken — the error surfaces immediately instead.
- Timeout: 30 seconds per request (
BOOKSTACK_TIMEOUT).
BookStack rate-limits its API per user (API_REQUESTS_PER_MIN, default 180),
returning 429 with X-RateLimit-Limit / X-RateLimit-Remaining headers. The
retry behaviour above is what absorbs that limit.
Books are the top-level containers in BookStack's hierarchy.
// Tool: bookstack_books_list
interface BooksListParams {
count?: number; // Results per page (1-500, default: 20)
offset?: number; // Number to skip (default: 0)
sort?: 'name' | 'created_at' | 'updated_at'; // Sort field
filter?: {
name?: string; // Exact name match
created_by?: number; // Creator user ID
};
}
interface ListResponse<Book> {
data: Book[];
total: number;
}Example Request:
{
"count": 10,
"filter": { "name": "API" },
"sort": "updated_at"
}Example Response:
{
"data": [
{
"id": 1,
"name": "API Documentation",
"slug": "api-documentation",
"description": "Complete API reference guide",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-20T15:45:00Z",
"created_by": 1,
"updated_by": 1,
"owned_by": 1,
"tags": [
{ "name": "category", "value": "documentation", "order": 0 }
]
}
],
"total": 1
}// Tool: bookstack_books_create
interface CreateBookParams {
name: string; // Required, max 255 chars
description?: string; // Optional, max 1900 chars
description_html?: string; // Optional, max 2000 chars
tags?: Tag[]; // Optional tag array
default_template_id?: number; // Optional template ID
}
interface Tag {
name: string;
value: string;
}// Tool: bookstack_books_read
interface BookWithContents extends Book {
contents: (Chapter | Page)[]; // Complete hierarchy
}// Tool: bookstack_books_update
// Same as CreateBookParams but all fields optional except id// Tool: bookstack_books_delete
// Moves book to recycle bin for potential restoration// Tool: bookstack_books_export
interface ExportParams {
id: number;
format: 'html' | 'pdf' | 'plaintext' | 'markdown' | 'zip';
}
interface ExportResult {
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
}Pages contain the actual content within books or chapters.
// Tool: bookstack_pages_list
interface PagesListParams extends PaginationParams {
filter?: {
book_id?: number; // Filter by parent book
chapter_id?: number; // Filter by parent chapter
name?: string; // Exact name match
draft?: boolean; // Filter by draft status
template?: boolean; // Filter by template status
};
}// Tool: bookstack_pages_create
interface CreatePageParams {
book_id?: number; // Required if chapter_id not provided
chapter_id?: number; // Required if book_id not provided
name: string; // Required, max 255 chars
html?: string; // HTML content (required if markdown not provided)
markdown?: string; // Markdown content (required if html not provided)
tags?: Tag[]; // Optional tags
priority?: number; // Optional priority for ordering
}// Tool: bookstack_pages_read
interface PageWithContent extends Page {
html: string; // Rendered HTML content, page includes resolved
raw_html: string; // Raw HTML as stored - this is what the edit tools patch
markdown?: string; // Markdown source; empty string for HTML-authored pages
}
// With any option below set, the response is a narrowed summary instead of the
// full page object. With none set, the complete page object comes back unchanged.
interface ReadPageOptions {
id: number; // Required
grep?: string; // Literal phrase in the STORED source (1-1000 chars); returns excerpts only
case_sensitive?: boolean; // Default false
context?: number; // Context characters per match, 1-2000, default 200
max_matches?: number; // Excerpts returned, 1-50, default 10 (total is still reported)
offset?: number; // Start of a character window into the stored source
length?: number; // Length of that window
metadata_only?: boolean; // Metadata and total_chars only, no content
}
// A grep response lists matches: [{ offset, match, context, context_truncated_start,
// context_truncated_end }]. `context` is an exact slice of the stored source (no ellipses),
// so it can be used verbatim as an edit's old_string.// Tool: bookstack_pages_update
// Same as CreatePageParams but all fields optional except id
// Can move pages between books/chapters by changing book_id/chapter_id
// NOTE: replaces the ENTIRE content field. For a partial change use
// bookstack_pages_edit or bookstack_pages_append below.// Tool: bookstack_pages_edit
//
// The BookStack API has no PATCH for page content, so the server performs the
// read-modify-write cycle itself: it reads the page, applies the edits to its
// STORED source (raw_html, or markdown for markdown pages) and writes the result
// back. The caller sends only the fragment it wants changed.
interface EditPageParams {
id: number; // Required
edits: Array<{ // Required, at least one; applied in order
old_string: string; // Exact text, whitespace included; must be unique
new_string: string; // Replacement; '' deletes the anchored text
replace_all?: boolean; // Default false; opt-in for a repeated anchor
}>;
dry_run?: boolean; // Report what would change, write nothing
expected_updated_at?: string; // Best-effort stale-page preflight; not an atomic lock
allow_shrink?: boolean; // Permit a result under half the original size
}
// The response carries no page content:
// {
// page_id, name, slug, book_id, chapter_id, updated_at, revision_count,
// editor, field, chars_before, chars_after, delta,
// edits: [{ index, occurrences_replaced, context }],
// written, verified, unverified_fragment_count, chars_stored
// }
// verified is null, with a note, when the write succeeded but the page could not be
// re-read: do not retry the write, read the page to check it.An anchor that cannot be applied comes back as a tool error result (isError: true)
whose text carries actionable detail rather than a bare failure:
found_with_different_whitespace when the text exists but the whitespace differs,
first_occurrences when it matched more than once, chars_before/chars_after when
the shrink guard fired. A page that changed before this server read it comes back the
same way with type: 'concurrent_modification'.
// Tool: bookstack_pages_append
interface AppendPageParams {
id: number; // Required
content: string; // Required, in the page's own format
position?: 'start' | 'end'; // Default 'end'; 'start' means after the heading
section?: string; // Heading text; omit to target the whole page
separator?: string; // Default '\n\n' for markdown, '\n' otherwise
dry_run?: boolean;
expected_updated_at?: string;
}An exact heading match (trimmed, case-insensitive) wins; otherwise a substring is accepted
only if exactly one heading contains it. An unknown section fails with available_sections
listing the real heading names, an ambiguous one with matching_sections. A section ends at
the next heading of the same or a higher level, so end lands after its subsections. The
response, dry runs included, reports the heading used as section_matched.
// Tool: bookstack_pages_outline
// Heading structure only - no content is transferred.
// {
// page_id, name, slug, book_id, chapter_id, updated_at, revision_count,
// editor, field, total_chars, heading_count,
// headings: [{ level, text, offset, length }] // length = section size, subsections included
// }// Tool: bookstack_pages_delete
// Moves page to recycle bin// Tool: bookstack_pages_export
// Same format options as booksChapters organize pages within books.
// Tool: bookstack_chapters_list
interface ChaptersListParams extends PaginationParams {
filter?: {
book_id?: number; // Filter by parent book
name?: string; // Exact name match
created_by?: number; // Creator user ID
};
}// Tool: bookstack_chapters_create
interface CreateChapterParams {
book_id: number; // Required parent book
name: string; // Required, max 255 chars
description?: string; // Optional, max 1900 chars
description_html?: string; // Optional, max 2000 chars
tags?: Tag[]; // Optional tags
priority?: number; // Optional priority for ordering
}// Tool: bookstack_chapters_read
interface ChapterWithPages extends Chapter {
pages: Page[]; // All pages within the chapter
}// Tool: bookstack_chapters_update
// Same as CreateChapterParams but all fields optional except id
// Can move chapters between books with book_id// Tool: bookstack_chapters_delete
// Deletes chapter and all contained pages// Tool: bookstack_chapters_export
// Exports chapter and all contained pagesBookshelves organize books into collections.
// Tool: bookstack_shelves_list
interface ShelvesListParams extends PaginationParams {
filter?: {
name?: string; // Exact name match
created_by?: number; // Creator user ID
};
}// Tool: bookstack_shelves_create
interface CreateShelfParams {
name: string; // Required, max 255 chars
description?: string; // Optional, max 1900 chars
description_html?: string; // Optional, max 2000 chars
tags?: Tag[]; // Optional tags
books?: number[]; // Optional array of book IDs
}// Tool: bookstack_shelves_read
interface BookshelfWithBooks extends Bookshelf {
books: Book[]; // All books on the shelf
}// Tool: bookstack_shelves_update
// Same as CreateShelfParams but all fields optional except id
// books array replaces all existing books on shelf// Tool: bookstack_shelves_delete
// Deletes shelf but books remainUser management operations (requires admin permissions).
// Tool: bookstack_users_list
interface UsersListParams extends PaginationParams {
filter?: {
name?: string; // Exact name match
email?: string; // Partial email match
active?: boolean; // Filter by active status
};
}// Tool: bookstack_users_create
interface CreateUserParams {
name: string; // Required, max 255 chars
email: string; // Required, must be unique
password?: string; // Optional, min 8 chars
roles?: number[]; // Optional role IDs
send_invite?: boolean; // Send invitation email
external_auth_id?: string; // For LDAP/SAML users
}// Tool: bookstack_users_read
interface UserWithRoles extends User {
roles: Role[]; // All assigned roles
}// Tool: bookstack_users_update
interface UpdateUserParams {
name?: string; // New display name
email?: string; // New email (must be unique)
password?: string; // New password
roles?: number[]; // New role assignments (replaces existing)
active?: boolean; // Enable/disable user
external_auth_id?: string; // External auth ID
}// Tool: bookstack_users_delete
interface DeleteUserParams {
id: number; // User ID to delete
migrate_ownership_id?: number; // Optional user to transfer content to
}Role management for permissions (requires admin permissions).
// Tool: bookstack_roles_list
interface RolesListParams extends PaginationParams {
filter?: {
display_name?: string; // Partial display name match
system_name?: string; // Partial system name match
};
sort?: 'display_name' | 'system_name' | 'created_at' | 'updated_at';
}// Tool: bookstack_roles_create
interface CreateRoleParams {
display_name: string; // Required, max 180 chars
description?: string; // Optional, max 1000 chars
mfa_enforced?: boolean; // Enforce MFA for this role
external_auth_id?: string; // For LDAP/SAML roles
permissions?: {
'content-export'?: boolean;
'restrictions-manage-all'?: boolean;
'restrictions-manage-own'?: boolean;
'settings-manage'?: boolean;
'user-roles-manage'?: boolean;
'users-manage'?: boolean;
};
}// Tool: bookstack_roles_read
interface RoleWithPermissions extends Role {
permissions: string[]; // All granted permissions
}// Tool: bookstack_roles_update
// Same as CreateRoleParams but all fields optional except id// Tool: bookstack_roles_delete
interface DeleteRoleParams {
id: number; // Role ID to delete
migrate_ownership_id?: number; // Optional role to migrate users to
}File attachments for pages.
// Tool: bookstack_attachments_list
interface AttachmentsListParams extends PaginationParams {
filter?: {
name?: string; // Exact name match
uploaded_to?: number; // Page ID filter
extension?: string; // File extension filter
};
}// Tool: bookstack_attachments_create
interface CreateAttachmentParams {
uploaded_to: number; // Required page ID
name: string; // Required, max 255 chars
file?: string; // Base64 encoded file content
link?: string; // External URL (alternative to file)
}// Tool: bookstack_attachments_read
interface Attachment {
id: number;
name: string;
extension: string;
uploaded_to: number; // Page ID
external: boolean; // True if link, false if file
order: number;
created_at: string;
updated_at: string;
created_by: number;
updated_by: number;
links: {
html: string; // HTML embed code
markdown: string; // Markdown link
};
}// Tool: bookstack_attachments_update
interface UpdateAttachmentParams {
uploaded_to?: number; // Move to different page
name?: string; // New name
file?: string; // Replace file content
link?: string; // Replace link URL
}// Tool: bookstack_attachments_delete
// Permanently deletes attachmentImage gallery management.
// Tool: bookstack_images_list
interface ImageGalleryListParams extends PaginationParams {
filter?: {
name?: string; // Exact name match
type?: 'gallery' | 'drawio'; // Image type filter
uploaded_to?: number; // Page association filter
};
}// Tool: bookstack_images_create
interface CreateImageParams {
name: string; // Required, max 255 chars
image: string; // Required, Base64 encoded image
type?: 'gallery' | 'drawio'; // Image type (default: gallery)
uploaded_to?: number; // Optional page association
}// Tool: bookstack_images_read
interface Image {
id: number;
name: string;
url: string; // Full URL to image
type: string; // Image type
path: string; // Server path
created_at: string;
updated_at: string;
created_by: number;
updated_by: number;
}// Tool: bookstack_images_update
interface UpdateImageParams {
name?: string; // New name
image?: string; // Replace image content
uploaded_to?: number; // Change page association
}// Tool: bookstack_images_delete
// Permanently deletes imageComments 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.
// 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;
};
}// 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
}// Tool: bookstack_comments_read
// Returns the comment with html, archived and its direct replies// Tool: bookstack_comments_update
interface UpdateCommentParams {
id: number;
html?: string; // Replaces the content
archived?: boolean; // Top-level comments only
}// Tool: bookstack_comments_delete
// Permanently deletes the commentImports of BookStack's portable ZIP format. All import tools need the content-import
permission.
// Tool: bookstack_imports_list
interface ImportsListParams extends PaginationParams {
filter?: {
name?: string;
size?: number; // Bytes
type?: 'book' | 'chapter' | 'page';
created_by?: number;
};
}// 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// Tool: bookstack_imports_read
// Returns the pending import with `details` describing its content// 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;
}// Tool: bookstack_imports_delete
// Permanently deletes a pending import without running itRead-only listings of the tags on content visible to the authenticated user.
// Tool: bookstack_tags_list_names
interface TagNamesListParams extends PaginationParams {
filter?: { name?: string };
}
// Entries: { name, values, usages, page_count, chapter_count, book_count, shelf_count }// 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 }Universal search across all content types.
// Tool: bookstack_search
interface SearchParams {
query: string; // Required search query
page?: number; // Page number (default: 1)
count?: number; // Results per page (1-100, default: 20)
}
interface SearchResult {
id: number;
name: string;
slug: string;
type: 'bookshelf' | 'book' | 'chapter' | 'page';
url: string;
preview_html: {
name: string; // Highlighted name
content: string; // Content excerpt with highlights
};
tags: Tag[];
book?: Book; // Parent book (for chapters/pages)
chapter?: Chapter; // Parent chapter (for pages)
}Advanced Search Syntax:
"exact phrase"- Exact phrase matching{in_name:text}/{in_body:text}- Field-specific search{type:page}- Entity type filter; combine types with|[tag]/[tag=value]- Tag-based search-"phrase"/-[tag]/-{filter}- Negation (a bare term cannot be negated)
Examples:
"API documentation" // Exact phrase
{in_name:authentication} // Search in names only
authentication {type:page} // Pages containing authentication
{type:page|chapter} database // Pages OR chapters mentioning database
[category=api] // Content tagged category=api
[category] // Content carrying a "category" tag at all
{type:page} -[deprecated] // Pages not tagged deprecated
{created_by:me} {updated_after:2026-01-01}
⚠️ Unrecognised syntax fails open. BookStack silently discards a{filter:...}term it does not know rather than rejecting it, so a typo widens the query to match everything instead of erroring. Three specific traps:
- Entity type is
{type:page}—[page]is tag syntax and looks for a tag named "page".- Tags are
[name=value]—tag:name=valueand{tag:name=value}are both wrong.- There are no boolean
AND/OR/NOToperators. Terms are combined implicitly,|unions types inside{type:...}, and-negates. A query likeauthentication AND securitysearches for the literal word "AND".
Manage deleted content restoration.
// Tool: bookstack_recyclebin_list
interface RecycleBinItem {
id: number; // Deletion ID
created_at: string; // When the item was deleted
updated_at: string; // When the deletion record last changed
deletable_type: string; // Original entity type ('page', 'book', 'chapter', ...)
deletable_id: number; // Original entity ID
deleted_by: number; // User who deleted
deletable: unknown; // Original entity data; shape varies by deletable_type
}Note the deletion timestamp is
created_at(the creation time of the deletion record). The endpoint returns nodeleted_atfield.
// Tool: bookstack_recyclebin_restore
// Restores item to original location// Tool: bookstack_recyclebin_delete_permanently
// Cannot be undoneContent permission management.
// Tool: bookstack_permissions_read
interface ContentPermissions {
inheriting: boolean; // Whether inheriting from parent
permissions: {
role_id: number;
role_name: string;
view: boolean;
create: boolean;
update: boolean;
delete: boolean;
}[];
}// Tool: bookstack_permissions_update
interface UpdateContentPermissionsParams {
fallback_permissions?: {
inheriting?: boolean;
restricted?: boolean;
};
permissions: {
role_id?: number; // Role to grant permissions to
user_id?: number; // User to grant permissions to (alternative)
view?: boolean;
create?: boolean;
update?: boolean;
delete?: boolean;
}[];
}System activity tracking.
// Tool: bookstack_audit_log_list
interface AuditLogListParams extends PaginationParams {
// sort: '-created_at' (default) | 'created_at' | '-id' | 'id'
// | '-type' | 'type' | '-user_id' | 'user_id'
filter?: {
type?: string; // Exact event name, e.g. 'page_create'
user_id?: number; // Acting user
loggable_type?: string; // Affected item type: 'page' | 'book' | 'chapter' | 'bookshelf'
loggable_id?: number; // Affected item id
date_from?: string; // '2026-07-16' or '2026-07-16 09:20:00'
date_to?: string; // As above; a bare date resolves to 00:00:00 that day
};
}
interface AuditLogEntry {
id: number;
type: string; // Event type (e.g., 'page_create')
detail: string; // Event description
user_id: number; // User who performed action
loggable_type?: string; // Affected item type; null for events with no content item
loggable_id?: number; // Affected item id; null for events with no content item
ip: string; // IP address
created_at: string; // Event timestamp
user: User; // User details
}
⚠️ The filters aretype/loggable_type/loggable_id— earlier versions of this document listedevent/entity_type/entity_id. Those were removed because BookStack ignores them: an unrecognised filter is silently dropped rather than rejected, so a query using them returns a broad unfiltered log that looks like a successful result.Every filter is an exact match — no partial or wildcard matching.
typemust be the whole event name, so'page'matches nothing while'page_update'matches.loggable_typeis only recorded for BookStack's core content types; logins and role changes carrynulland can never match it.Listing requires a token whose user can manage both users and system settings.
countaccepts 1-500 and a larger value is rejected, not clamped.
System information and health checks.
// Tool: bookstack_system_info
interface SystemInfo {
version: string; // BookStack version
instance_id: string; // Unique instance identifier
php_version: string; // PHP version
theme: string; // Active theme
language: string; // Default language
timezone: string; // Server timezone
app_url: string; // Application URL
drawing_enabled: boolean; // Drawing feature status
registrations_enabled: boolean; // Registration status
upload_limit: number; // File upload limit (bytes)
}interface Book {
id: number;
name: string;
slug: string;
description?: string;
description_html?: string;
created_at: string;
updated_at: string;
created_by: number;
updated_by: number;
owned_by: number;
image_id?: number;
default_template_id?: number;
tags: Tag[];
cover?: Image;
}
interface Page {
id: number;
book_id: number;
chapter_id?: number;
name: string;
slug: string;
priority: number;
draft: boolean;
template: boolean;
created_at: string;
updated_at: string;
created_by: number;
updated_by: number;
owned_by: number;
revision_count: number;
editor: string;
tags: Tag[];
}
interface Chapter {
id: number;
book_id: number;
name: string;
slug: string;
description?: string;
description_html?: string;
priority: number;
created_at: string;
updated_at: string;
created_by: number;
updated_by: number;
owned_by: number;
tags: Tag[];
}
interface User {
id: number;
name: string;
email: string;
avatar_url?: string;
external_auth_id?: string;
slug: string;
created_at: string;
updated_at: string;
last_activity_at?: string;
}
interface Role {
id: number;
display_name: string;
description?: string;
mfa_enforced: boolean;
external_auth_id?: string;
created_at: string;
updated_at: string;
}
interface Tag {
name: string;
value: string;
order: number;
}interface ListResponse<T> {
data: T[];
total: number;
}
interface ErrorResponse {
error: {
code: number;
message: string;
validation?: Record<string, string[]>;
};
}interface PaginationParams {
count?: number; // Results per page
offset?: number; // Number to skip
sort?: string; // Sort field
}
interface FilterParams {
name?: string; // Name filter (exact match)
created_by?: number;// Creator filter
// ... other entity-specific filters
}A failed tool call is not a JSON-RPC error: it returns a tools/call result with
isError: true, whose text gives the message and then the details as JSON (type,
validation, status, details and any recovery hints). The codes below apply to the
protocol errors that remain: an unknown tool name and a failed resources/read.
| Code | Type | Description | HTTP Status |
|---|---|---|---|
InvalidRequest |
validation_error |
Invalid parameters | 400 |
InvalidParams |
validation_error |
Parameter validation failed | 400, 422 |
MethodNotFound |
not_found_error |
Tool/resource not found | 404 |
InternalError |
server_error |
Server-side error | 500+ |
| Code | Description | Common Causes | Solutions |
|---|---|---|---|
AUTHENTICATION_FAILED |
Invalid API token | Wrong token, expired token | Regenerate token in BookStack |
PERMISSION_DENIED |
Insufficient permissions | User lacks required role | Assign appropriate role |
RESOURCE_NOT_FOUND |
Entity doesn't exist | Wrong ID, deleted entity | Verify ID, check recycle bin |
VALIDATION_ERROR |
Parameter validation failed | Missing required fields | Check parameter requirements |
RATE_LIMIT_EXCEEDED |
Too many requests | High frequency requests | Implement delays, check limits |
CONTENT_TOO_LARGE |
Content exceeds limits | Large file/text upload | Reduce content size |
DUPLICATE_ENTRY |
Unique constraint violation | Duplicate email, name | Use unique values |
// 1. Create a book
const book = await bookstack_books_create({
name: "API Documentation",
description: "Complete REST API reference",
tags: [
{ name: "category", value: "documentation" },
{ name: "version", value: "1.0" }
]
});
// 2. Create chapters
const authChapter = await bookstack_chapters_create({
book_id: book.id,
name: "Authentication",
description: "API authentication methods",
priority: 1
});
const endpointsChapter = await bookstack_chapters_create({
book_id: book.id,
name: "Endpoints",
description: "Available API endpoints",
priority: 2
});
// 3. Create pages
const authPage = await bookstack_pages_create({
chapter_id: authChapter.id,
name: "Getting Started",
markdown: `# Authentication
## API Token
Your API token must be included in the Authorization header:
\`\`\`
Authorization: Token your_token_here
\`\`\`
## Rate Limits
- 60 requests per minute
- 10 request burst capacity
`,
tags: [{ name: "type", value: "guide" }]
});
// 4. Add to a bookshelf
await bookstack_shelves_create({
name: "Developer Resources",
books: [book.id]
});// Search for API-related content
const apiContent = await bookstack_search({
query: '[page] API AND authentication',
count: 50
});
// Find all books by a specific author
const userBooks = await bookstack_books_list({
filter: { created_by: 5 },
sort: 'updated_at'
});
// Get complete book structure
const fullBook = await bookstack_books_read({ id: 1 });
console.log(`Book has ${fullBook.contents.length} items`);
// Export documentation
const pdfExport = await bookstack_books_export({
id: 1,
format: 'pdf'
});// Create documentation team role
const docRole = await bookstack_roles_create({
display_name: "Documentation Team",
description: "Can manage documentation content",
permissions: {
'content-export': true,
'restrictions-manage-own': true
}
});
// Create team member
const user = await bookstack_users_create({
name: "Jane Smith",
email: "jane@company.com",
roles: [docRole.id],
send_invite: true
});
// Set book permissions
await bookstack_permissions_update('book', 1, {
permissions: [
{
role_id: docRole.id,
view: true,
create: true,
update: true,
delete: false
}
]
});// Audit recent changes to pages
const recentChanges = await bookstack_audit_log_list({
filter: {
date_from: '2026-01-01',
loggable_type: 'page' // NOT entity_type — BookStack ignores unknown filters
},
count: 100
});
// Check recycle bin
const deletedItems = await bookstack_recyclebin_list();
// Restore an accidentally deleted item.
// `id` is the recycle bin ENTRY's id (deletedItems.data[n].id), not the deleted
// page's own id — that one is reported separately as `deletable_id`.
if (deletedItems.data.length > 0) {
const { restore_count } = await bookstack_recyclebin_restore({
id: deletedItems.data[0].id
});
// Restoring a book also restores the chapters and pages deleted with it,
// so restore_count is routinely greater than 1.
console.log(`Restored ${restore_count} item(s)`);
}
// System health check
const systemInfo = await bookstack_system_info();
console.log(`BookStack version: ${systemInfo.version}`);There is no batch tool and no batching layer — every tool acts on exactly one item.
"Bulk" work means looping and issuing one call each, so keep the loop as small as
possible: list with a filter and a large count rather than reading everything, and
avoid polling, since outbound calls are rate-limited.
// One list call (count caps at 500), then one update per item that needs it
const books = await bookstack_books_list({ count: 500 });
for (const book of books.data) {
// Only touch the books that actually need the tag
if (!book.tags.some(tag => tag.name === 'status')) {
// Every tool takes a single params object with `id` inside it —
// there is no (id, body) two-argument form.
await bookstack_books_update({
id: book.id,
// `tags` REPLACES the existing set, so send the full list you want to end up with
tags: [
...book.tags,
{ name: 'status', value: 'active' }
]
});
}
}- All timestamps are in ISO 8601 format (UTC)
- IDs are positive integers
- Text fields have specified maximum lengths
- Binary content (files/images) must be Base64 encoded
- Tags are key-value pairs with optional ordering
- Deleted items can be restored from recycle bin
- Export formats: HTML (immediate), PDF (server-generated), Markdown, Plain Text
- Rate limiting applies to all operations
- Authentication is required for every tool
- Validation is strict by default (
VALIDATION_STRICT_MODE=true): invalid params are rejected at the boundary rather than forwarded to BookStack. Set it tofalseto log a warning and forward them instead - Pagination
countis capped at 500 (100 forbookstack_search); above the cap the call is rejected, not clamped
For additional help, use the bookstack_help tool or consult the error guides with bookstack_error_guides.