Summary
Add tools for BookStack's /api/image-gallery endpoint: upload, list, and delete images in the gallery. We've implemented this in our fork and it's working well — happy to contribute a PR if there's interest.
Why this matters for MCP
MCP's tool interface is text-based JSON — there's no binary streaming primitive. This creates a fundamental problem for image uploads:
Without an upload tool, the only way to get images into BookStack pages is to embed them as base64 data URIs in the html or markdown argument of create_page/update_page. This means:
- The model must generate the base64 string as output tokens — slow, expensive, and error-prone for large images
- The full base64 payload transits through the model's context window, consuming tokens that could be used for actual reasoning
- A 200 KB JPEG becomes ~270 KB of base64 text — multiplied across a page with several figures, this can easily blow past context limits
- Base64 data URIs aren't real hosted assets — they bloat the page HTML permanently and can't be reused, thumbnailed, or managed in BookStack's gallery
With a create_image tool, the model passes a file path or URL (a few bytes of text), and the MCP server reads/fetches the image bytes directly and uploads them to BookStack. Zero image bytes transit the model. BookStack hosts the image and returns a URL that the model can embed in page content via a normal update_page call. This is the correct MCP pattern for binary content: let the server handle I/O, keep the model interface lightweight.
Proposed tools
create_image (write)
Upload an image to the BookStack gallery from a local file path or URL.
Inputs:
file_path: string (optional) — absolute path readable by the MCP server
url: string (optional) — HTTP(S) URL to fetch (exactly one of file_path/url required)
name: string (optional) — gallery display name (defaults to filename)
uploaded_to: number — page ID (BookStack requires a page association)
Returns:
id, name, url, thumbs, plus ready-to-paste html and markdown snippets
list_images (read)
List images in the gallery, optionally filtered by page.
Inputs:
uploaded_to: number (optional) — filter by page ID
offset, count, sort — standard pagination
delete_image (write)
Delete an image from the gallery by ID.
Implementation notes from our fork
- Must use native
fetch, not axios, for the multipart upload. The axios instance in BookStackClient sets a default Content-Type: application/json header which clobbers the multipart/form-data boundary. This causes BookStack to fail parsing the body entirely (422 on every request, regardless of inputs). Native fetch sets the boundary automatically from FormData.
- The
createImage client method should surface BookStack's response body in errors — a bare Request failed with status code 422 without the validation details makes debugging very difficult.
- We store
authToken as an instance property on BookStackClient so the fetch call can use it without extracting it from axios internals.
- Gated behind
enableWrite like the other write tools.
- 50 MB sanity check on upload size (matches typical BookStack/PHP limits).
- Supports common web formats: PNG, JPEG, GIF, WebP, SVG.
Real-world usage
We use this to let Claude build richly illustrated wiki pages autonomously: it takes screenshots or receives image file paths, uploads them to BookStack's gallery via create_image, gets back hosted URLs, then embeds them in page content via update_page. The entire workflow stays within MCP tool calls — no manual image uploading or copy-pasting URLs.
Happy to open a PR with our implementation if you'd like to include this upstream.
Summary
Add tools for BookStack's
/api/image-galleryendpoint: upload, list, and delete images in the gallery. We've implemented this in our fork and it's working well — happy to contribute a PR if there's interest.Why this matters for MCP
MCP's tool interface is text-based JSON — there's no binary streaming primitive. This creates a fundamental problem for image uploads:
Without an upload tool, the only way to get images into BookStack pages is to embed them as base64 data URIs in the
htmlormarkdownargument ofcreate_page/update_page. This means:With a
create_imagetool, the model passes a file path or URL (a few bytes of text), and the MCP server reads/fetches the image bytes directly and uploads them to BookStack. Zero image bytes transit the model. BookStack hosts the image and returns a URL that the model can embed in page content via a normalupdate_pagecall. This is the correct MCP pattern for binary content: let the server handle I/O, keep the model interface lightweight.Proposed tools
create_image(write)Upload an image to the BookStack gallery from a local file path or URL.
list_images(read)List images in the gallery, optionally filtered by page.
delete_image(write)Delete an image from the gallery by ID.
Implementation notes from our fork
fetch, not axios, for the multipart upload. The axios instance inBookStackClientsets a defaultContent-Type: application/jsonheader which clobbers themultipart/form-databoundary. This causes BookStack to fail parsing the body entirely (422 on every request, regardless of inputs). Nativefetchsets the boundary automatically fromFormData.createImageclient method should surface BookStack's response body in errors — a bareRequest failed with status code 422without the validation details makes debugging very difficult.authTokenas an instance property onBookStackClientso the fetch call can use it without extracting it from axios internals.enableWritelike the other write tools.Real-world usage
We use this to let Claude build richly illustrated wiki pages autonomously: it takes screenshots or receives image file paths, uploads them to BookStack's gallery via
create_image, gets back hosted URLs, then embeds them in page content viaupdate_page. The entire workflow stays within MCP tool calls — no manual image uploading or copy-pasting URLs.Happy to open a PR with our implementation if you'd like to include this upstream.