diff --git a/README.md b/README.md index e750251..735e819 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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) | diff --git a/src/bookstack-client.ts b/src/bookstack-client.ts index 7166644..59f1f08 100644 --- a/src/bookstack-client.ts +++ b/src/bookstack-client.ts @@ -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 { + 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 { + 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; diff --git a/src/index.ts b/src/index.ts index b31f710..f270808 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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", {