BookStack MCP Server — 69 tools via DADL
The BookStack DADL turns BookStack's API into an MCP server that Claude, GPT or any MCP-compatible agent can consume directly. One YAML file declares all 69 tools — book, chapter, image, page, role, attachment, and more — and ToolMesh serves them at runtime. No Python boilerplate, no per-endpoint code, no separate MCP server process.
Below: the endpoint coverage matrix, a two-block ToolMesh setup, the full tool reference grouped by BookStack feature area, required credential scopes.
Source: BookStack REST API
Which BookStack endpoints are covered?
98% (69 of ~77 endpoints)
Focus: BookStack full API: books (CRUD, exports), pages (CRUD, HTML, markdown, exports), chapters (CRUD, exports), shelves (CRUD, book assignment), attachments (links, uploads), images (gallery, drawio, file data), comments (threads, replies, archiving), search (cross-entity), users (CRUD, invites), roles (permissions, MFA), recycle-bin (restore, purge), content-permissions (role overrides, fallback), audit-log, tags, ZIP imports.
Missing: Cover-image upload on book/shelf create/update and file replacement on attachment/image update (multipart facets of covered endpoints); the _method=PUT form workaround. All 77 documented endpoints are otherwise reachable -- the 15 export endpoints are collapsed into 6 parameterized tools.
How do you configure the BookStack DADL?
- Log in to your BookStack instance as the user the API should act as (its roles define what the API can see and do)
- Ensure one of the user's roles grants the 'Access System API' permission: Settings > Roles > (role) > System Permissions
- Open the user's profile (avatar top-right > 'Edit Profile') and scroll to the 'API Tokens' section
- Click 'Create Token', enter a name and expiry date, then Save -- the Token ID and Token Secret are shown
- Join both values with a colon to form the credential: '<token_id>:<token_secret>' -- store this single string as bookstack_token
Environment variable: CREDENTIAL_BOOKSTACK_TOKEN
The backends.yaml url MUST include the /api suffix. The header sent is "Authorization: Token <token_id>:<token_secret>" -- the credential value is the colon-joined pair, ToolMesh prepends the "Token " prefix. Content visibility mirrors the token user's roles exactly; for read-mostly agents prefer a dedicated low-privilege user. Default rate limit is 180 requests/min per user (env API_REQUESTS_PER_MIN).
How do you install the BookStack MCP server with ToolMesh?
Add to your backends.yaml:
- name: bookstack
transport: rest
dadl: bookstack.dadl
url: "https://wiki.example.com/api"
Set the credential:
CREDENTIAL_BOOKSTACK_TOKEN=your-token-here What 69 tools does the BookStack DADL expose?
GET search_content Search across all content types (shelves, books, chapters, pages) using the same query syntax as the BookStack UI search bar. Returns {data, total}; each result has a 'type' property (bookshelf, book, chapter, page), tags, parent references (book/chapter), and a 'preview_html' object with highlighted name/content snippets. Uses its own page/count paging -- sort and filter params are NOT supported here.
GET list_shelves List all shelves visible to the API user. Returns id, name, slug, description, created_at, updated_at, created_by, updated_by, owned_by per shelf in {data, total}.
GET get_shelf Get a single shelf with tags, cover image info, description_html and the visible books on it (id, name, slug each).
POST create_shelf Create a new shelf. Optionally assign books by ID -- they appear on the shelf in the order given. Provide description (plain text) or description_html, not both.
PUT update_shelf Update a shelf. Providing 'books' REPLACES the entire book assignment with the given list -- fetch current books via get_shelf first when only adding/removing one.
DELETE delete_shelf Delete a shelf (sent to the recycle bin; books on it are NOT deleted). Returns 204. GET list_books List all books visible to the API user. Returns id, name, slug, description, created_at, updated_at, created_by, updated_by, owned_by plus cover image info per book in {data, total}.
GET get_book Get a single book with tags, cover, description_html, shelves it is on, and a 'contents' tree listing its direct chapters and pages in display order -- each entry has a 'type' (chapter/page); chapters nest their pages. Use this to navigate a book's structure before reading pages.
POST create_book Create a new book. Provide description (plain text) or description_html. Cover-image upload requires multipart and is not supported here.
PUT update_book Update a book's details. Same writable fields as create_book. DELETE delete_book Delete a book including its chapters and pages (sent to the recycle bin). Returns 204. GET export_book Export a whole book (all chapters and pages compiled) as text. The result is the raw exported content -- HTML document, Markdown, or plain text. For PDF/ZIP use export_book_file.
GET export_book_file Export a whole book as a binary file -- 'pdf' or 'zip' (portable BookStack ZIP). Returns a download URL via the ToolMesh file broker.
GET list_chapters List all chapters visible to the API user. Returns id, book_id, name, slug, description, priority, created_at, updated_at, created_by, updated_by, owned_by per chapter in {data, total}.
GET get_chapter Get a single chapter with tags, description_html, book_slug and its visible pages (listing-level fields each).
POST create_chapter Create a new chapter inside a book. PUT update_chapter Update a chapter. Passing a different book_id MOVES the chapter (and its pages) into that book -- requires delete permission on the chapter.
DELETE delete_chapter Delete a chapter including its pages (sent to the recycle bin). Returns 204. GET export_chapter Export a chapter (all its pages compiled) as text -- HTML document, Markdown, or plain text. For PDF/ZIP use export_chapter_file.
GET export_chapter_file Export a chapter as a binary file -- 'pdf' or 'zip' (portable BookStack ZIP). Returns a download URL via the ToolMesh file broker.
GET list_pages List all pages visible to the API user. Returns id, book_id, chapter_id (0 when directly in a book), name, slug, priority, draft, template, created_at, updated_at, created_by, updated_by, owned_by per page in {data, total}. Content is NOT included -- call get_page for that.
GET get_page Get a single page with full content: 'html' (rendered output, includes resolved), 'raw_html' (stored source -- use as editing basis), 'markdown' (only if last saved with the Markdown editor), tags, and a 'comments' object with 'active' and 'archived' comment trees.
POST create_page Create a new page. Exactly ONE of book_id (page directly in book) or chapter_id (page inside chapter) is required, and exactly ONE of html or markdown content. Keep HTML to a single-block depth of plain elements for editor compatibility; base64 data-URI images are extracted into the image gallery automatically.
PUT update_page Update a page's details or content. Passing book_id or chapter_id MOVES the page to that parent (requires delete permission on the page). When updating content, base your edit on 'raw_html' from get_page and send it via 'html' -- the rendered 'html' field has include-tags resolved and round-trips badly.
DELETE delete_page Delete a page (sent to the recycle bin). Returns 204. GET export_page Export a single page as text -- HTML document, Markdown, or plain text. For PDF/ZIP use export_page_file. Note: get_page already returns html/markdown content; exports add standalone document wrapping.
GET export_page_file Export a single page as a binary file -- 'pdf' or 'zip' (portable BookStack ZIP). Returns a download URL via the ToolMesh file broker.
GET list_attachments List attachments visible to the API user. Returns id, name, extension, uploaded_to (page ID), external (true = link attachment, false = file upload), order, created_at, updated_at, created_by, updated_by in {data, total}.
GET get_attachment Get details and content of an attachment. 'content' holds the link URL for external attachments, or the FULL base64-encoded file data for uploads -- which can be very large; strip it with a jq override when you only need metadata. 'links' provides ready-made HTML and Markdown embed snippets.
POST create_attachment Attach an external LINK to a page. For uploading a file as attachment use upload_attachment instead.
POST upload_attachment Upload a FILE as attachment to a page (multipart request). The file is fetched from the given URL by ToolMesh and uploaded to BookStack.
PUT update_attachment Update an attachment's name, target link, or move it to another page. Replacing an uploaded FILE requires a multipart PUT which is not modeled -- delete and re-create via upload_attachment instead.
DELETE delete_attachment Permanently delete an attachment (no recycle bin). Returns 204. GET list_images List images in the system (page gallery images and drawio diagrams). Returns id, name, url, path, type (gallery|drawio), uploaded_to (page ID), created_by, updated_by, created_at, updated_at in {data, total}. Visibility requires access to the page each image was uploaded to.
GET get_image Get details of a single image: url, 'thumbs' (scaled variants) and 'content' with ready-made HTML/Markdown embed snippets as BookStack would insert them. Image file data is NOT included -- use get_image_data or the 'url' property.
POST create_image Upload a new image to the gallery of a page (multipart request). Use type 'gallery' for normal page images; 'drawio' ONLY for PNG files with embedded diagrams.net data. If name is omitted the filename is used.
PUT update_image Update an image's name. Replacing the image FILE requires a multipart PUT which is not modeled -- upload a new image instead.
DELETE delete_image Permanently delete an image and its thumbnails (no recycle bin). Usage is NOT checked -- pages referencing the image will show broken references. Returns 204.
GET get_image_data Download the raw image file data for an image ID. Returns a download URL via the ToolMesh file broker instead of JSON.
GET get_image_data_by_url Download raw image file data identified by its public BookStack image URL (as found in page content src attributes) instead of an ID. Returns a download URL via the ToolMesh file broker.
GET list_comments List comments visible to the API user. Returns id, commentable_id (page ID), commentable_type, parent_id, local_id, content_ref, created_by, updated_by, created_at, updated_at in {data, total}. Comment HTML is NOT included -- use get_comment. For a page's full comment tree prefer get_page (comments property).
GET get_comment Get a single comment (with safe HTML content) plus its direct replies. Note: 'local_id' is page-scoped; 'parent_id' of replies refers to the parent's local_id, not the global id.
POST create_comment Create a comment on a page. To reply to an existing comment set reply_to to the parent comment's LOCAL_ID (page-scoped), not its global id.
PUT update_comment Update a comment's content and/or archive state. Only provide 'archived' when actively changing it; only top-level comments (non-replies) can be archived/unarchived.
DELETE delete_comment Permanently delete a comment (no recycle bin). Returns 204. GET list_users List all users. Requires 'Manage users' permission. Returns id, name, slug, email, external_auth_id, created_at, updated_at, last_activity_at plus profile/avatar URLs in {data, total}.
GET get_user Get a single user including their assigned roles (id + display_name). Requires 'Manage users' permission. POST create_user Create a new user. Requires 'Manage users' permission. Either set a password or send_invite=true so the user sets their own via email.
PUT update_user Update a user. Requires 'Manage users' permission. 'roles' replaces the full role assignment. DELETE delete_user Permanently delete a user. Requires 'Manage users' permission. Pass migrate_ownership_id to transfer ownership of their content to another user first. Returns 204.
GET list_roles List all roles. Requires 'Manage roles' permission. Returns display_name, description, mfa_enforced, external_auth_id, timestamps plus permissions_count and users_count in {data, total}.
GET get_role Get a single role including its granted permission name strings and a high-level list of assigned users. Requires 'Manage roles' permission.
POST create_role Create a new role. Permissions are given as an array of permission name strings (e.g. 'access-api', 'page-create-all', 'book-view-own') -- inspect an existing role via get_role for the full vocabulary.
PUT update_role Update a role. CAUTION: 'permissions' replaces ALL granted permissions and an empty array clears them -- fetch current permissions via get_role and send the modified full set.
DELETE delete_role Permanently delete a role (users keep their other roles). Requires 'Manage roles' permission. Returns 204. GET list_recycle_bin List items in the recycle bin. Requires permission to manage both system settings and permissions. Each entry has a deletion id (use for restore/purge), deleted_by, deletable_type (page/chapter/book/ bookshelf), deletable_id and a 'deletable' object with child counts (books/chapters) or parent info (chapters/pages).
PUT restore_recycle_bin_item Restore a deletion from the recycle bin, including all child content. Takes the DELETION id from list_recycle_bin, not the content's own ID. Returns {restore_count}.
DELETE purge_recycle_bin_item PERMANENTLY destroy a deletion from the recycle bin including all child content -- this cannot be undone. Takes the DELETION id from list_recycle_bin. Returns {delete_count}.
GET get_content_permissions Read the content-level permission OVERRIDES for one item: owner, role_permissions (per-role view/create/update/delete flags) and fallback_permissions ('inheriting': true means no override; its flag values are null then). Shows only overrides on this item -- not evaluated/inherited permissions.
PUT update_content_permissions Update content-level permission overrides for one item. OMIT owner_id / role_permissions / fallback_permissions entirely to leave that category unchanged. CAUTION: an empty role_permissions array CLEARS all configured role overrides -- read existing permissions first and send the merged result.
GET list_audit_log List audit log events. Requires permission to manage both users and system settings. Returns id, type (e.g. page_create, auth_login, permissions_update), detail, user_id (plus user object), loggable_id, loggable_type, ip, created_at in {data, total}.
GET list_tag_names List distinct tag NAMES used across visible content, with usage totals: name, values (count of distinct values), usages, page_count, chapter_count, book_count, shelf_count. Only 'name' is filterable. Requires BookStack >= v26.05 -- older instances return a 404 HTML page.
GET list_tag_values List the distinct VALUES set for one tag name across visible content, with usage totals per value. Only 'value' is filterable. Requires BookStack >= v26.05 -- older instances return a 404 HTML page.
GET list_imports List pending ZIP imports visible to the user. Requires 'Import content' permission. Returns id, name, size, type (book/chapter/page), created_by, created_at, updated_at in {data, total}.
POST create_import Upload and validate a BookStack portable ZIP file (from export_*_file zip exports) as a pending import. Does NOT import yet -- call run_import afterwards. Requires 'Import content' permission.
GET get_import Read a pending ZIP import including a 'details' property with metadata about the ZIP content (structure varies by import type).
POST run_import Execute a pending ZIP import. parent_type + parent_id are REQUIRED when the import's type is 'chapter' or 'page' (check via get_import); book imports need no parent. Returns the imported item on success.
DELETE delete_import Delete a pending ZIP import (the staged upload, not imported content). Returns 204. GET get_system_info Read instance details: version, instance_id, app_name, app_logo (may be null), base_url. Useful for verifying connectivity and version-gated features.