Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ npx bookstack-mcp

## Features

- 20 read-only tools + 18 write tools for complete BookStack API coverage
- 20 read-only tools + 20 write tools for complete BookStack API coverage
- Books, chapters, pages, shelves, attachments, and comments — full CRUD
- Recycle bin support — restore or permanently delete soft-deleted content
- Type-safe input validation with Zod (auto-coerces string/number params for broad client compatibility)
Expand Down Expand Up @@ -253,8 +253,8 @@ Both templates support `id` autocompletion: as you type, the server searches Boo

| Tool | Description |
|------|-------------|
| `create_book` / `delete_book` | Create or delete a book |
| `create_chapter` / `delete_chapter` | Create or delete a chapter |
| `create_book` / `update_book` / `delete_book` | Create, update, or delete a book |
| `create_chapter` / `update_chapter` / `delete_chapter` | Create, update (including moving to a different book or reordering), or delete a chapter |
| `create_page` | Create a new page (HTML or Markdown) |
| `update_page` | Update content, rename, or move to a different book/chapter |
| `delete_page` | Delete a page (recoverable from recycle bin) |
Expand Down
26 changes: 26 additions & 0 deletions src/bookstack-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,32 @@ export class BookStackClient {
return await this.enhanceChapterResponse(response.data);
}

async updateBook(id: number, data: {
name?: string;
description?: string;
tags?: Tag[];
}): Promise<any> {
if (!this.enableWrite) {
throw new Error('Write operations are disabled. Set BOOKSTACK_ENABLE_WRITE=true to enable.');
}
const response = await this.client.put(`/books/${id}`, data);
return this.enhanceBookResponse(response.data);
}

async updateChapter(id: number, data: {
name?: string;
description?: string;
book_id?: number;
priority?: number;
tags?: Tag[];
}): Promise<any> {
if (!this.enableWrite) {
throw new Error('Write operations are disabled. Set BOOKSTACK_ENABLE_WRITE=true to enable.');
}
const response = await this.client.put(`/chapters/${id}`, data);
return await this.enhanceChapterResponse(response.data);
}

async createPage(data: {
name: string;
html?: string;
Expand Down
56 changes: 56 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,62 @@ function registerTools(server: McpServer, client: BookStackClient, config: BookS
}
);

writeTool(
"update_book",
{
description: "Update an existing book",
inputSchema: {
id: z.coerce.number().min(1),
name: z.string().optional().describe("New book name"),
description: z.string().optional().describe("New book description"),
tags: z.array(z.object({
name: z.string(),
value: z.string()
}).strict()).optional().describe("Tags for the book")
}
},
async (args) => {
const book = await client.updateBook(args.id, {
name: args.name,
description: args.description,
tags: args.tags as any
});
return {
content: [{ type: "text", text: JSON.stringify(book) }]
};
}
);

writeTool(
"update_chapter",
{
description: "Update an existing chapter. Pass book_id to move the chapter to a different book, or priority to reorder it within its book.",
inputSchema: {
id: z.coerce.number().min(1),
name: z.string().optional().describe("New chapter name"),
description: z.string().optional().describe("New chapter description"),
book_id: z.coerce.number().min(1).optional().describe("Optional: Move chapter to this book"),
priority: z.coerce.number().optional().describe("Optional: Reorder chapter within its book (lower sorts first)"),
tags: z.array(z.object({
Comment thread
strausmann marked this conversation as resolved.
name: z.string(),
value: z.string()
}).strict()).optional().describe("Tags for the chapter")
}
},
async (args) => {
const chapter = await client.updateChapter(args.id, {
name: args.name,
description: args.description,
book_id: args.book_id,
priority: args.priority,
tags: args.tags as any
});
return {
content: [{ type: "text", text: JSON.stringify(chapter) }]
};
}
);

writeTool(
"create_page",
{
Expand Down