|
| 1 | +# BookStack MCP Server - AI Coding Agent Instructions |
| 2 | + |
| 3 | +## Project Overview |
| 4 | +BookStack MCP v2.5.0 is a **monorepo** providing a Model Context Protocol server for BookStack. Architecture: `packages/core` (shared API client + types) and `packages/stdio` (MCP server with stdio transport). The core design uses **native `fetch` only** (no axios) and `McpServer` + `registerTool()` for clean, maintainable code. |
| 5 | + |
| 6 | +## Monorepo Structure |
| 7 | +``` |
| 8 | +packages/core/ # @bookstack-mcp/core - BookStack API client |
| 9 | + src/ |
| 10 | + bookstack-client.ts # HTTP client, all API methods, response enhancement |
| 11 | + types.ts # Shared TypeScript types |
| 12 | + tests/ # Functional tests with vitest |
| 13 | +
|
| 14 | +packages/stdio/ # bookstack-mcp-stdio - MCP server |
| 15 | + src/ |
| 16 | + index.ts # Entry point: env validation, tool registration, stdio transport |
| 17 | +``` |
| 18 | + |
| 19 | +**Key Design**: Core is a dependency of stdio. Tests import from `@bookstack-mcp/core`. Root commands (`npm run build`, `npm test`) delegate to workspaces. |
| 20 | + |
| 21 | +## Critical Patterns |
| 22 | + |
| 23 | +### Error Handling: Native Fetch (No Axios) |
| 24 | +All HTTP requests use native `fetch`. On error, set `error.status` and `error.response`: |
| 25 | + |
| 26 | +```typescript |
| 27 | +// packages/core/src/bookstack-client.ts - request() method |
| 28 | +if (!res.ok) { |
| 29 | + const text = await res.text(); |
| 30 | + const err = new Error(message) as Error & { |
| 31 | + status?: number; |
| 32 | + response?: { status: number; data: string }; |
| 33 | + }; |
| 34 | + err.status = res.status; |
| 35 | + err.response = { status: res.status, data: text }; |
| 36 | + throw err; |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +Tool handlers in `packages/stdio/src/index.ts` check `error.status`: |
| 41 | +```typescript |
| 42 | +function sanitizeError(error: unknown): string { |
| 43 | + const err = error as { status?: number; response?: { status: number; data: string }; message?: string } | null; |
| 44 | + if (err && typeof err === 'object' && typeof err.status === 'number') { |
| 45 | + const status = err.status; |
| 46 | + if (status === 401 || status === 403) return 'Authentication or permission error...'; |
| 47 | + if (status === 404) return 'The requested content was not found...'; |
| 48 | + // ... |
| 49 | + } |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +**Never reference `err.response.status` from axios** - use `err.status` directly. |
| 54 | + |
| 55 | +### Tool Registration Pattern |
| 56 | +Use `McpServer.registerTool()` with Zod schemas. All tools wrapped in `toolHandler()` for consistent error handling: |
| 57 | + |
| 58 | +```typescript |
| 59 | +// packages/stdio/src/index.ts |
| 60 | +server.registerTool( |
| 61 | + "get_page", |
| 62 | + { |
| 63 | + title: "Get Page Content", |
| 64 | + description: "Get full content of a specific page", |
| 65 | + inputSchema: { |
| 66 | + id: z.number().int().min(1).describe("Page ID") |
| 67 | + } |
| 68 | + }, |
| 69 | + toolHandler(async (args) => { |
| 70 | + const page = await client.getPage(args.id); |
| 71 | + return { |
| 72 | + content: [{ type: "text", text: JSON.stringify(page, null, 2) }] |
| 73 | + }; |
| 74 | + }) |
| 75 | +); |
| 76 | +``` |
| 77 | + |
| 78 | +### Write Operations Security |
| 79 | +Write tools only registered when `BOOKSTACK_ENABLE_WRITE=true`: |
| 80 | + |
| 81 | +```typescript |
| 82 | +if (config.enableWrite) { |
| 83 | + server.registerTool("create_page", ...); |
| 84 | + server.registerTool("update_page", ...); |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +**Always check** `this.enableWrite` in BookStackClient before executing write methods. |
| 89 | + |
| 90 | +### Response Enhancement |
| 91 | +All API responses are enhanced in `packages/core/src/bookstack-client.ts`: |
| 92 | +- Add direct URLs using slugs: `${baseUrl}/books/${book.slug}/page/${page.slug}` |
| 93 | +- Add markdown links, content previews (150-200 chars), word counts |
| 94 | +- Convert timestamps to human-friendly dates |
| 95 | +- Include contextual location info (book/chapter hierarchy) |
| 96 | + |
| 97 | +Private methods: `enhanceBookResponse()`, `enhancePageResponse()`, `enhanceChapterResponse()`, `enhanceSearchResults()`. |
| 98 | + |
| 99 | +### URL Generation Strategy |
| 100 | +- **Prefer slugs over IDs**: URLs use `book.slug` and `page.slug` when available |
| 101 | +- **Maintain caches**: `bookSlugCache` and `pageInfoCache` avoid redundant API calls |
| 102 | +- **Fallback**: If slug unavailable, use ID |
| 103 | + |
| 104 | +## Development Workflows |
| 105 | + |
| 106 | +### Build & Run |
| 107 | +```bash |
| 108 | +npm install # Install all workspace dependencies |
| 109 | +npm run build # Compile TypeScript in all packages |
| 110 | +npm run type-check # Type-check without emitting |
| 111 | +npm run dev # Hot reload (tsx on packages/stdio) |
| 112 | +npm start # Run production build |
| 113 | +``` |
| 114 | + |
| 115 | +### Testing |
| 116 | +```bash |
| 117 | +npm test # Run packages/core tests with vitest |
| 118 | +``` |
| 119 | + |
| 120 | +**Requirements**: Tests need environment variables: |
| 121 | +- `TEST_BOOKSTACK_URL` |
| 122 | +- `TEST_BOOKSTACK_TOKEN_ID` |
| 123 | +- `TEST_BOOKSTACK_TOKEN_SECRET` |
| 124 | + |
| 125 | +Tests use `global-setup.ts` to seed data, `global-teardown.ts` to clean up. Sequential execution via vitest config. Helpers in `packages/core/tests/helpers.ts` provide `createWriteClient()`, `loadSeedData()`, etc. |
| 126 | + |
| 127 | +### Adding a New Tool |
| 128 | +1. Add method to `BookStackClient` in `packages/core/src/bookstack-client.ts` if needed |
| 129 | +2. Register tool in `packages/stdio/src/index.ts` using `server.registerTool()` |
| 130 | +3. If write operation, add inside `if (config.enableWrite) { ... }` block |
| 131 | +4. Use `toolHandler()` wrapper for consistent error handling |
| 132 | + |
| 133 | +### Modifying Enhancement Logic |
| 134 | +Edit private methods in `packages/core/src/bookstack-client.ts`: |
| 135 | +- `enhanceBookResponse()` - Add URLs, previews to books |
| 136 | +- `enhancePageResponse()` - Add URLs, word counts, previews to pages |
| 137 | +- `enhanceChapterResponse()` - Add URLs to chapters |
| 138 | +- `enhanceSearchResults()` - Add URLs, previews to search results |
| 139 | + |
| 140 | +## Configuration |
| 141 | + |
| 142 | +### Environment Variables (Required) |
| 143 | +```bash |
| 144 | +BOOKSTACK_BASE_URL=https://your-bookstack.com |
| 145 | +BOOKSTACK_TOKEN_ID=your-token-id |
| 146 | +BOOKSTACK_TOKEN_SECRET=your-token-secret |
| 147 | +BOOKSTACK_ENABLE_WRITE=false # Optional, defaults to false |
| 148 | +``` |
| 149 | + |
| 150 | +Validated in `packages/stdio/src/index.ts`: |
| 151 | +- `getRequiredEnvVar()` - Exits if missing |
| 152 | +- `validateBaseUrl()` - Checks URL format and scheme |
| 153 | + |
| 154 | +### TypeScript Configuration |
| 155 | +- Target: ES2022 with ESNext modules |
| 156 | +- Module resolution: Bundler (for workspace imports) |
| 157 | +- Strict mode: disabled (historical) |
| 158 | +- Output: `dist/` in each package |
| 159 | + |
| 160 | +## Deployment Options |
| 161 | + |
| 162 | +### Claude Desktop |
| 163 | +Add to `claude_desktop_config.json`: |
| 164 | +```json |
| 165 | +{ |
| 166 | + "mcpServers": { |
| 167 | + "bookstack": { |
| 168 | + "command": "node", |
| 169 | + "args": ["/path/to/packages/stdio/dist/index.js"], |
| 170 | + "env": { "BOOKSTACK_BASE_URL": "...", ... } |
| 171 | + } |
| 172 | + } |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +### LibreChat |
| 177 | +Add to `librechat.yaml`: |
| 178 | +```yaml |
| 179 | +mcpServers: |
| 180 | + bookstack: |
| 181 | + command: npx |
| 182 | + args: ["-y", "bookstack-mcp"] |
| 183 | + env: |
| 184 | + BOOKSTACK_BASE_URL: "https://..." |
| 185 | +``` |
| 186 | +
|
| 187 | +## Common Gotchas |
| 188 | +
|
| 189 | +1. **Axios references**: This codebase uses native `fetch`, not axios. Error properties are `error.status` and `error.response`, not `error.response.status`. |
| 190 | + |
| 191 | +2. **Workspace imports**: Use `@bookstack-mcp/core` to import from core package. TypeScript resolves via `exports` in `packages/core/package.json`. |
| 192 | + |
| 193 | +3. **Stdio logging**: Use `console.error()` for logs (goes to stderr). Never use `console.log()` in `packages/stdio/src/index.ts` - it breaks stdio protocol. |
| 194 | + |
| 195 | +4. **Write operations**: Always gate behind `config.enableWrite` check. Default is `false` for security. |
| 196 | + |
| 197 | +5. **Response format**: All tool handlers return `{ content: [{ type: "text", text: string }] }`. Use `JSON.stringify(result, null, 2)` for readability. |
| 198 | + |
| 199 | +6. **URL construction**: Always use slug-based URLs when available. Fallback to ID-based URLs. Never hardcode paths. |
| 200 | + |
| 201 | +## Reference Files |
| 202 | + |
| 203 | +- [packages/core/src/bookstack-client.ts](packages/core/src/bookstack-client.ts) - HTTP client, API methods, enhancement logic |
| 204 | +- [packages/stdio/src/index.ts](packages/stdio/src/index.ts) - Tool registration, error handling |
| 205 | +- [packages/core/src/types.ts](packages/core/src/types.ts) - Shared TypeScript types |
| 206 | +- [packages/core/tests/helpers.ts](packages/core/tests/helpers.ts) - Test utilities |
| 207 | +- [CLAUDE.md](CLAUDE.md) - Extended project documentation |
0 commit comments