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
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().int().min(0).optional().describe("Optional: Reorder chapter within its book (non-negative integer, lower sorts first)"),
tags: z.array(z.object({
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
Loading