slima-mcp 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -8
- package/dist/index.js +13 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/index.ts","../src/core/schema-compat.ts","../src/cli/server.ts","../src/core/utils/errors.ts","../src/core/api/client.ts","../src/core/tools/books.ts","../src/core/utils/file-tree.ts","../src/core/tools/content.ts","../src/core/tools/file-read-rendering.ts","../src/core/tools/beta-reader.ts","../src/core/tools/files.ts","../src/core/ai-file-policy.ts","../src/core/tools/structured.ts","../src/core/tools/teaching.ts","../src/core/tools/teams.ts","../src/core/tools/workspace.ts","../src/core/resources/book_schema.ts","../src/core/server-instructions.ts","../src/core/utils/logger.ts","../src/cli/token-storage.ts","../src/cli/auth.ts"],"sourcesContent":["/**\n * Slima MCP Server - CLI Entry Point\n *\n * Usage:\n * slima-mcp - Start MCP server (requires SLIMA_API_TOKEN or prior auth)\n * slima-mcp auth - Authenticate with Slima (opens browser)\n * slima-mcp logout - Remove saved credentials\n * slima-mcp status - Show authentication status\n * slima-mcp --help - Show help\n */\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { withGeminiSchemaCompat } from '../core/schema-compat.js';\nimport { createSlimaServer } from './server.js';\nimport { logger } from '../core/utils/logger.js';\nimport { loadToken, getCredentialsFilePath } from './token-storage.js';\nimport { runAuth, runLogout, runStatus } from './auth.js';\n\ndeclare const __VERSION__: string;\nconst VERSION = __VERSION__;\n\nfunction showHelp(): void {\n console.log(`\nSlima MCP v${VERSION}\nMCP Server for Slima - AI Writing IDE for Novel Authors\n\nUSAGE:\n slima-mcp [command]\n\nCOMMANDS:\n (none) Start the MCP server\n auth Authenticate with Slima (opens browser)\n auth --force Re-authenticate (switch account)\n logout Remove saved credentials\n status Show authentication status\n --help Show this help message\n --version Show version\n\nAUTHENTICATION:\n 1. Run: slima-mcp auth\n 2. Complete authentication in browser\n 3. Token is automatically saved\n\n Or set SLIMA_API_TOKEN environment variable.\n\nEXAMPLES:\n slima-mcp auth # Authenticate with browser\n slima-mcp auth --force # Re-authenticate (switch account)\n slima-mcp status # Check auth status\n SLIMA_API_TOKEN=xxx slima-mcp # Use env var\n\nCONFIGURATION (for AI tools):\n Claude Desktop, Cursor, etc.:\n {\n \"mcpServers\": {\n \"slima\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"slima-mcp\"]\n }\n }\n }\n\nMORE INFO:\n https://github.com/slima-ai/slima-mcp\n`);\n}\n\nfunction showVersion(): void {\n console.log(`slima-mcp v${VERSION}`);\n}\n\nasync function startServer(): Promise<void> {\n // Use env var first, then stored token\n let apiToken = process.env.SLIMA_API_TOKEN;\n let baseUrl = process.env.SLIMA_API_URL;\n\n if (!apiToken) {\n const credentials = await loadToken();\n if (credentials) {\n apiToken = credentials.apiToken;\n baseUrl = baseUrl || credentials.baseUrl;\n }\n }\n\n if (!apiToken) {\n logger.error('Not authenticated and SLIMA_API_TOKEN not set');\n logger.error('');\n logger.error('To authenticate, run:');\n logger.error(' slima-mcp auth');\n logger.error('');\n logger.error('Or set environment variable:');\n logger.error(' SLIMA_API_TOKEN=your_token slima-mcp');\n logger.error('');\n logger.error('Get your API token from: https://app.slima.ai/settings/api-tokens');\n process.exit(1);\n }\n\n try {\n const server = createSlimaServer({\n apiToken,\n baseUrl,\n });\n\n const transport = new StdioServerTransport();\n // Same schema sanitising as the Worker — a stdio client can be driven by Gemini too\n // (Gemini CLI, ADK agents), and one behaviour across both transports is one thing to\n // reason about. See src/core/schema-compat.ts.\n await server.connect(withGeminiSchemaCompat(transport));\n\n logger.info('Slima MCP Server running on stdio');\n\n // Graceful shutdown\n process.on('SIGINT', async () => {\n logger.info('Shutting down...');\n await server.close();\n process.exit(0);\n });\n\n process.on('SIGTERM', async () => {\n logger.info('Shutting down...');\n await server.close();\n process.exit(0);\n });\n } catch (error) {\n logger.error('Failed to start server', error);\n process.exit(1);\n }\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const command = args[0];\n\n switch (command) {\n case 'auth': {\n const forceFlag = args.includes('--force') || args.includes('-f');\n await runAuth({ force: forceFlag });\n break;\n }\n\n case 'logout':\n await runLogout();\n break;\n\n case 'status':\n await runStatus();\n break;\n\n case '--help':\n case '-h':\n case 'help':\n showHelp();\n break;\n\n case '--version':\n case '-v':\n showVersion();\n break;\n\n case undefined:\n // Default: start MCP server\n await startServer();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n console.error('Run `slima-mcp --help` for usage.');\n process.exit(1);\n }\n}\n\nmain();\n","/**\n * Tool-schema compatibility with Gemini's function-calling API.\n *\n * Gemini does NOT take JSON Schema for `functionDeclarations[].parameters`. It takes a\n * strict OpenAPI-3.0-subset **proto**, and an unknown field is not ignored — it is a hard\n * HTTP 400 for the ENTIRE request:\n *\n * Invalid JSON payload received. Unknown name \"$schema\" at\n * 'tools[0].function_declarations[0].parameters': Cannot find field.\n *\n * One bad key therefore costs the client *every* tool we publish, not just the tool that\n * carried it — and the failure is invisible from our side: the MCP handshake succeeds,\n * `tools/list` returns 200 with the full catalogue, and only Gemini's own model layer\n * rejects it afterwards.\n *\n * 2026-08-25 incident: a user connected Slima to the Gemini app. OAuth completed, the\n * token was issued, every `POST /mcp` returned 200/202 with zero 4xx — and Gemini told\n * them \"no Slima tools are registered\" and answered book questions from web search.\n * `api_tokens.last_used_at` stayed NULL for all three of their tokens: not one tool call\n * ever reached Rails. The whole cause was `$schema` + `additionalProperties`, which the\n * SDK's zod-to-json-schema emits automatically on every single tool.\n *\n * Both keys are pure metadata for tool-calling purposes — the server validates arguments\n * against the zod shape either way, so dropping them from the *advertised* schema changes\n * no behaviour. Verified against all three real APIs with our actual tool catalogue:\n * Anthropic and OpenAI accept the schemas with or without them; Gemini only without.\n */\n\nimport type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * Keys Gemini's `parameters` proto rejects outright. Measured, not assumed — every key\n * here was fed to `generateContent` and came back as `Unknown name \"<key>\"` (2026-08-25,\n * gemini-3-flash-preview). Kept as data so the guard test below can stay a data check.\n */\nexport const GEMINI_REJECTED_SCHEMA_KEYS = [\n '$ref',\n '$schema',\n 'additionalProperties',\n 'const',\n 'contains',\n 'deprecated',\n 'examples',\n 'exclusiveMaximum',\n 'exclusiveMinimum',\n 'multipleOf',\n 'patternProperties',\n 'readOnly',\n 'uniqueItems',\n 'writeOnly',\n] as const;\n\n/**\n * The subset we strip automatically.\n *\n * Deliberately only these two, NOT all of GEMINI_REJECTED_SCHEMA_KEYS. These two carry no\n * meaning a tool caller can act on, so removing them is lossless. The rest do carry\n * meaning — dropping a `$ref` leaves `{}` (a schema that describes nothing), dropping a\n * `const` silently widens an argument the tool actually constrains. Quietly mangling a\n * schema is worse than the 400 it avoids, so those stay unhandled on purpose and the\n * guard test in tests/worker/tool-schema-compat.test.ts turns red the day a tool starts\n * emitting one. Fix it at the zod shape then, not here.\n */\nexport const STRIPPED_SCHEMA_KEYS = ['$schema', 'additionalProperties'] as const;\n\nconst STRIPPED = new Set<string>(STRIPPED_SCHEMA_KEYS);\n\n/** Recursively copy a JSON Schema without the keys Gemini refuses to parse. */\nexport function stripIncompatibleSchemaKeys<T>(node: T): T {\n if (Array.isArray(node)) {\n return node.map((item) => stripIncompatibleSchemaKeys(item)) as unknown as T;\n }\n if (node !== null && typeof node === 'object') {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node as Record<string, unknown>)) {\n if (STRIPPED.has(key)) continue;\n out[key] = stripIncompatibleSchemaKeys(value);\n }\n return out as unknown as T;\n }\n return node;\n}\n\ninterface ToolListResult {\n tools?: Array<Record<string, unknown>>;\n}\n\n/**\n * Sanitize a `tools/list` response in place of the raw one. Any other message passes\n * through untouched (identity, same object) so this stays free on the hot path.\n */\nexport function sanitizeOutgoingMessage(message: JSONRPCMessage): JSONRPCMessage {\n const result = (message as { result?: ToolListResult }).result;\n if (!result || !Array.isArray(result.tools)) return message;\n\n return {\n ...message,\n result: {\n ...result,\n tools: result.tools.map((tool) => {\n const sanitized: Record<string, unknown> = { ...tool };\n for (const field of ['inputSchema', 'outputSchema']) {\n if (sanitized[field]) sanitized[field] = stripIncompatibleSchemaKeys(sanitized[field]);\n }\n return sanitized;\n }),\n },\n } as JSONRPCMessage;\n}\n\ninterface SendCapableTransport {\n send(message: JSONRPCMessage, options?: unknown): Promise<void>;\n}\n\n/**\n * Wrap a transport so every outgoing message goes through {@link sanitizeOutgoingMessage}.\n *\n * Hooking `send` rather than the `tools/list` request handler keeps this transport-shaped:\n * it covers JSON responses, SSE frames and stdio alike, and it does not reach into the\n * SDK's private handler registry. Call it BEFORE `server.connect(transport)`.\n */\nexport function withGeminiSchemaCompat<T extends SendCapableTransport>(transport: T): T {\n const send = transport.send.bind(transport);\n transport.send = (message: JSONRPCMessage, options?: unknown) =>\n send(sanitizeOutgoingMessage(message), options);\n return transport;\n}\n","/**\n * Slima MCP Server (CLI/stdio)\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { SlimaApiClient } from '../core/api/client.js';\nimport {\n registerBookTools,\n registerTeachingTools,\n registerTeamTools,\n registerWorkspaceTools,\n registerContentTools,\n registerBetaReaderTools,\n registerFileTools,\n registerStructuredTools,\n} from '../core/tools/index.js';\nimport { registerBookSchemaResource } from '../core/resources/index.js';\nimport { SERVER_INSTRUCTIONS } from '../core/server-instructions.js';\nimport { logger } from '../core/utils/logger.js';\n\ndeclare const __VERSION__: string;\n\nexport interface ServerConfig {\n apiToken: string;\n baseUrl?: string;\n}\n\n/**\n * Create Slima MCP Server\n */\nexport function createSlimaServer(config: ServerConfig): McpServer {\n // Create API Client with static token getter (for CLI)\n const client = new SlimaApiClient({\n baseUrl: config.baseUrl || 'https://api.slima.ai',\n getToken: async () => config.apiToken,\n logger,\n });\n\n const server = new McpServer(\n {\n name: 'slima',\n version: __VERSION__,\n },\n {\n instructions: SERVER_INSTRUCTIONS,\n }\n );\n\n // Register tools\n // Teaching first: it is what an AI should reach for before anything else,\n // and tool order is the only ordering signal the protocol carries.\n registerTeachingTools(server, client);\n registerBookTools(server, client);\n registerTeamTools(server, client);\n registerWorkspaceTools(server, client);\n registerContentTools(server, client);\n registerBetaReaderTools(server, client, logger);\n registerFileTools(server, client);\n registerStructuredTools(server, client);\n\n // Register resources (per-book schema via URI template)\n registerBookSchemaResource(server, client);\n\n logger.info('Slima MCP Server initialized');\n logger.info(`API endpoint: ${config.baseUrl || 'https://api.slima.ai'}`);\n\n return server;\n}\n","/**\n * 錯誤處理工具\n */\n\nexport class SlimaApiError extends Error {\n constructor(\n public status: number,\n public code?: string,\n message?: string\n ) {\n super(message || `API Error: ${status}`);\n this.name = 'SlimaApiError';\n }\n}\n\nexport class AuthenticationError extends SlimaApiError {\n constructor(message = 'Invalid or expired API token') {\n super(401, 'UNAUTHORIZED', message);\n this.name = 'AuthenticationError';\n }\n}\n\nexport class NotFoundError extends SlimaApiError {\n constructor(message?: string) {\n super(404, 'NOT_FOUND', message || 'Resource not found');\n this.name = 'NotFoundError';\n }\n}\n\nexport class InsufficientCreditsError extends SlimaApiError {\n constructor() {\n super(402, 'INSUFFICIENT_CREDITS', 'Insufficient credits');\n this.name = 'InsufficientCreditsError';\n }\n}\n\n/**\n * 429 from Rails' rack-attack.\n *\n * `retryAfter` is the whole point of having a distinct class: without it the\n * caller is told \"you were throttled\" but not for how long, and an agent's\n * only options are to guess or to give up. Rails runs ~24 throttle rules and\n * sends Retry-After with them; dropping that header turns a recoverable pause\n * into an opaque failure.\n */\nexport class RateLimitedError extends SlimaApiError {\n constructor(\n public retryAfterSeconds?: number,\n message?: string\n ) {\n super(429, 'RATE_LIMITED', message || 'Rate limited');\n this.name = 'RateLimitedError';\n }\n}\n\n/**\n * The request exceeded its own deadline — distinct from the server refusing\n * it. Nothing here used to time out at all, so a hung server meant a tool call\n * that never returned.\n */\nexport class RequestTimeoutError extends SlimaApiError {\n constructor(public timeoutMs: number) {\n super(408, 'REQUEST_TIMEOUT', `Request timed out after ${Math.round(timeoutMs / 1000)}s`);\n this.name = 'RequestTimeoutError';\n }\n}\n\n/**\n * 格式化錯誤訊息供 MCP 回傳\n */\nexport function formatErrorForMcp(error: unknown): string {\n if (error instanceof RateLimitedError) {\n const wait = error.retryAfterSeconds;\n\n return wait\n ? `Rate limited. Wait ${wait} second${wait === 1 ? '' : 's'} and try the same call again — this is a temporary throttle, not a rejected request.`\n : 'Rate limited. Wait a few seconds and try the same call again — this is a temporary throttle, not a rejected request.';\n }\n\n if (error instanceof RequestTimeoutError) {\n return `${error.message}. The server may still be processing it, so re-running a write could duplicate work — prefer re-reading the current state first.`;\n }\n\n if (error instanceof SlimaApiError) {\n switch (error.code) {\n case 'UNAUTHORIZED':\n return 'Invalid API token. Please check your configuration.';\n case 'INSUFFICIENT_CREDITS':\n return 'Insufficient credits. Please top up your account.';\n case 'TOKEN_EXPIRED':\n return 'API token has expired. Please generate a new one.';\n case 'TOKEN_REVOKED':\n return 'API token has been revoked. Please generate a new one.';\n default:\n return `Error: ${error.message}`;\n }\n }\n\n if (error instanceof Error) {\n return error.message;\n }\n\n return 'An unexpected error occurred.';\n}\n","/**\n * Slima API Client\n *\n * Supports dependency injection for token retrieval,\n * allowing different strategies for CLI (file-based) vs Worker (KV-based).\n */\n\n// Version is injected at build time by tsup `define` (CLI + Worker) and mirrored\n// in vitest.config.ts so tests see a real version string too.\ndeclare const __VERSION__: string;\n\nimport {\n type Book,\n type Commit,\n type Branch,\n type Blob,\n type Persona,\n type ReaderTest,\n type ReaderTestProgress,\n type CreateReaderTestParams,\n type ApiResponse,\n type ApiError,\n type CommitsListResponse,\n type BlobsDownloadResponse,\n type McpFileReadResponse,\n type McpFileCreateResponse,\n type McpFileUpdateResponse,\n type McpFileEditResponse,\n type McpFileDeleteResponse,\n type McpFileAppendResponse,\n type McpSearchResponse,\n type McpFileVisualizeResponse,\n type McpStructuredResponse,\n type BooksPage,\n type PaginationMeta,\n} from './types.js';\nimport {\n SlimaApiError,\n AuthenticationError,\n NotFoundError,\n InsufficientCreditsError,\n RateLimitedError,\n RequestTimeoutError,\n} from '../utils/errors.js';\n\n/**\n * Per-request deadline.\n *\n * Sixty seconds is well clear of any single Slima endpoint — the long-running\n * one (Beta Reader) is polled in short calls rather than held open — while\n * still being short enough that a wedged server surfaces as an error the agent\n * can act on instead of a tool call that never comes back.\n */\nconst REQUEST_TIMEOUT_MS = 60_000;\n\n/**\n * Retry-After is either a delta in seconds or an HTTP date (RFC 9110). Both\n * appear in the wild, so handle both and return undefined rather than a wrong\n * number when it is absent or unparseable — a missing wait is better than a\n * confidently incorrect one.\n */\nfunction parseRetryAfter(response: Response): number | undefined {\n // Guarded the same way handleUpgradeHint is: mocked Response objects in tests\n // do not always carry a full Headers interface, and a throttle response is a\n // bad moment to crash on a missing accessor.\n const header = response.headers?.get?.('retry-after');\n if (!header) return undefined;\n\n const seconds = Number(header);\n if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);\n\n const when = Date.parse(header);\n if (Number.isNaN(when)) return undefined;\n\n return Math.max(0, Math.ceil((when - Date.now()) / 1000));\n}\n\n// Export for logger injection (optional)\nexport interface Logger {\n debug: (message: string, ...args: unknown[]) => void;\n info: (message: string, ...args: unknown[]) => void;\n warn: (message: string, ...args: unknown[]) => void;\n error: (message: string, error?: unknown) => void;\n}\n\n// Default no-op logger\nconst defaultLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n\n/**\n * API Client Configuration (new style with token getter)\n */\nexport interface ApiClientConfig {\n baseUrl: string;\n getToken: () => Promise<string>; // Async token getter (injected)\n logger?: Logger;\n}\n\n/**\n * Legacy config format (backwards compatibility)\n * @deprecated Use ApiClientConfig with getToken instead\n */\nexport interface SlimaConfig {\n baseUrl: string;\n token: string;\n}\n\n// Type guard to check if config is legacy format\nfunction isLegacyConfig(config: ApiClientConfig | SlimaConfig): config is SlimaConfig {\n return 'token' in config && typeof (config as SlimaConfig).token === 'string';\n}\n\nexport class SlimaApiClient {\n private readonly baseUrl: string;\n private readonly getToken: () => Promise<string>;\n private readonly logger: Logger;\n\n constructor(config: ApiClientConfig | SlimaConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n\n // Support both legacy and new config formats\n if (isLegacyConfig(config)) {\n const staticToken = config.token;\n this.getToken = async () => staticToken;\n this.logger = defaultLogger;\n } else {\n this.getToken = config.getToken;\n this.logger = config.logger || defaultLogger;\n }\n }\n\n /**\n * Send API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n options: { envelope?: boolean } = {}\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const token = await this.getToken();\n\n this.logger.debug(`${method} ${path}`);\n\n // Nothing here used to bound the call. If Rails stopped responding the\n // fetch would sit open for whatever the host runtime allowed, and the MCP\n // client had no deadline of its own — a hung server became a tool call\n // that never returned.\n const controller = new AbortController();\n const deadline = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n 'User-Agent': `slima-mcp-server/${__VERSION__}`,\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (error) {\n // Check the signal, not just the error name. `AbortError` is what Node and\n // browsers throw, but this same client runs in Cloudflare Workers, and a\n // runtime that names its abort differently would otherwise slip past into\n // the generic branch and lose the \"the server may still be processing it\"\n // guidance. The name check stays as the fallback for the reverse case —\n // an abort surfacing before the signal flips.\n if (controller.signal.aborted || (error instanceof Error && error.name === 'AbortError')) {\n throw new RequestTimeoutError(REQUEST_TIMEOUT_MS);\n }\n // Anything else — including a platform-level timeout the runtime imposes\n // on its own, which we did not trigger and cannot recognise — is passed\n // through unchanged rather than mislabelled as our deadline.\n throw error;\n } finally {\n clearTimeout(deadline);\n }\n\n // Rails surfaces a soft staleness signal via this header when the client\n // is older than McpVersions::MIN_RECOMMENDED. Log once per request so the\n // user / wrapping AI client notices without hard-failing the call.\n this.handleUpgradeHint(response);\n\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n const json = (await response.json()) as ApiResponse<T>;\n // `envelope` keeps meta (pagination totals); the default unwraps to `data`.\n return (options.envelope ? json : json.data) as T;\n }\n\n /**\n * Like #request but keeps the whole envelope. Paginated endpoints carry the\n * real total in `meta`, and dropping it is how `list_books` came to report a\n * capped page as if it were the whole library.\n *\n * Delegates to #request rather than re-implementing fetch/auth/error handling:\n * the first version copied those ~20 lines and had already lost request()'s\n * debug log, so list_books was the one call invisible at debug level.\n */\n private async requestWithMeta<T>(\n method: string,\n path: string\n ): Promise<{ data: T; meta?: PaginationMeta }> {\n return this.request<{ data: T; meta?: PaginationMeta }>(method, path, undefined, {\n envelope: true,\n });\n }\n\n /**\n * Surface the X-Slima-MCP-Upgrade-Hint header (if present) as a warn log.\n * Deduped per (hint, baseUrl) to keep stderr quiet during long sessions.\n * Defensive: mocked Response objects in tests may not carry a full Headers\n * interface, so guard the access rather than hard-crashing the request.\n */\n private readonly loggedUpgradeHints = new Set<string>();\n private handleUpgradeHint(response: Response): void {\n const hint = response?.headers?.get?.('X-Slima-MCP-Upgrade-Hint');\n if (!hint) return;\n\n const key = `${hint}::${this.baseUrl}`;\n if (this.loggedUpgradeHints.has(key)) return;\n this.loggedUpgradeHints.add(key);\n\n this.logger.warn(\n `[slima-mcp] ${hint} — upgrade options: npm install -g slima-mcp@latest, ` +\n 'or change your Claude Desktop config to \"npx -y slima-mcp@0\".'\n );\n }\n\n /**\n * Handle error response\n */\n private async handleErrorResponse(response: Response): Promise<never> {\n let errorData: ApiError | undefined;\n\n try {\n errorData = (await response.json()) as ApiError;\n } catch {\n // Cannot parse JSON\n }\n\n const code = errorData?.error?.code;\n const message = errorData?.error?.message;\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(message);\n case 404:\n throw new NotFoundError(message);\n case 402:\n throw new InsufficientCreditsError();\n case 429:\n // Rails' rack-attack sends Retry-After alongside the 429. Carrying it\n // through is what separates \"wait N seconds, then repeat the call\"\n // from a generic failure the agent has to guess its way out of.\n throw new RateLimitedError(parseRetryAfter(response), message);\n default:\n throw new SlimaApiError(response.status, code, message);\n }\n }\n\n // === Book Operations ===\n\n /**\n * List books (one page). `meta` carries the true total — callers must report\n * it rather than presenting a page as the whole library.\n */\n async listBooks(options: { page?: number } = {}): Promise<BooksPage> {\n const page = options.page && options.page > 1 ? `&page=${options.page}` : '';\n const response = await this.requestWithMeta<Book[]>(\n 'GET',\n `/api/v1/books?status=active${page}`\n );\n return { books: response.data ?? [], meta: response.meta ?? {} };\n }\n\n /**\n * Get book details\n */\n // === Teaching surface =====================================================\n //\n // These are why the client stays thin. Everything they return is authored and\n // served by Rails, so the guidance an AI reads updates when the server\n // deploys — including for the users who pinned an old version of this package,\n // who are exactly the ones stale guidance would mislead.\n\n async getCapabilities(): Promise<unknown> {\n return this.request('GET', '/api/v1/mcp/capabilities');\n }\n\n async listGuides(): Promise<{ guides: Array<{ slug: string; title: string; summary: string }> }> {\n return this.request('GET', '/api/v1/mcp/guides');\n }\n\n async loadGuide(slug: string): Promise<{ slug: string; title: string; body: string }> {\n return this.request('GET', `/api/v1/mcp/guides/${encodeURIComponent(slug)}`);\n }\n\n async listWritingSkills(): Promise<{ skills: Array<{ key: string; name: string; description: string }> }> {\n return this.request('GET', '/api/v1/mcp/skills');\n }\n\n async loadWritingSkill(key: string): Promise<{ key: string; name: string; body: string }> {\n return this.request('GET', `/api/v1/mcp/skills/${encodeURIComponent(key)}`);\n }\n\n // === Teams ================================================================\n\n async listTeams(): Promise<unknown[]> {\n return this.request('GET', '/api/v1/teams');\n }\n\n async getTeam(token: string): Promise<unknown> {\n return this.request('GET', `/api/v1/teams/${token}`);\n }\n\n async listTeamSkills(token: string): Promise<unknown[]> {\n return this.request('GET', `/api/v1/teams/${token}/skills`);\n }\n\n async listTeamMemories(token: string): Promise<unknown> {\n return this.request('GET', `/api/v1/teams/${token}/memories`);\n }\n\n async listTeamRules(token: string): Promise<unknown> {\n return this.request('GET', `/api/v1/teams/${token}/rules`);\n }\n\n async semanticSearch(bookToken: string, query: string, limit?: number): Promise<unknown> {\n return this.request('POST', `/api/v1/books/${bookToken}/semantic-search`, { query, limit });\n }\n\n // === Notes and comments ===================================================\n\n async listNotes(bookToken: string): Promise<unknown> {\n return this.request('GET', `/api/v1/books/${bookToken}/notes`);\n }\n\n async createNote(bookToken: string, note: Record<string, unknown>): Promise<unknown> {\n return this.request('POST', `/api/v1/books/${bookToken}/notes`, { note });\n }\n\n async listComments(bookToken: string): Promise<unknown> {\n return this.request('GET', `/api/v1/books/${bookToken}/comments`);\n }\n\n async createComment(bookToken: string, comment: Record<string, unknown>): Promise<unknown> {\n return this.request('POST', `/api/v1/books/${bookToken}/comments`, { comment });\n }\n\n async resolveComment(bookToken: string, commentToken: string): Promise<unknown> {\n return this.request('POST', `/api/v1/books/${bookToken}/comments/${commentToken}/resolve`);\n }\n\n async reopenComment(bookToken: string, commentToken: string): Promise<unknown> {\n return this.request('POST', `/api/v1/books/${bookToken}/comments/${commentToken}/reopen`);\n }\n\n async getBook(token: string): Promise<Book> {\n return this.request<Book>('GET', `/api/v1/books/${token}`);\n }\n\n /**\n * Create a new book\n */\n async createBook(params: {\n title: string;\n authorName?: string;\n description?: string;\n }): Promise<Book> {\n return this.request<Book>('POST', '/api/v1/books', {\n book: {\n title: params.title,\n author_name: params.authorName,\n description: params.description,\n },\n });\n }\n\n // === Version Control ===\n\n /**\n * List commits\n */\n async listCommits(bookToken: string, limit = 10): Promise<Commit[]> {\n const response = await this.request<CommitsListResponse>(\n 'GET',\n `/api/v1/books/${bookToken}/commits?limit=${limit}`\n );\n return response.commits;\n }\n\n /**\n * Get commit details\n */\n async getCommit(bookToken: string, commitToken: string): Promise<Commit> {\n return this.request<Commit>(\n 'GET',\n `/api/v1/books/${bookToken}/commits/${commitToken}`\n );\n }\n\n /**\n * List branches\n */\n async listBranches(bookToken: string): Promise<Branch[]> {\n return this.request<Branch[]>('GET', `/api/v1/books/${bookToken}/branches`);\n }\n\n /**\n * Download blobs\n */\n async downloadBlobs(bookToken: string, hashes: string[]): Promise<Blob[]> {\n const response = await this.request<BlobsDownloadResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/blobs/download`,\n { hashes }\n );\n return response.blobs;\n }\n\n // === Persona ===\n\n /**\n * List personas\n */\n async listPersonas(genre?: string): Promise<Persona[]> {\n const params = genre ? `?genre=${encodeURIComponent(genre)}` : '';\n return this.request<Persona[]>('GET', `/api/v1/personas${params}`);\n }\n\n /**\n * Get persona details\n */\n async getPersona(token: string): Promise<Persona> {\n return this.request<Persona>('GET', `/api/v1/personas/${token}`);\n }\n\n // === Reader Test ===\n\n /**\n * Create reader test\n */\n async createReaderTest(\n bookToken: string,\n params: CreateReaderTestParams\n ): Promise<ReaderTest> {\n return this.request<ReaderTest>(\n 'POST',\n `/api/v1/books/${bookToken}/reader_tests`,\n {\n reader_test: {\n report_type: params.reportType || 'chapter',\n persona_tokens: params.personaTokens,\n commit_token: params.commitToken,\n model: params.model,\n scope_config: params.scopeConfig,\n },\n content: params.content,\n }\n );\n }\n\n /**\n * Get reader test progress\n */\n async getReaderTestProgress(\n bookToken: string,\n testToken: string\n ): Promise<ReaderTestProgress> {\n return this.request<ReaderTestProgress>(\n 'GET',\n `/api/v1/books/${bookToken}/reader_tests/${testToken}/progress`\n );\n }\n\n /**\n * Get reader test result\n */\n async getReaderTest(bookToken: string, testToken: string): Promise<ReaderTest> {\n return this.request<ReaderTest>(\n 'GET',\n `/api/v1/books/${bookToken}/reader_tests/${testToken}`\n );\n }\n\n // === MCP File Operations ===\n\n /**\n * Read file content\n */\n async readFile(bookToken: string, path: string): Promise<McpFileReadResponse> {\n return this.request<McpFileReadResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/read`,\n { path }\n );\n }\n\n /**\n * Create file\n */\n async createFile(\n bookToken: string,\n params: {\n path: string;\n content?: string;\n parentPath?: string;\n commitMessage?: string;\n /** manuscript | reference | character | location | storyline */\n contentType?: string;\n }\n ): Promise<McpFileCreateResponse> {\n return this.request<McpFileCreateResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/create`,\n {\n path: params.path,\n content: params.content || '',\n parent_path: params.parentPath,\n commit_message: params.commitMessage,\n content_type: params.contentType,\n }\n );\n }\n\n /**\n * Update (overwrite) file content\n */\n async updateFile(\n bookToken: string,\n params: { path: string; content: string; commitMessage?: string }\n ): Promise<McpFileUpdateResponse> {\n return this.request<McpFileUpdateResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/update`,\n {\n path: params.path,\n content: params.content,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * Field-level operations on a structured file — the ONLY write path for\n * `.beats` / `.timeline` / `.script`.\n *\n * One endpoint takes every type; the type comes from the path's extension.\n * The separate tool names on this side exist because a tool that writes must\n * start with a verb, or the desktop agent will not know to ask the human.\n */\n async applyStructuredOps(\n bookToken: string,\n params: { path: string; intent: string; operations: Array<Record<string, unknown>> }\n ): Promise<McpStructuredResponse> {\n return this.request<McpStructuredResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/structured`,\n { path: params.path, intent: params.intent, operations: params.operations }\n );\n }\n\n /**\n * Search-and-replace inside a file, applied SERVER-side.\n *\n * The old client-side read-modify-write reported a replacement count this\n * process computed and blind-wrote the whole file back, so concurrent edits\n * silently clobbered each other while both callers saw a success\n * (iss_gkqwnovhajzl7xvz). Rails re-reads inside its branch lock.\n */\n async editFile(\n bookToken: string,\n params: {\n path: string;\n oldString: string;\n newString: string;\n replaceAll?: boolean;\n commitMessage?: string;\n }\n ): Promise<McpFileEditResponse> {\n return this.request<McpFileEditResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/edit`,\n {\n path: params.path,\n old_string: params.oldString,\n new_string: params.newString,\n replace_all: params.replaceAll,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * Delete file\n */\n async deleteFile(\n bookToken: string,\n params: { path: string; commitMessage?: string; recursive?: boolean }\n ): Promise<McpFileDeleteResponse> {\n return this.request<McpFileDeleteResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/delete`,\n {\n path: params.path,\n commit_message: params.commitMessage,\n recursive: params.recursive,\n }\n );\n }\n\n /**\n * Append content to file\n */\n async appendToFile(\n bookToken: string,\n params: { path: string; content: string; commitMessage?: string }\n ): Promise<McpFileAppendResponse> {\n return this.request<McpFileAppendResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/append`,\n {\n path: params.path,\n content: params.content,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * Render a structured file (today: .map relationship graphs) as an image\n * plus a text summary.\n */\n async visualizeFile(\n bookToken: string,\n path: string\n ): Promise<McpFileVisualizeResponse> {\n return this.request<McpFileVisualizeResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/visualize`,\n { path }\n );\n }\n\n /**\n * Search file content\n */\n async searchFiles(\n bookToken: string,\n params: { query: string; fileTypes?: string[]; limit?: number }\n ): Promise<McpSearchResponse> {\n return this.request<McpSearchResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/search`,\n {\n query: params.query,\n file_types: params.fileTypes,\n limit: params.limit,\n }\n );\n }\n}\n","/**\n * Book Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport type { Book } from '../api/types.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport { buildFileTree, formatFileTree } from '../utils/file-tree.js';\nimport type { FlatFileSnapshot } from '../utils/file-tree.js';\n\n// Shared Studio presentation helpers. Kept here because they're only useful\n// for books.ts' markdown output formatting; other tools use bookType directly.\nfunction studioLabel(book: Pick<Book, 'bookType'>): string {\n if (book.bookType === 'script') return '📝 Script Studio';\n // A team's shared knowledge base used to fall through to \"Writing Studio\",\n // which hid both that it is team-owned and that some of its files may be\n // present-but-withheld.\n if (book.bookType === 'knowledge') return '📚 Team Knowledge Base';\n return '📖 Writing Studio';\n}\n\nfunction scriptStudioWriteRulesBlock(book: Pick<Book, 'token'>): string {\n return `### 🔒 Write Restrictions (Script Studio)\n\nThis book uses **structured schemas**. Via MCP you can:\n- ✅ **Read** any file (series.json, *.character, *.scene, etc.)\n- ✅ **Write / Edit / Delete** under \\`.script_studio/planning/**/*\\` only (free-form reference tree)\n- ❌ **Cannot modify** structured files (\\`series.json\\`, \\`season.json\\`, \\`episode.json\\`, \\`*.character\\`, \\`*.location\\`, \\`*.scene\\`, \\`*.storyline\\`, \\`*.note\\`)\n- ❌ **Cannot modify** \\`.script_studio/planning/.initialized\\` (frontend bootstrap sentinel)\n\nTo add characters / scenes / storylines, use the Script Studio UI in the Slima app.\n\nFor full per-book details, read the MCP resource: \\`slima://books/${book.token}/schema\\`.`;\n}\n\n/**\n * Register book-related tools\n */\nexport function registerBookTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === create_book ===\n server.tool(\n 'create_book',\n `Create a new **Writing Studio** book in your Slima library.\n\nOnly Writing Studio (free-form novels / long-form prose) books can be created via MCP.\nTo create a **Script Studio** book (screenwriting with structured series/seasons/episodes/scenes),\nplease use the Slima app UI — MCP does not yet support Script Studio scaffolding.`,\n {\n title: z.string().describe('Book title (required)'),\n author_name: z.string().optional().describe('Author name (optional)'),\n description: z.string().optional().describe('Book description (optional)'),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ title, author_name, description }) => {\n try {\n const book = await client.createBook({\n title,\n authorName: author_name,\n description,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `✅ Book created successfully!\\n\\n- **Title**: ${book.title}\\n- **Token**: ${book.token}\\n- **Author**: ${book.authorName || 'N/A'}\\n\\nYou can now add files using create_file with book_token: ${book.token}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === list_books ===\n server.tool(\n 'list_books',\n 'List the books in your Slima library. Results are paginated; if the response says more books exist, call again with the next `page` before concluding a book is not there.',\n {\n page: z.number().optional().describe('Page number (default 1). Only needed when the response says more books exist.'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ page }) => {\n try {\n const { books, meta } = await client.listBooks({ page });\n\n if (books.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No books found in your library.',\n },\n ],\n };\n }\n\n const formatted = books\n .map((book) => {\n const words = book.totalWordCount?.toLocaleString() || '0';\n return `- **${book.title}** (${book.token}) — ${studioLabel(book)}\\n Author: ${book.authorName || 'N/A'} | Words: ${words}`;\n })\n .join('\\n\\n');\n\n // Never present a page as the whole library (CLAUDE.md Gotcha #12: a\n // silent truncation reads as \"that book does not exist\"). Rails caps an\n // un-paginated /books at 100 and reports the real total in meta.\n //\n // \"Are there more?\" is meta.page < meta.totalPages — NOT total >\n // books.length. The latter is also true on the LAST page (37 of 137),\n // where it sent the AI to fetch a page 3 that does not exist.\n const currentPage = meta.page ?? 1;\n const totalPages = meta.totalPages;\n const hasMore = totalPages !== undefined && currentPage < totalPages;\n const header = hasMore\n ? `Showing ${books.length} of ${meta.total} book(s) — page ${currentPage} of ${totalPages}. Call list_books again with \\`page: ${currentPage + 1}\\` for the rest.`\n : meta.total !== undefined && meta.total > books.length\n ? `Showing ${books.length} of ${meta.total} book(s) — page ${currentPage} of ${totalPages ?? '?'} (this is the last page).`\n : `Found ${books.length} book(s):`;\n\n return {\n content: [\n {\n type: 'text',\n text: `${header}\\n\\n${formatted}\\n\\n_📝 Script Studio books have write restrictions via MCP (see \\`get_book\\` or resource \\`slima://books/{token}/schema\\`). 📖 Writing Studio books are fully writable._`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_book ===\n server.tool(\n 'get_book',\n 'Get details of a specific book',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token }) => {\n try {\n const book = await client.getBook(book_token);\n\n const header = `# ${studioLabel(book)} — ${book.title}\n\n- **Token**: ${book.token}\n- **Author**: ${book.authorName || 'N/A'}\n- **Description**: ${book.description || 'N/A'}\n- **Language**: ${book.language || 'N/A'}\n- **Total Words**: ${book.totalWordCount?.toLocaleString() || '0'}\n- **Manuscript Words**: ${book.manuscriptWordCount?.toLocaleString() || '0'}\n- **Reference Words**: ${book.referenceWordCount?.toLocaleString() || '0'}\n- **Created**: ${book.createdAt}\n- **Updated**: ${book.updatedAt}`;\n\n const text =\n book.bookType === 'script'\n ? `${header}\\n\\n${scriptStudioWriteRulesBlock(book)}`\n : header;\n\n return {\n content: [{ type: 'text', text }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_book_structure ===\n server.tool(\n 'get_book_structure',\n 'Get the file/folder structure of a book',\n {\n book_token: z.string().describe('Book token'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token }) => {\n try {\n const [book, commits] = await Promise.all([\n client.getBook(book_token),\n client.listCommits(book_token, 1),\n ]);\n\n if (commits.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No commits found. The book may be empty.',\n },\n ],\n };\n }\n\n // API returns flat structure with parentToken, convert to nested for display\n const flatSnapshot = commits[0].filesSnapshot as unknown as FlatFileSnapshot[];\n const nestedTree = buildFileTree(flatSnapshot);\n const tree = formatFileTree(nestedTree);\n\n const footer =\n book.bookType === 'script'\n ? `\\n\\n_📝 This is a Script Studio book. Via MCP, writes are restricted to \\`.script_studio/planning/**/*\\`. For the full write-restriction spec, read resource \\`slima://books/${book.token}/schema\\`._`\n : '';\n\n return {\n content: [\n {\n type: 'text',\n text: `File structure for ${studioLabel(book)} — ${book.title} (${book_token}):\\n\\n${tree}${footer}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_writing_stats ===\n server.tool(\n 'get_writing_stats',\n `Get word-count totals for a book.\n\nReports the book's authoritative totals only. Do NOT compute \"progress\", \"words\nwritten today\", or a writing streak from commit history — Slima prunes intra-day\ncommits after 30 days, so any delta derived from \"the last N commits\" silently\nchanges span over time and will not match what the author sees in the app. Those\nfigures are account-level and are not exposed here.`,\n {\n book_token: z.string().describe('Book token'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token }) => {\n try {\n const book = await client.getBook(book_token);\n\n const text = `# Writing Stats: ${book.title}\n\n- **Total Words**: ${book.totalWordCount?.toLocaleString() || '0'}\n- **Manuscript Words**: ${book.manuscriptWordCount?.toLocaleString() || '0'} (the book itself — what counts toward the author's word count)\n- **Reference Words**: ${book.referenceWordCount?.toLocaleString() || '0'} (notes, worldbuilding — never counted)\n- **Language**: ${book.language || 'N/A'}\n- **Last updated**: ${book.updatedAt}\n\n_Daily word count, writing streak and rest protection are account-level in Slima and are not available through this tool. Do not estimate them from commit history — the figure would not match the author's._`;\n\n return {\n content: [{ type: 'text', text }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * File Tree Utilities\n *\n * Converts flat parentToken-based file structure (from API)\n * to nested children-based structure (for display).\n */\n\n/**\n * File snapshot with nested children (for display)\n */\nexport interface FileSnapshot {\n token: string;\n name: string;\n kind: 'file' | 'folder' | string;\n position: number;\n blobHash?: string;\n wordCount?: number;\n isManuscript?: boolean;\n children?: FileSnapshot[];\n}\n\n/**\n * Flat file snapshot with parentToken (from API)\n * Supports both camelCase and snake_case (API may return either)\n */\nexport interface FlatFileSnapshot {\n token: string;\n name: string;\n kind: 'file' | 'folder' | string;\n position: number;\n // camelCase\n blobHash?: string;\n wordCount?: number;\n isManuscript?: boolean;\n parentToken?: string;\n // snake_case (Rails API)\n blob_hash?: string;\n word_count?: number;\n is_manuscript?: boolean;\n parent_token?: string;\n}\n\n/**\n * Get parent token from file (supports both camelCase and snake_case)\n */\nfunction getParentToken(file: FlatFileSnapshot): string | undefined {\n return file.parentToken || file.parent_token;\n}\n\n/**\n * Get word count from file (supports both camelCase and snake_case)\n */\nfunction getWordCount(file: FlatFileSnapshot): number | undefined {\n return file.wordCount ?? file.word_count;\n}\n\n/**\n * Get isManuscript from file (supports both camelCase and snake_case)\n */\nfunction getIsManuscript(file: FlatFileSnapshot): boolean | undefined {\n return file.isManuscript ?? file.is_manuscript;\n}\n\n/**\n * Get blobHash from file (supports both camelCase and snake_case)\n */\nfunction getBlobHash(file: FlatFileSnapshot): string | undefined {\n return file.blobHash || file.blob_hash;\n}\n\n/**\n * Convert flat parentToken structure to nested children structure\n *\n * @param flatFiles - Array of files with parentToken references\n * @returns Array of root-level files with nested children\n */\nexport function buildFileTree(flatFiles: FlatFileSnapshot[]): FileSnapshot[] {\n if (flatFiles.length === 0) {\n return [];\n }\n\n // Create a map for quick lookup\n const fileMap = new Map<string, FileSnapshot>();\n\n // First pass: create all FileSnapshot objects (without children)\n for (const file of flatFiles) {\n fileMap.set(file.token, {\n token: file.token,\n name: file.name,\n kind: file.kind,\n position: file.position,\n blobHash: getBlobHash(file),\n wordCount: getWordCount(file),\n isManuscript: getIsManuscript(file),\n children: [],\n });\n }\n\n // Second pass: build parent-child relationships\n const rootFiles: FileSnapshot[] = [];\n\n for (const file of flatFiles) {\n const node = fileMap.get(file.token)!;\n const parentToken = getParentToken(file);\n\n if (parentToken) {\n // Has parent - add to parent's children\n const parent = fileMap.get(parentToken);\n if (parent) {\n parent.children = parent.children || [];\n parent.children.push(node);\n } else {\n // Parent not found - treat as root\n rootFiles.push(node);\n }\n } else {\n // No parent - this is a root node\n rootFiles.push(node);\n }\n }\n\n // Sort children by position recursively\n sortChildrenByPosition(rootFiles);\n\n // Clean up empty children arrays\n cleanEmptyChildren(rootFiles);\n\n return rootFiles;\n}\n\n/**\n * Recursively sort children by position\n */\nfunction sortChildrenByPosition(files: FileSnapshot[]): void {\n files.sort((a, b) => a.position - b.position);\n\n for (const file of files) {\n if (file.children && file.children.length > 0) {\n sortChildrenByPosition(file.children);\n }\n }\n}\n\n/**\n * Remove empty children arrays for cleaner output\n */\nfunction cleanEmptyChildren(files: FileSnapshot[]): void {\n for (const file of files) {\n if (file.children && file.children.length === 0) {\n delete file.children;\n } else if (file.children) {\n cleanEmptyChildren(file.children);\n }\n }\n}\n\n/**\n * Format nested file tree as display string\n *\n * @param files - Nested file structure (with children)\n * @param indent - Current indentation level\n * @returns Formatted tree string\n */\nexport function formatFileTree(files: FileSnapshot[], indent = 0): string {\n return files\n .sort((a, b) => a.position - b.position)\n .map((f) => {\n const prefix = ' '.repeat(indent);\n const icon = f.kind === 'folder' ? '📁' : '📄';\n const words = f.kind !== 'folder' && f.wordCount ? ` (${f.wordCount} words)` : '';\n const manuscript = f.isManuscript ? ' [M]' : '';\n let line = `${prefix}${icon} ${f.name}${words}${manuscript}`;\n\n if (f.children && f.children.length > 0) {\n line += '\\n' + formatFileTree(f.children, indent + 1);\n }\n\n return line;\n })\n .join('\\n');\n}\n","/**\n * Content Access Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp, NotFoundError } from '../utils/errors.js';\nimport { renderFileRead } from './file-read-rendering.js';\nimport type { FileSnapshot } from '../api/types.js';\n\n/**\n * Register content access tools\n */\nexport function registerContentTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === get_chapter ===\n server.tool(\n 'get_chapter',\n 'Read a chapter or file from a book. Accepts a full path, or just a filename if you are not sure of the folder. If you already know the exact path, `read_file` is equivalent and more direct.',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n file_path: z.string().describe('File path (e.g., /chapters/01.md) or just a filename (e.g., 01.md)'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token, file_path }) => {\n const normalized = file_path.replace(/^\\//, '');\n\n try {\n // Straight through Rails' audited read. This tool used to reimplement\n // reading — list commits, scan the snapshot, download the blob by hash —\n // which skipped the team-skill audit trail and daily read budget\n // entirely, never saw contentWithheld/throttled, and needed two\n // endpoints a narrowly-scoped mcp:books token cannot reach.\n const result = await client.readFile(book_token, normalized);\n return { content: [{ type: 'text', text: renderFileRead(result) }] };\n } catch (error) {\n if (!(error instanceof NotFoundError)) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n // Fall through: the path may have been a bare filename.\n }\n\n try {\n // One listCommits for both the resolution AND the not-found hint: the\n // hint used to re-fetch the identical snapshot, doubling the cost of the\n // exact case (a miss) that is already the slow path.\n const { resolvedPath, availablePaths } = await resolveFromStructure(client, book_token, normalized);\n\n if (!resolvedPath) {\n const available = availablePaths;\n const hint = available.length\n ? `\\n\\nAvailable files:\\n- ${available.slice(0, 10).join('\\n- ')}${available.length > 10 ? `\\n... and ${available.length - 10} more` : ''}`\n : '';\n return {\n content: [\n {\n type: 'text',\n text: `File not found: ${file_path}${hint}\\n\\nTip: use get_book_structure to see the full file tree.`,\n },\n ],\n isError: true,\n };\n }\n\n // Read the RESOLVED path through the same audited endpoint — resolution\n // decides *which* file, it never becomes a second way to read one.\n const result = await client.readFile(book_token, resolvedPath);\n return { content: [{ type: 'text', text: renderFileRead(result) }] };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n/** Full path of a snapshot entry, walking parentToken up to the root. */\nfunction fullPathOf(entry: FileSnapshot, byToken: Map<string, FileSnapshot>): string {\n const parts = [entry.name];\n let current: FileSnapshot | undefined = entry;\n let depth = 0;\n\n while (current) {\n const parentToken = (current as unknown as { parentToken?: string }).parentToken;\n if (!parentToken) break;\n const parent: FileSnapshot | undefined = byToken.get(parentToken);\n if (!parent || depth++ > byToken.size) break;\n parts.unshift(parent.name);\n current = parent;\n }\n\n return parts.join('/');\n}\n\nfunction flatten(files: FileSnapshot[]): FileSnapshot[] {\n // Snapshots are flat (parentToken) in current data, but older ones nested\n // children — handle both rather than silently seeing half the tree.\n return files.flatMap((f) => [f, ...(f.children ? flatten(f.children) : [])]);\n}\n\n/**\n * Resolve a loose reference (bare filename, partial path, case-insensitive) to\n * one real path, AND return every available path for the not-found hint — both\n * from a single snapshot fetch.\n */\nasync function resolveFromStructure(\n client: SlimaApiClient,\n bookToken: string,\n needle: string\n): Promise<{ resolvedPath: string | null; availablePaths: string[] }> {\n const commits = await client.listCommits(bookToken, 1);\n if (commits.length === 0) return { resolvedPath: null, availablePaths: [] };\n\n const entries = flatten((commits[0].filesSnapshot ?? []) as FileSnapshot[]);\n const byToken = new Map(entries.map((e) => [e.token, e]));\n const wanted = needle.toLowerCase();\n\n const stripExt = (value: string) => value.replace(/\\.[^./]+$/, '');\n const candidates = entries.filter((e) => e.kind !== 'folder');\n\n // Same set of loose forms the previous implementation accepted (exact path,\n // bare name, file token, name without extension) — narrowing it here would be\n // a silent capability regression for anyone whose prompts rely on it.\n const match =\n candidates.find((e) => fullPathOf(e, byToken).toLowerCase() === wanted) ??\n candidates.find((e) => e.name.toLowerCase() === wanted) ??\n candidates.find((e) => e.token.toLowerCase() === wanted) ??\n candidates.find((e) => fullPathOf(e, byToken).toLowerCase().endsWith(`/${wanted}`)) ??\n candidates.find((e) => stripExt(e.name.toLowerCase()) === stripExt(wanted));\n\n return {\n resolvedPath: match ? fullPathOf(match, byToken) : null,\n availablePaths: candidates.map((e) => fullPathOf(e, byToken)),\n };\n}\n\n\n\n","/**\n * How a file read is presented to the model — in ONE place.\n *\n * `read_file` and `get_chapter` both surface the same Rails response\n * (`/mcp/files/read`), and they used to render it independently: read_file said\n * \"(empty file)\" for a withheld confidential skill while get_chapter said \"could\n * not retrieve file content\". Neither was true, and the drift meant fixing one\n * left the other lying.\n *\n * The three outcomes are distinct on purpose. A withheld file is not an error\n * and not empty — the AI may still USE that skill, it just may not read the\n * source, and telling it anything else sends it off to \"repair\" a file it is\n * forbidden to write.\n */\nimport type { McpFileReadResponse } from '../api/types.js';\n\nexport function renderFileRead(result: McpFileReadResponse): string {\n if (result.contentWithheld) {\n return [\n `# ${result.file.name} 🔒`,\n '',\n `**Path:** ${result.file.path}`,\n `**Words:** ${result.file.wordCount}`,\n '',\n '---',\n '',\n `The content of this file is withheld from you (reason: ${result.withheldReason ?? 'confidential'}).`,\n 'It is a confidential team skill: your team allows it to be USED but not READ,',\n 'so Slima applies it on the server without showing you the source.',\n '',\n 'This is intentional, not an error and not an empty file. Do not try to rewrite,',\n 'restore or \"fix\" it — writes to this path are rejected. If you need its contents,',\n 'ask a team manager.',\n ].join('\\n');\n }\n\n if (result.throttled) {\n return [\n `# ${result.file.name} ⏳`,\n '',\n `**Path:** ${result.file.path}`,\n `**Words:** ${result.file.wordCount}`,\n '',\n '---',\n '',\n `This team skill's read budget is exhausted for now (${result.throttledReason ?? 'daily_read_limit'}).`,\n 'The file is not empty — try again later, or ask a team manager.',\n ].join('\\n');\n }\n\n return [\n `# ${result.file.name}`,\n '',\n `**Path:** ${result.file.path}`,\n `**Words:** ${result.file.wordCount}`,\n `**Type:** ${result.file.fileType || 'unknown'}`,\n '',\n '---',\n '',\n result.content || '(empty file)',\n ].join('\\n');\n}\n","/**\n * Beta Reader Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient, Logger } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport type { FileSnapshot, ReaderTest, IndividualFeedback, AggregatedMetrics } from '../api/types.js';\n\n// Default no-op logger\nconst defaultLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n\n/**\n * Register Beta Reader tools\n */\nexport function registerBetaReaderTools(\n server: McpServer,\n client: SlimaApiClient,\n logger: Logger = defaultLogger\n): void {\n // === list_personas ===\n server.tool(\n 'list_personas',\n 'List available virtual reader personas for beta reading feedback',\n {\n genre: z\n .string()\n .optional()\n .describe('Filter by genre (e.g., fantasy, romance, scifi)'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ genre }) => {\n try {\n const personas = await client.listPersonas(genre);\n\n if (personas.length === 0) {\n const genreText = genre ? ` for genre \"${genre}\"` : '';\n return {\n content: [\n {\n type: 'text',\n text: `No personas found${genreText}.`,\n },\n ],\n };\n }\n\n const formatted = personas\n .map((p) => {\n const name = p.displayLabels?.zh || p.displayLabels?.en || p.slug;\n const tags = p.genreTags?.join(', ') || 'General';\n const demo = p.demographics || {};\n const gender = demo.gender || 'N/A';\n const age = demo.ageRange || 'N/A';\n\n return `- **${name}** (\\`${p.token}\\`)\n Gender: ${gender} | Age: ${age}\n Genres: ${tags}`;\n })\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `Available Personas (${personas.length}):\\n\\n${formatted}\\n\\n**Tip**: Use the persona token with \\`analyze_chapter\\` to get feedback from a specific reader.`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === analyze_chapter ===\n server.tool(\n 'analyze_chapter',\n `Get AI Beta Reader feedback on a chapter from a specific reader persona. This may take a moment to complete.\n\n**Not supported for Script Studio books** (\\`book_type: \"script\"\\`) — AI Beta Reader is a Writing Studio feature and does not yet parse \\`.scene\\` or \\`series.json\\` structures. Use the Script Studio UI's built-in analysis for screenplays.`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123) — must be a Writing Studio book (book_type: \"book\").'),\n file_path: z.string().describe('Chapter file path (e.g., /chapters/01.md)'),\n persona_token: z\n .string()\n .describe('Persona token (e.g., psn_xxx). Use list_personas to find available personas.'),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ book_token, file_path, persona_token }) => {\n try {\n // Gate: reject Script Studio books — AI Beta Reader does not parse\n // scene/character schema yet. Fetch before any other work so we don't\n // waste a commits/list call.\n const book = await client.getBook(book_token);\n if (book.bookType === 'script') {\n return {\n content: [\n {\n type: 'text',\n text: `❌ analyze_chapter is not available for Script Studio books.\\n\\nBook \"${book.title}\" (${book.token}) is a Script Studio book. Please use the Script Studio UI's built-in analysis features (structural notes, character consistency, scene-level feedback) — AI Beta Reader is a Writing Studio feature.`,\n },\n ],\n isError: true,\n };\n }\n\n // 1. Get latest commit and chapter content\n const commits = await client.listCommits(book_token, 1);\n\n if (commits.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No commits found. The book may be empty.',\n },\n ],\n isError: true,\n };\n }\n\n // 2. Find file\n const file = findFileInSnapshot(commits[0].filesSnapshot, file_path);\n\n if (!file) {\n return {\n content: [\n {\n type: 'text',\n text: `File not found: ${file_path}. Use get_book_structure to see available files.`,\n },\n ],\n isError: true,\n };\n }\n\n if (!file.blobHash) {\n return {\n content: [\n {\n type: 'text',\n text: `File \"${file_path}\" has no content (empty file).`,\n },\n ],\n isError: true,\n };\n }\n\n // 3. Download content\n const blobs = await client.downloadBlobs(book_token, [file.blobHash]);\n\n if (blobs.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'Could not retrieve file content.',\n },\n ],\n isError: true,\n };\n }\n\n const content = blobs[0].content;\n\n // 4. Create Reader Test\n logger.info(`Creating reader test for ${file_path} with persona ${persona_token}`);\n\n const test = await client.createReaderTest(book_token, {\n reportType: 'chapter',\n personaTokens: [persona_token],\n commitToken: commits[0].token,\n scopeConfig: { chapters: [file_path] },\n content: content,\n });\n\n // 5. Poll for completion (max 90 seconds)\n const result = await pollForCompletion(\n () => client.getReaderTestProgress(book_token, test.token),\n 90000,\n logger\n );\n\n if (result.status === 'failed') {\n return {\n content: [\n {\n type: 'text',\n text: 'Analysis failed. Please try again later.',\n },\n ],\n isError: true,\n };\n }\n\n if (result.status !== 'completed') {\n // The report is still running server-side — and it has already been\n // paid for. Naming get_reader_test here matters: the previous wording\n // (\"you can check the status later\") pointed at a capability that did\n // not exist, so an agent's only reachable next step was to call\n // analyze_chapter again, which starts a *second* report and charges a\n // second time.\n return {\n content: [\n {\n type: 'text',\n text: `The report is still being written (${result.progress ?? 0}% complete). It is running on the server and has already been paid for.\n\nCall \\`get_reader_test\\` with this token in a little while to collect it:\n\n book_token: ${book_token}\n test_token: ${test.token}\n\nDo NOT call analyze_chapter again for this chapter — that starts a separate report and charges for it a second time.`,\n },\n ],\n };\n }\n\n // 6. Get full result\n const fullResult = await client.getReaderTest(book_token, test.token);\n\n // 7. Format feedback\n const feedback = formatBetaReaderFeedback(fullResult, file.name);\n\n return {\n content: [{ type: 'text', text: feedback }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // Collect a report that analyze_chapter started but could not wait out.\n //\n // Without this, analyze_chapter's timeout branch had nowhere to send the\n // agent: it handed back a test token that no tool accepted. The only move\n // left was to re-run analyze_chapter, which bills a second report for work\n // already in flight. A wrong next step is expensive here, not just unhelpful.\n server.tool(\n 'get_reader_test',\n `Collect an AI Beta Reader report by its test token — for a report that analyze_chapter started but did not finish inside its wait window.\n\nUse this rather than calling analyze_chapter again: the report is already running server-side and has already been charged. Re-running starts a second report and charges again.`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n test_token: z\n .string()\n .describe('Reader test token, as handed back by analyze_chapter when it timed out'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token, test_token }) => {\n try {\n const progress = await client.getReaderTestProgress(book_token, test_token);\n\n if (progress.status === 'failed') {\n return {\n content: [\n {\n type: 'text',\n text: 'That report failed while running. Start a new one with analyze_chapter.',\n },\n ],\n isError: true,\n };\n }\n\n if (progress.status !== 'completed') {\n return {\n content: [\n {\n type: 'text',\n text: `Still being written (${progress.progress ?? 0}% complete). Call get_reader_test again with the same token shortly — do not start a new analysis, this one is still running and already paid for.`,\n },\n ],\n };\n }\n\n const fullResult = await client.getReaderTest(book_token, test_token);\n\n return {\n content: [\n { type: 'text', text: formatBetaReaderFeedback(fullResult, describeScope(fullResult)) },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n/**\n * A heading for a report collected by token rather than by file.\n *\n * analyze_chapter knows the file it was handed; get_reader_test only has the\n * token, and ReaderTest carries no filename. Falling back to the report's own\n * scope keeps the heading honest instead of inventing a name.\n */\nfunction describeScope(test: ReaderTest): string {\n const scopePath = test.scopeConfig?.filePath;\n\n return typeof scopePath === 'string' && scopePath.length > 0\n ? scopePath\n : `${test.reportType} report`;\n}\n\n/**\n * Poll for completion\n */\nasync function pollForCompletion(\n checkFn: () => Promise<{ status: string; progress?: number }>,\n timeoutMs: number,\n logger: Logger\n): Promise<{ status: string; progress?: number }> {\n const startTime = Date.now();\n const pollInterval = 3000; // 3 seconds\n // Carried out of the loop so a timeout can report how far the report actually\n // got. \"Still being written\" is far more useful with a number attached.\n let lastProgress: number | undefined;\n\n while (Date.now() - startTime < timeoutMs) {\n const result = await checkFn();\n lastProgress = result.progress;\n logger.debug(`Poll status: ${result.status}, progress: ${result.progress || 'N/A'}`);\n\n if (result.status === 'completed' || result.status === 'failed') {\n return result;\n }\n\n await sleep(pollInterval);\n }\n\n return { status: 'timeout', progress: lastProgress };\n}\n\n/**\n * Sleep\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Format Beta Reader feedback\n */\nfunction formatBetaReaderFeedback(test: ReaderTest, fileName: string): string {\n const feedbacks = test.individualFeedbacks || [];\n const metrics = test.aggregatedMetrics;\n\n if (feedbacks.length === 0 && !metrics) {\n return 'No feedback available.';\n }\n\n let output = `# Beta Reader Feedback: ${fileName}\\n\\n`;\n\n // Aggregated metrics summary\n if (metrics) {\n output += formatAggregatedMetrics(metrics);\n }\n\n // Individual persona feedback\n if (feedbacks.length > 0) {\n output += `\\n---\\n\\n## Individual Reader Feedback\\n\\n`;\n output += feedbacks.map(formatIndividualFeedback).join('\\n---\\n\\n');\n }\n\n return output;\n}\n\n/**\n * Format aggregated metrics\n */\nfunction formatAggregatedMetrics(metrics: AggregatedMetrics): string {\n let output = `## Summary Metrics\\n\\n`;\n\n // Overall\n if (metrics.overall) {\n const o = metrics.overall;\n output += `### Overall Scores\\n`;\n if (o.avgContinueReading) output += `- Continue Reading Intent: ${o.avgContinueReading.toFixed(1)}/10\\n`;\n if (o.avgRecommendation) output += `- Recommendation Score: ${o.avgRecommendation.toFixed(1)}/10\\n`;\n if (o.avgWantMore) output += `- Want More: ${o.avgWantMore.toFixed(1)}/10\\n`;\n if (o.dnfRisk) output += `- DNF Risk: ${o.dnfRisk}\\n`;\n output += '\\n';\n }\n\n // Kindle Rating\n if (metrics.kindleRating) {\n output += `### Predicted Kindle Rating\\n`;\n output += `- Average Score: ⭐ ${metrics.kindleRating.avgScore.toFixed(1)}/5\\n`;\n if (metrics.kindleRating.individualScores) {\n const scores = metrics.kindleRating.individualScores\n .map(s => `${s.personaName || 'Reader'}: ${s.score}/5 (${s.confidence})`)\n .join(', ');\n output += `- Individual Scores: ${scores}\\n`;\n }\n output += '\\n';\n }\n\n // Characters\n if (metrics.characters) {\n const c = metrics.characters;\n output += `### Character Evaluation\\n`;\n if (c.avgProtagonistLikability) output += `- Protagonist Likability: ${c.avgProtagonistLikability.toFixed(1)}/10\\n`;\n if (c.avgMotivationClarity) output += `- Motivation Clarity: ${c.avgMotivationClarity.toFixed(1)}/10\\n`;\n if (c.avgDialogueNaturalness) output += `- Dialogue Naturalness: ${c.avgDialogueNaturalness.toFixed(1)}/10\\n`;\n output += '\\n';\n }\n\n // Pacing\n if (metrics.pacing) {\n const p = metrics.pacing;\n output += `### Pacing Evaluation\\n`;\n if (p.avgOverall) output += `- Overall Pacing: ${p.avgOverall.toFixed(1)}/10\\n`;\n if (p.consensus) output += `- Consensus: ${p.consensus}\\n`;\n if (p.commonDnfTriggers && p.commonDnfTriggers.length > 0) {\n output += `- Common DNF Triggers:\\n`;\n p.commonDnfTriggers.slice(0, 3).forEach(t => {\n output += ` - ${t.trigger} (${t.count}x)\\n`;\n });\n }\n output += '\\n';\n }\n\n // Market\n if (metrics.market) {\n const m = metrics.market;\n output += `### Market Positioning\\n`;\n if (m.avgWordOfMouth) output += `- Word of Mouth Potential: ${m.avgWordOfMouth.toFixed(1)}/10\\n`;\n if (m.topComparableBooks && m.topComparableBooks.length > 0) {\n const books = m.topComparableBooks.slice(0, 3).map(b => b.title).join(', ');\n output += `- Comparable Books: ${books}\\n`;\n }\n output += '\\n';\n }\n\n return output;\n}\n\n/**\n * Format individual feedback\n */\nfunction formatIndividualFeedback(feedback: IndividualFeedback): string {\n const name = feedback.personaName || feedback.personaToken;\n let output = `### ${name}\\n\\n`;\n\n // Overall scores\n if (feedback.overall) {\n const o = feedback.overall;\n if (o.continueReading) output += `**Continue Reading Intent**: ${o.continueReading}/10\\n`;\n if (o.recommendation) output += `**Recommendation**: ${o.recommendation}/10\\n`;\n if (o.dnfRisk) output += `**DNF Risk**: ${o.dnfRisk}\\n`;\n output += '\\n';\n }\n\n // Kindle Rating\n if (feedback.kindleRating) {\n output += `**Kindle Rating**: ⭐ ${feedback.kindleRating.score}/5`;\n if (feedback.kindleRating.rationale) {\n output += ` - ${feedback.kindleRating.rationale}`;\n }\n output += '\\n\\n';\n }\n\n // Detailed Feedback\n if (feedback.detailedFeedback) {\n const d = feedback.detailedFeedback;\n\n if (d.whatWorked) {\n output += `**What Worked**:\\n${d.whatWorked}\\n\\n`;\n }\n\n if (d.whatDidntWork) {\n output += `**What Didn't Work**:\\n${d.whatDidntWork}\\n\\n`;\n }\n\n if (d.strongestElement) {\n output += `**Strongest Element**: ${d.strongestElement}\\n`;\n }\n\n if (d.weakestElement) {\n output += `**Weakest Element**: ${d.weakestElement}\\n`;\n }\n\n if (d.wouldYouBuy) {\n output += `**Would You Buy**: ${d.wouldYouBuy}\\n`;\n }\n\n if (d.specificSuggestions && d.specificSuggestions.length > 0) {\n output += `\\n**Specific Suggestions**:\\n`;\n d.specificSuggestions.forEach((s, i) => {\n output += `${i + 1}. ${s}\\n`;\n });\n }\n\n if (d.finalThoughts) {\n output += `\\n**Final Thoughts**:\\n${d.finalThoughts}\\n`;\n }\n }\n\n // Emotional Journey\n if (feedback.emotionalJourney) {\n const e = feedback.emotionalJourney;\n if (e.emotionalArc) {\n output += `\\n**Emotional Arc**: ${e.emotionalArc}\\n`;\n }\n if (e.peakMoments && e.peakMoments.length > 0) {\n const moments = e.peakMoments.map(m =>\n typeof m === 'string' ? m : (m as { moment?: string; description?: string }).moment || (m as { moment?: string; description?: string }).description || JSON.stringify(m)\n );\n output += `**Peak Moments**: ${moments.join(', ')}\\n`;\n }\n if (e.overallMood) {\n output += `**Overall Mood**: ${e.overallMood}\\n`;\n }\n }\n\n return output;\n}\n\n/**\n * Find file in snapshot\n */\nfunction findFileInSnapshot(\n files: FileSnapshot[],\n path: string\n): FileSnapshot | null {\n const normalizedPath = path.replace(/^\\//, '').toLowerCase();\n const pathParts = normalizedPath.split('/');\n\n function search(\n items: FileSnapshot[],\n remainingParts: string[]\n ): FileSnapshot | null {\n for (const item of items) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n if (remainingParts.length === 1) {\n const target = remainingParts[0];\n if (\n itemNameLower === target ||\n itemTokenLower === target ||\n itemNameLower.replace(/\\.[^.]+$/, '') === target.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n }\n\n if (item.kind === 'folder' && item.children) {\n if (\n itemNameLower === remainingParts[0] ||\n itemTokenLower === remainingParts[0]\n ) {\n const result = search(item.children, remainingParts.slice(1));\n if (result) return result;\n }\n\n const result = search(item.children, remainingParts);\n if (result) return result;\n }\n }\n\n return null;\n }\n\n let result = search(files, pathParts);\n if (result) return result;\n\n result = searchByName(files, pathParts[pathParts.length - 1]);\n return result;\n}\n\nfunction searchByName(\n files: FileSnapshot[],\n name: string\n): FileSnapshot | null {\n const nameLower = name.toLowerCase();\n\n for (const item of files) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n if (\n itemNameLower === nameLower ||\n itemTokenLower === nameLower ||\n itemNameLower.replace(/\\.[^.]+$/, '') === nameLower.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n\n if (item.kind === 'folder' && item.children) {\n const result = searchByName(item.children, name);\n if (result) return result;\n }\n }\n\n return null;\n}\n","/**\n * MCP File Operations Tools\n *\n * Provides tools for AI to read, write, create, delete, and search files in books.\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport { renderFileRead } from './file-read-rendering.js';\nimport { AI_WRITE_POLICY_NOTE } from '../ai-file-policy.js';\n\n// Studio-aware write rules shown on every write tool description + path param.\n// Rails (`Mcp::FileOperationService#validate_studio_write_path!`) enforces this\n// as the ground truth; duplicating it here lets the AI plan correctly instead\n// of bouncing off HTTP 400 INVALID_PATH.\nconst SCRIPT_STUDIO_WRITE_WARNING = `\n**IMPORTANT — Script Studio books (\\`book_type: \"script\"\\`):**\nWrites are restricted to \\`.script_studio/planning/**/*\\` only.\nStructured files (\\`series.json\\`, \\`*.character\\`, \\`*.scene\\`, \\`*.storyline\\`, \\`*.note\\`, \\`*.location\\`, \\`season.json\\`, \\`episode.json\\`) are READ-ONLY via MCP — structural edits must go through the Script Studio UI.\nThe sentinel file \\`.script_studio/planning/.initialized\\` is also read-only (frontend bootstrap marker).\nAttempting to write outside \\`planning/\\` returns HTTP 400 with code \\`INVALID_PATH\\`.\nFor per-book details, read resource \\`slima://books/{book_token}/schema\\`.\n\nFor Writing Studio books (\\`book_type: \"book\"\\`), all paths remain writable.\nAlways check \\`book_type\\` via \\`list_books\\` or \\`get_book\\` before writing to an unfamiliar book.`;\n\n// AI file-type policy, mirrored from slima_vue `src/services/fileTypeRegistry.ts`\n// (`aiPolicy`) via Rails `Conversations::SystemPromptBuilder::AI_FILE_POLICY`.\n// The frontend registry is the single source of truth; this is the third mirror,\n// which is exactly why it says as little as possible and points at the tools that\n// DO work instead of restating the whole table.\n//\n// Why it is worth repeating here at all: the in-app coach is told this in its\n// system prompt and is blocked client-side when it forgets. An external agent\n// gets neither, so without this text it will \"helpfully\" overwrite a `.map` with\n// prose and corrupt the author's relationship graph.\n// The write policy now lives in ai-file-policy.ts as one composed paragraph.\n// It used to be assembled here from AI_READONLY_EXTENSIONS alone, which is how\n// `.map` came to be described as read-only for weeks after it became writable\n// and `.beats` came to be described not at all: a list of \"the read-only ones\"\n// cannot express \"writable, but only whole-file\" or \"writable, via this tool\".\nconst AI_READONLY_WARNING = AI_WRITE_POLICY_NOTE;\n\n// Slima classifies every file as manuscript or reference (plus intrinsic planner\n// labels). ONLY manuscript counts toward the author's word count, writing streak,\n// Beta Reader chapter picker and manuscript export. A new file inherits its\n// folder and falls back to `reference` at the root, so prose written into a fresh\n// path is invisible in all of those unless it is declared.\nconst CONTENT_TYPE_HINT =\n 'How Slima classifies this file. ' +\n '\"manuscript\" = the book itself (prose/chapters) — this is what counts toward the author\\'s ' +\n 'word count, daily goal and writing streak. ' +\n '\"reference\" = notes, worldbuilding, research (never counted). ' +\n '\"character\" / \"location\" / \"storyline\" = planner cards, which are reference plus a label. ' +\n 'Omit to inherit the containing folder (root-level files default to \"reference\"). ' +\n 'If you were asked to WRITE a chapter, pass \"manuscript\" — otherwise those words will not ' +\n 'appear in the author\\'s word count.';\n\nconst PATH_PARAM_HINT =\n 'File path relative to book root. ' +\n 'For Script Studio books: MUST be under \".script_studio/planning/\" (e.g., \".script_studio/planning/references/outline.md\"). ' +\n 'For Writing Studio books: any path (e.g., \"characters/protagonist.md\").';\n\n// Structured file extensions owned by Script Studio. Read access still works;\n// search_content filters them by default (override with include_structured: true).\nconst SCRIPT_STRUCTURED_EXTENSIONS = ['character', 'scene', 'storyline', 'note', 'location'];\n\n/**\n * Register file operation tools\n */\nexport function registerFileTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === read_file ===\n server.tool(\n 'read_file',\n 'Read the content of any file in a book by its path. For `.map` relationship graphs, prefer `view_relationship_map` — this tool returns their raw JSON (node ids and pixel coordinates), which is hard to reason about.',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path (e.g., \"characters/protagonist.md\" or \"chapter-01.md\")'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token, path }) => {\n try {\n const result = await client.readFile(book_token, path);\n\n return {\n content: [{ type: 'text', text: renderFileRead(result) }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === write_file ===\n server.tool(\n 'write_file',\n `Replace the entire content of an existing file (creates a new commit).\n${SCRIPT_STUDIO_WRITE_WARNING}\n${AI_READONLY_WARNING}`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(PATH_PARAM_HINT),\n content: z.string().describe('New content to write'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },\n async ({ book_token, path, content, commit_message }) => {\n try {\n const result = await client.updateFile(book_token, {\n path,\n content,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `File updated successfully.\\n\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === edit_file ===\n server.tool(\n 'edit_file',\n `Replace a specific text segment in a file (search and replace). Use this for precise edits instead of rewriting the whole file.\n\nThe replacement is performed by the server inside a lock, so the reported count is what was actually written — not a local guess. If the text is missing or ambiguous the call fails with a message telling you what to do next; act on it rather than retrying the same arguments.\n${SCRIPT_STUDIO_WRITE_WARNING}\n${AI_READONLY_WARNING}`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(PATH_PARAM_HINT),\n old_string: z.string().describe('The exact text to find and replace (must match exactly, including whitespace)'),\n new_string: z.string().describe('The new text to replace it with (pass an empty string to delete)'),\n replace_all: z.boolean().optional().describe('Replace all occurrences (default: false, replaces only the first match)'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },\n async ({ book_token, path, old_string, new_string, replace_all, commit_message }) => {\n try {\n // Delegates outright: no read-then-write here. A local read-modify-write\n // cannot see a concurrent write, which is how two edits used to clobber\n // each other while both reported success (iss_gkqwnovhajzl7xvz).\n const result = await client.editFile(book_token, {\n path,\n oldString: old_string,\n newString: new_string,\n replaceAll: replace_all,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `✅ File edited.\\n\\n**Replacements applied by the server:** ${result.replacements}\\n**Commit:** ${result.commit.token}\\n**Message:** ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n // Rails' edit errors are already written for an AI reader (what went\n // wrong AND what to do next), so pass them through verbatim.\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === create_file ===\n server.tool(\n 'create_file',\n `Create a new file in a book (creates a new commit).\n\n**Writing a chapter? Pass \\`content_type: \"manuscript\"\\`.** Files default to\n\"reference\" (not counted in the author's word count, streak, or manuscript export)\nunless they inherit a manuscript folder or you say otherwise.\n${SCRIPT_STUDIO_WRITE_WARNING}\n${AI_READONLY_WARNING}`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(\n `${PATH_PARAM_HINT} Parent folders are auto-created (mkdir -p behavior).`\n ),\n content: z.string().optional().describe('Initial content (optional, defaults to empty)'),\n content_type: z\n .enum(['manuscript', 'reference', 'character', 'location', 'storyline'])\n .optional()\n .describe(CONTENT_TYPE_HINT),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ book_token, path, content, content_type, commit_message }) => {\n try {\n const result = await client.createFile(book_token, {\n path,\n content: content || '',\n contentType: content_type,\n commitMessage: commit_message,\n });\n\n // Report the classification back: \"created\" alone hid the one thing the\n // author would notice — whether those words count.\n const classification = content_type\n ? `Classified as: ${content_type}${content_type === 'manuscript' ? ' (counts toward the word count)' : ' (not counted in the word count)'}`\n : 'Classified by inheriting the parent folder (root-level defaults to reference, which is NOT counted in the word count).';\n\n // The server replaces the content with an empty skeleton for structured\n // types and says so in `notes`. Dropping that here would leave the model\n // with a 201 and the belief that its content is in the file — the exact\n // silent-success shape this whole surface is built to avoid.\n const notes = (result.notes ?? []).map((n) => `\\n\\n⚠️ ${n}`).join('');\n\n return {\n content: [\n {\n type: 'text',\n text: `File created successfully.\\n\\nFile Token: ${result.fileToken}\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}\\n${classification}${notes}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === delete_file ===\n server.tool(\n 'delete_file',\n `Delete a file or an empty folder from a book (creates a new commit).\n\nA folder that still has anything inside it is refused unless you pass\n\\`recursive: true\\`. That is not bureaucracy: deleting only the folder would\nleave its contents with no parent, and the app then shows the whole subtree at\nthe book's top level — the author sees their files scattered, with no error.\nSay the word and everything inside goes, and you are told how many.\n${SCRIPT_STUDIO_WRITE_WARNING}\n${AI_READONLY_WARNING}`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(PATH_PARAM_HINT),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n recursive: z\n .boolean()\n .optional()\n .describe('Required to delete a folder that is not empty — everything inside goes with it'),\n },\n { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },\n async ({ book_token, path, commit_message, recursive }) => {\n try {\n const result = await client.deleteFile(book_token, {\n path,\n commitMessage: commit_message,\n recursive,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: [\n 'File deleted successfully.',\n // The model asked to delete one path; several may have gone. It has to\n // be able to tell the author that.\n result.deletedDescendants\n ? `${result.deletedDescendants} item(s) inside it were deleted too — tell the author.`\n : null,\n '',\n `Commit: ${result.commit.token}`,\n `Message: ${result.commit.name || result.commit.message}`,\n ]\n .filter((line) => line !== null)\n .join('\\n'),\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === append_to_file ===\n server.tool(\n 'append_to_file',\n `Append content to the end of an existing file (creates a new commit).\n${SCRIPT_STUDIO_WRITE_WARNING}\n${AI_READONLY_WARNING}`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(PATH_PARAM_HINT),\n content: z.string().describe('Content to append'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ book_token, path, content, commit_message }) => {\n try {\n const result = await client.appendToFile(book_token, {\n path,\n content,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `Content appended successfully.\\n\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === view_relationship_map ===\n server.tool(\n 'view_relationship_map',\n `See a \\`.map\\` character-relationship graph as a picture plus a readable summary.\n\nUse this instead of \\`read_file\\` for \\`.map\\` files: their raw JSON is node ids and pixel coordinates.\nThe summary lists every character, every relationship (\"A —childhood friends→ B\") and any faction groups,\nso it is useful even in clients that do not display images.\n\n\\`.map\\` files are read-only to AI — describe changes you recommend in words; the author edits them in the map editor.`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('Path to a .map file (e.g., \"world/relations.map\")'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token, path }) => {\n try {\n const result = await client.visualizeFile(book_token, path);\n\n if (result.contentWithheld) {\n return {\n content: [\n {\n type: 'text',\n text: `🔒 \"${path}\" is a confidential team skill. Its contents are withheld from you by your team's settings (reason: ${result.withheldReason ?? 'confidential'}). This is intentional, not an error — do not try to rewrite it.`,\n },\n ],\n };\n }\n\n if (result.throttled) {\n return {\n content: [\n {\n type: 'text',\n text: `⏳ Read budget for this team skill is exhausted for today (${result.throttledReason ?? 'daily_read_limit'}). Try again later; the file is not empty.`,\n },\n ],\n };\n }\n\n // The summary goes FIRST and always: image blocks do not reach the model\n // on every MCP client (claude.ai / Claude Desktop remote connectors\n // currently drop them — anthropics/claude-ai-mcp#238), so a picture-only\n // reply would be an empty turn there.\n const content: Array<\n { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }\n > = [{ type: 'text', text: result.summary ?? '(no summary available)' }];\n\n if (result.format === 'png' && result.image) {\n content.push({ type: 'image', data: result.image, mimeType: 'image/png' });\n } else if (result.imageUnavailable) {\n content.push({\n type: 'text',\n text: `_(The image could not be rendered on the server, so only the summary above is available.)_`,\n });\n }\n\n return { content };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === search_content ===\n server.tool(\n 'search_content',\n `Search for content across all files in a book.\n\n**Script Studio behavior:** By default, structured files (\\`*.character\\`, \\`*.scene\\`, \\`*.storyline\\`, \\`*.note\\`, \\`*.location\\`, and \\`*.json\\` under \\`.script_studio/\\`) are **excluded** from search results — their raw JSON is noisy and not useful for full-text search.\nPass \\`include_structured: true\\` to search those files too.\nWriting Studio books are unaffected by this filter.`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n query: z.string().describe('Search query (e.g., \"blue eyes\" or \"protagonist name\")'),\n file_types: z\n .array(z.string())\n .optional()\n .describe('Filter by file extension, with or without the dot: [\"md\", \".txt\", \"beats\"]'),\n limit: z.number().optional().describe('Maximum number of matches to return (default: 20)'),\n include_structured: z\n .boolean()\n .optional()\n .describe(\n 'Script Studio only: set to true to include structured files (*.character / *.scene / etc.) in search results. Default: false (excluded).'\n ),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token, query, file_types, limit, include_structured }) => {\n try {\n // Only fetch the book when we actually need to decide about filtering.\n // For Writing Studio books the extra round-trip is wasted work.\n let bookType: string | undefined;\n if (!include_structured) {\n try {\n const book = await client.getBook(book_token);\n bookType = book.bookType;\n } catch {\n // If we can't load the book (eg. network hiccup), fall through without filtering.\n // The downstream search call will surface any real permission / not-found errors.\n }\n }\n\n const result = await client.searchFiles(book_token, {\n query,\n fileTypes: file_types,\n limit,\n });\n\n const shouldFilter = !include_structured && bookType === 'script';\n const filteredMatches = shouldFilter\n ? result.matches.filter((m) => !isStructuredScriptFile(m.file.path))\n : result.matches;\n\n if (filteredMatches.length === 0) {\n const suffix =\n shouldFilter && result.matches.length > 0\n ? ` (${result.matches.length} match(es) in structured files were excluded — pass include_structured: true to see them)`\n : '';\n return {\n content: [\n {\n type: 'text',\n text: `No matches found for \"${query}\"${suffix}.`,\n },\n ],\n };\n }\n\n const output: string[] = [\n `# Search Results for \"${query}\"`,\n '',\n `Found ${filteredMatches.length} file(s) with matches:`,\n '',\n ];\n\n if (shouldFilter && filteredMatches.length < result.matches.length) {\n const excluded = result.matches.length - filteredMatches.length;\n output.push(\n `_Note: excluded ${excluded} Script Studio structured file(s). Pass include_structured: true to include them._`\n );\n output.push('');\n }\n\n for (const match of filteredMatches) {\n output.push(`## ${match.file.path}`);\n output.push(`Words: ${match.file.wordCount} | Matches: ${match.matchCount}`);\n output.push('');\n\n for (const snippet of match.snippets) {\n output.push(`> ${snippet.text}`);\n output.push('');\n }\n }\n\n return {\n content: [{ type: 'text', text: output.join('\\n') }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n// Internal: path-based Script Studio structured file check.\n// Matches files with structured extensions OR any .json under .script_studio/.\nfunction isStructuredScriptFile(path: string): boolean {\n if (!path) return false;\n const lower = path.toLowerCase();\n if (lower.startsWith('.script_studio/') && lower.endsWith('.json')) return true;\n const ext = lower.split('.').pop();\n if (!ext) return false;\n return SCRIPT_STRUCTURED_EXTENSIONS.includes(ext);\n}\n","/**\n * What an AI may write, as ONE list.\n *\n * The authority is slima_vue `src/services/fileTypeRegistry.ts` (`aiPolicy`),\n * mirrored server-side in `db/contracts/file_type_registry.v1.json`. This file\n * is the third mirror, and it exists as data rather than prose specifically\n * because CLAUDE.md Gotcha #17 records what happens otherwise: `.map` was\n * AI-readonly from day one, the policy was restated in three separate places,\n * and one of them said \"write: Yes\" for months — so users burned credits on\n * writes that could never land.\n *\n * ## It has already drifted once, in both directions at the same time\n *\n * As of 2026-08-25 this file still listed `.map` as read-only (it became\n * writable on 2026-08-22) and did not mention `.beats` at all. So it was\n * simultaneously hiding a capability the author had paid for AND failing to\n * warn about one that could corrupt a file. A comment saying \"keep this in\n * sync\" does not keep anything in sync.\n *\n * ## The real fix, not done yet\n *\n * `GET /api/v1/mcp/capabilities` already returns this table, derived from the\n * contract Rails enforces. This file should be a build-time artifact of that\n * endpoint with CI failing when the committed copy differs. Until then, the\n * only honest thing this file can do is say as little as possible and point at\n * the tools that DO work — and `get_capabilities` remains the authority a model\n * should consult, which is why the server instructions tell it to call that first.\n */\n\n/** Generic write / edit / create / delete are allowed. */\nexport const AI_WRITABLE_EXTENSIONS = ['.md', '.markdown', '.txt'] as const;\n\n/**\n * Structured types with field-level MCP tools — the generic write tools are\n * refused, and the tool named here is the only way in.\n *\n * These are NOT read-only: saying so would hide a capability (the failure this\n * file's header describes, in the direction that costs the author money).\n *\n * All five landed together on 2026-09-06. Before that, `.map` and `.beats` took\n * a whole-document `write_file` and the other three were refused outright — two\n * different half-answers to the same question. A whole-document write is what\n * loses the parts of a file the model was never shown, so it is gone: the server\n * now refuses every generic write on these and names the tool below in the\n * refusal.\n */\nexport const AI_STRUCTURED_TOOLS: Record<string, string> = {\n '.beats': 'update_beat_board',\n '.timeline': 'update_timeline',\n '.script': 'update_script',\n '.map': 'update_relationship_map',\n '.geomap': 'update_world_map',\n};\n\n/**\n * No write tool may touch these — Slima's own editors own their format, and\n * there is no field-level route either.\n */\nexport const AI_READONLY_EXTENSIONS = [\n '.character',\n '.location',\n '.json',\n '.yaml',\n '.yml',\n] as const;\n\n/** Tools that CAN read a structured type usefully, keyed by extension. */\nexport const AI_STRUCTURED_VIEWERS: Record<string, string> = {\n '.map': 'view_relationship_map — renders the graph as an image plus a cast/relationship summary',\n};\n\n/**\n * The one paragraph the write tools carry. Deliberately short: it is paid for\n * on every request, including the conversations that never touch a file.\n */\nexport const AI_WRITE_POLICY_NOTE = `\n**Not every file type takes a generic write.**\n- Field-level tools only: ${Object.entries(AI_STRUCTURED_TOOLS)\n .map(([ext, tool]) => `\\`${ext}\\` → \\`${tool}\\``)\n .join(', ')}\n- Not writable at all: ${AI_READONLY_EXTENSIONS.map((e) => `\\`${e}\\``).join(', ')}\n\nA refusal names the file type and what to do instead — it is information, not\nan obstacle to route around. Call \\`get_capabilities\\` for the authoritative\ntable; this list ships with the client and can lag a deploy.`;\n","/**\n * Field-level operations on Slima's structured file types.\n *\n * ## Why these exist at all\n *\n * `.beats` / `.timeline` / `.script` are documents with a schema and a\n * reconciliation semantics, not text. Writing one whole-file means the model\n * has to send back every part of it — including the parts it was never given\n * and cannot see. That failure is silent: the file still parses, it just has\n * less of the author's work in it (a beat board loses its chapter cards; a\n * relationship map loses a character). These tools take one change at a time,\n * so the parts the model cannot see are parts it cannot drop.\n *\n * ## Why three tools and not forty-two\n *\n * Tool descriptions are sent on EVERY request, including the conversations\n * that only read a chapter. The individual operations for these three types\n * come to ~33KB of schema; batched, they are ~17KB, and the shape is one the\n * models have already been through Gemini/Grok's schema subset with. Each tool\n * takes `operations[]` — send one to make one change.\n *\n * ## Why the names start with a verb\n *\n * The desktop agent decides whether to ask a human by looking for a verb\n * prefix on the tool name. `beats_batch` does not read as a write, so it would\n * not prompt. `update_beat_board` does.\n * (NOTE: this is second-hand — confirm against the desktop agent's source\n * before treating the prefix list as exhaustive.)\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport type { McpStructuredResponse } from '../api/types.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\n\n/** One entry per structured type that has a server-side executor. */\ninterface StructuredType {\n /** MCP tool name — MUST start with a verb (see file header). */\n tool: string;\n extension: string;\n /** What the author calls it. */\n noun: string;\n /** The operations this type accepts, for the description. */\n ops: string;\n /** A concrete batch, in this type's language. */\n example: string;\n /** Rules that belong to this type alone. Omit when the shared ones are enough. */\n extra?: string;\n}\n\nconst TYPES: StructuredType[] = [\n {\n tool: 'update_beat_board',\n extension: '.beats',\n noun: 'beat board',\n ops: 'add_act, update_act, remove_act, add_beat, update_beat, remove_beat, move_beat',\n example:\n '{\"op\": \"add_act\", \"name\": \"Act Two\", \"op_ref\": \"A2\"}, ' +\n '{\"op\": \"add_beat\", \"title\": \"The turn\", \"act_id\": \"@A2\"}',\n },\n {\n tool: 'update_timeline',\n extension: '.timeline',\n noun: 'timeline',\n ops: 'add_entry, update_entry, remove_entry, set_config',\n example: '{\"op\": \"add_entry\", \"title\": \"The siege begins\", \"time_mode\": \"label\", \"time_label\": \"Winter\"}',\n },\n {\n tool: 'update_script',\n extension: '.script',\n noun: 'screenplay',\n ops: 'add_scene, update_scene_heading, remove_scene, add_element, update_element, remove_element',\n example:\n '{\"op\": \"add_scene\", \"interior\": true, \"location\": \"HARBOUR\", \"op_ref\": \"S1\"}, ' +\n '{\"op\": \"add_element\", \"scene_id\": \"@S1\", \"type\": \"action\", \"text\": \"The fog lifts.\"}',\n },\n {\n tool: 'update_relationship_map',\n extension: '.map',\n noun: 'relationship map',\n ops:\n 'add_node, update_node, move_node, remove_node, add_edge, update_edge, remove_edge, ' +\n 'add_group, update_group, remove_group, set_meta',\n example:\n '{\"op\": \"add_node\", \"label\": \"Lin Shuang\", \"op_ref\": \"N1\", ' +\n '\"position\": {\"near_id\": \"<id of Shen Yan>\", \"direction\": \"right\"}}, ' +\n '{\"op\": \"add_edge\", \"source_id\": \"<id of Shen Yan>\", \"target_id\": \"@N1\", \"label\": \"mentor\"}',\n extra:\n '**Position is content here, not layout.** `update_node` will not move anything — that is\\n' +\n '`move_node`, so that \"I moved her next to him\" is its own reviewable change. Prefer\\n' +\n '`{near_id, direction}` over computing numbers. Existing nodes never move; the final\\n' +\n 'coordinates come back in the notes. Group boxes are computed from `member_ids` — never\\n' +\n 'send x/y/width/height. Removing a node removes its edges, and you are told so.',\n },\n {\n tool: 'update_world_map',\n extension: '.geomap',\n noun: 'world map',\n ops:\n 'add_place, update_place, add_label, update_label, remove_element, add_river, ' +\n 'add_border, paint_region, set_meta',\n example:\n '{\"op\": \"add_place\", \"x\": 210, \"y\": 96, \"name\": \"Starhaven\", \"tier\": \"port\"}, ' +\n '{\"op\": \"add_label\", \"x\": 240, \"y\": 60, \"text\": \"The Cold Sea\", \"size\": \"large\"}',\n extra:\n '**Coordinates are grid cells, not pixels**: x 0-519, y 0-363. `read_file` gives you the\\n' +\n 'map with its three terrain grids replaced by a note — they are machine-encoded and you\\n' +\n 'do not need them; `paint_region` works in cells. `remove_element` takes any element id\\n' +\n '(`pl_` place, `lb_` label, `rv_` river, `bd_` border). Terrain has no undo: paint over it\\n' +\n 'instead. Move a place with `update_place`, never by removing and re-adding it. An unknown\\n' +\n 'place `tier` is refused with the full list of real ones — read the error, do not guess.',\n },\n];\n\n/**\n * Shared rules. Kept in one string because they are the same rules for every\n * type, and three drifting copies is how the `.map` policy ended up being\n * taught three different ways for months.\n */\nconst SHARED_RULES = `\n**Read the file first.** Every id you send must come from that read — never\ninvent one, never retype one from memory. An unknown id is refused with the\nlist of real ones, so a mistake costs a round trip, not the author's data.\n\n**One operation per change.** Each operation takes only its own arguments; a\nfield belonging to a different operation is refused rather than ignored.\n\n**\\`op_ref\\` chains within one batch.** Label what an operation creates with\n\\`op_ref: \"A2\"\\`, then a LATER operation can point at it as \\`\"@A2\"\\`. Backward\nreferences only, and only inside this one batch — a later call must use the\nreal id, which comes back in \\`applied\\`.\n\n**The batch is all-or-nothing.** If one operation fails nothing is written, so\nyou never have to work out how far it got.\n\n**Unlike the in-app assistant, there is no review panel here.** What you send\nlands in the author's version history immediately. Say what you changed, not\n\"please confirm it\".`;\n\nfunction describe(type: StructuredType): string {\n return `Change a Slima ${type.noun} (\\`${type.extension}\\`) one field at a time.\n\nThis is the ONLY way to write this file type — \\`write_file\\` and \\`edit_file\\`\nare refused for it, because a whole-file write would drop the parts of the\ndocument you were never shown.\n\nOperations: ${type.ops}\nExample \\`operations\\`: [${type.example}]\n${SHARED_RULES}${type.extra ? `\\n\\n${type.extra}` : ''}`;\n}\n\nfunction render(type: StructuredType, result: McpStructuredResponse): string {\n const lines = [`Applied ${result.applied.length} change(s) to the ${type.noun}.`, ''];\n\n const created = result.applied.filter((a) => a.id);\n if (created.length > 0) {\n lines.push('**New ids** (use these in later calls — do not re-read just for them):');\n for (const a of created) lines.push(`- \\`${a.op}\\` → ${a.id}${a.op_ref ? ` (was @${a.op_ref})` : ''}`);\n lines.push('');\n }\n\n // Side effects the model did not ask for. These are the whole reason the\n // server returns notes: the author will see them, so the author must be told.\n if (result.notes.length > 0) {\n lines.push('**Also happened, tell the author:**');\n for (const note of result.notes) lines.push(`- ${note}`);\n lines.push('');\n }\n\n lines.push(`**Commit:** ${result.commit.token}`);\n return lines.join('\\n');\n}\n\nexport function registerStructuredTools(server: McpServer, client: SlimaApiClient): void {\n for (const type of TYPES) {\n server.tool(\n type.tool,\n describe(type),\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(`Path to the ${type.extension} file (e.g., \"plan/board${type.extension}\")`),\n intent: z\n .string()\n .describe(\n \"One line in the author's own language saying what this batch does — it becomes the \" +\n 'commit message they read later. Not \"batch update\".'\n ),\n operations: z\n .array(z.record(z.unknown()))\n .describe(\n 'The changes, in order. Each is a flat object: `op` names the operation and the other ' +\n 'keys are that operation\\'s arguments. There is no nested `args`.'\n ),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ book_token, path, intent, operations }) => {\n try {\n const result = await client.applyStructuredOps(book_token, {\n path,\n intent,\n operations: operations as Array<Record<string, unknown>>,\n });\n return { content: [{ type: 'text', text: render(type, result) }] };\n } catch (error) {\n // Rails' messages for these already say what went wrong AND what to do\n // next (they list the real ids, name which operation owns a field), so\n // pass them through verbatim rather than summarising them away.\n return { content: [{ type: 'text', text: formatErrorForMcp(error) }], isError: true };\n }\n }\n );\n }\n}\n","/**\n * Teaching tools.\n *\n * Slima's operating knowledge — how commits work, what a manuscript file is,\n * which file types an AI may write — is authored and served by Rails, not\n * carried in this package. Users pin versions of this client; the\n * `X-Slima-MCP-Upgrade-Hint` machinery exists because they do. Guidance shipped\n * here would go stale in the field, silently, and would keep telling an AI to do\n * things the server no longer supports.\n *\n * So these tools are deliberately thin: they fetch and hand over text. The\n * judgement about what to say lives with the system being described.\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\n\nconst READ_ONLY = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n} as const;\n\nfunction text(body: string) {\n return { content: [{ type: 'text' as const, text: body }] };\n}\n\nfunction failure(error: unknown) {\n return { content: [{ type: 'text' as const, text: formatErrorForMcp(error) }], isError: true };\n}\n\nexport function registerTeachingTools(server: McpServer, client: SlimaApiClient): void {\n // === get_capabilities ===\n server.tool(\n 'get_capabilities',\n `What this Slima server supports: which file extensions an AI may write, the content types, the available tools, and the guide index.\n\nCall this once at the start of a Slima session, before writing anything. Every field is derived from the contract the server actually enforces, so it cannot drift from real behaviour the way a written description can.`,\n {},\n READ_ONLY,\n async () => {\n try {\n return text(JSON.stringify(await client.getCapabilities(), null, 2));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n // === list_guides ===\n server.tool(\n 'list_guides',\n `List the available Slima guides: titles and one-line summaries of when to load each.\n\nGuides teach how to operate Slima — file structure, version control, team knowledge bases, the relationship-map format. Load one with \\`load_guide\\` when you are about to do the thing it covers.`,\n {},\n READ_ONLY,\n async () => {\n try {\n const result = await client.listGuides();\n const rows = result.guides.map((g) => `- **${g.slug}** — ${g.title}\\n ${g.summary}`);\n return text(`## Slima guides\\n\\n${rows.join('\\n')}`);\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n // === load_guide ===\n server.tool(\n 'load_guide',\n `Load one Slima guide in full.\n\nRead \\`book-structure\\` before creating files, \\`version-control-basics\\` before a large rewrite, \\`team-knowledge-base\\` when working in a publisher's team, \\`beta-reader\\` before spending the author's credits on a report. \\`list_guides\\` has the current set.`,\n {\n slug: z.string().describe('Guide slug, e.g. \"book-structure\" (from list_guides)'),\n },\n READ_ONLY,\n async ({ slug }) => {\n try {\n const guide = await client.loadGuide(slug);\n return text(`# ${guide.title}\\n\\n${guide.body}`);\n } catch (error) {\n // The server answers an unknown slug with the list of real ones, so the\n // error is itself usable — passing it through beats replacing it with\n // \"not found\".\n return failure(error);\n }\n }\n );\n\n // === list_writing_skills ===\n server.tool(\n 'list_writing_skills',\n `List Slima's writing skills — the craft procedures the in-app coach follows for tasks like developing a character, revising a scene, or diagnosing pacing.\n\nUse these when the task is about the WRITING. Use \\`list_guides\\` when the task is about operating Slima.`,\n {},\n READ_ONLY,\n async () => {\n try {\n const result = await client.listWritingSkills();\n const rows = result.skills.map((s) => `- **${s.key}** — ${s.name}: ${s.description}`);\n return text(`## Writing skills\\n\\n${rows.join('\\n')}`);\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n // === load_writing_skill ===\n server.tool(\n 'load_writing_skill',\n `Load one writing skill in full — a step-by-step procedure plus the questions a coach would ask.\n\nFollow it rather than summarising it at the author.`,\n {\n key: z.string().describe('Skill key, e.g. \"character_develop\" (from list_writing_skills)'),\n },\n READ_ONLY,\n async ({ key }) => {\n try {\n const skill = await client.loadWritingSkill(key);\n return text(`# ${skill.name}\\n\\n${skill.body}`);\n } catch (error) {\n return failure(error);\n }\n }\n );\n}\n","/**\n * Team tools — read-only.\n *\n * Governance (enabling a skill, authoring a rule, changing the roster) is not\n * on the server's MCP surface for any role, so there is nothing to expose here.\n * Enabling a skill is what turns an unvetted file into something the AI applies\n * for the whole team; that stays a human decision.\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\n\nconst READ_ONLY = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n} as const;\n\nfunction json(body: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(body, null, 2) }] };\n}\n\nfunction failure(error: unknown) {\n return { content: [{ type: 'text' as const, text: formatErrorForMcp(error) }], isError: true };\n}\n\nconst TEAM_TOKEN = z.string().describe('Team token (from list_teams)');\n\nexport function registerTeamTools(server: McpServer, client: SlimaApiClient): void {\n server.tool(\n 'list_teams',\n `List the teams this account belongs to — publishers, studios, writing groups.\n\nA team book carries house rules and shared knowledge that change how you should write for it. If a book belongs to a team, read \\`list_team_rules\\` before writing.`,\n {},\n READ_ONLY,\n async () => {\n try {\n return json(await client.listTeams());\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'get_team',\n 'Get one team: its members and their roles.',\n { team_token: TEAM_TOKEN },\n READ_ONLY,\n async ({ team_token }) => {\n try {\n return json(await client.getTeam(team_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'list_team_rules',\n `The team's coaching rules — its house style — each marked mandatory or advisory.\n\nRead these BEFORE writing for a team book, not after a draft is rejected. Mandatory rules are not suggestions: prose that ignores one gets re-edited by a person, which is worse than not writing it.`,\n { team_token: TEAM_TOKEN },\n READ_ONLY,\n async ({ team_token }) => {\n try {\n return json(await client.listTeamRules(team_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'list_team_memories',\n `What the team has established over time: recurring characters, naming conventions, continuity spanning books, decisions already argued through.\n\nTreat these as settled. If one contradicts a book's text, say so rather than picking a side.`,\n { team_token: TEAM_TOKEN },\n READ_ONLY,\n async ({ team_token }) => {\n try {\n return json(await client.listTeamMemories(team_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'list_team_skills',\n `The team's own writing procedures.\n\nSome are marked \\`confidential: true\\` and come back WITHOUT a source path; their file contents are withheld from this connection. That is deliberate, and it does not stop the skill working — the server applies it on the team's behalf. Do not try to reconstruct one, reproduce it verbatim, or reach its contents by another route: those routes are closed and audited. Read the \\`team-knowledge-base\\` guide before working in a team knowledge base.`,\n { team_token: TEAM_TOKEN },\n READ_ONLY,\n async ({ team_token }) => {\n try {\n return json(await client.listTeamSkills(team_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n}\n","/**\n * Notes and comments — the author's working material around the manuscript.\n *\n * Destructive operations are absent because the server does not offer them to\n * MCP: a note is not version-controlled (unlike a file, there is no history to\n * recover it from), and accepting a suggested change applies an edit to the\n * manuscript, which is the author's decision rather than the agent's.\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\n\nconst READ_ONLY = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n} as const;\n\nconst WRITES = {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n} as const;\n\nconst BOOK_TOKEN = z.string().describe('Book token (bk_...)');\n\nfunction json(body: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(body, null, 2) }] };\n}\n\nfunction failure(error: unknown) {\n return { content: [{ type: 'text' as const, text: formatErrorForMcp(error) }], isError: true };\n}\n\nexport function registerWorkspaceTools(server: McpServer, client: SlimaApiClient): void {\n server.tool(\n 'list_notes',\n \"The author's margin notes on this book — book-level and chapter-level, including foreshadowing pairs.\",\n { book_token: BOOK_TOKEN },\n READ_ONLY,\n async ({ book_token }) => {\n try {\n return json(await client.listNotes(book_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'create_note',\n `Leave a note beside the manuscript. Notes do not touch the prose or the word count.\n\nUse \\`note_type: \"chapter\"\\` with the file's token in \\`file_id\\` to anchor it to one chapter; \\`\"book\"\\` for the work as a whole. Do not use notes as your own scratchpad — they appear in the author's interface as their own thinking. See the \\`notes-and-foreshadowing\\` guide.`,\n {\n book_token: BOOK_TOKEN,\n title: z.string().describe('Short title (required)'),\n content: z.string().optional().describe('Body, up to 50,000 characters'),\n note_type: z.enum(['book', 'chapter']).optional().describe('Defaults to \"book\"'),\n file_id: z.string().optional().describe('File token — required when note_type is \"chapter\"'),\n color: z.enum(['gray', 'red', 'blue', 'green', 'yellow', 'purple']).optional(),\n },\n WRITES,\n async ({ book_token, title, content, note_type, file_id, color }) => {\n try {\n // `client_id` is deliberately not exposed: it is the Slima app's local\n // primary key, the server mints one when it is absent, and a non-UUID\n // value is rejected. Nothing an agent could pass here would be better\n // than what the server generates.\n return json(\n await client.createNote(book_token, {\n title,\n content,\n note_type: note_type ?? 'book',\n file_id,\n color,\n })\n );\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'semantic_search',\n `Find passages by MEANING rather than by the words used.\n\n\\`search_content\\` finds the word an author typed; this finds the passage they meant — \"where does the mother's resentment surface?\" returns the scene even when neither word appears in it. On a team knowledge base it is the difference between browsing and asking.\n\nCosts a small embedding charge, unlike literal search. Returns §N paragraph anchors so you can quote them back precisely.`,\n {\n book_token: BOOK_TOKEN,\n query: z.string().describe('What you are looking for, in natural language'),\n limit: z.number().optional().describe('Max results (default 10, cap 50)'),\n },\n { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ book_token, query, limit }) => {\n try {\n return json(await client.semanticSearch(book_token, query, limit));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'list_comments',\n `Editorial comment threads on this book, open and resolved.\n\nRead these before starting work on a book you have been handed — they are where an editor has already said what is wrong, and producing an independent list without reading them usually contradicts a decision someone already made.`,\n { book_token: BOOK_TOKEN },\n READ_ONLY,\n async ({ book_token }) => {\n try {\n return json(await client.listComments(book_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'create_comment',\n `Leave an editorial comment.\n\nComment when the change is a judgement call — pacing, whether a motivation lands, whether a scene earns its length. Edit the file instead when the author asked for the change. When unsure, comment: a comment costs ten seconds to dismiss, an unwanted rewrite costs the author a diff of their own prose.`,\n {\n book_token: BOOK_TOKEN,\n content: z.string().describe('The comment'),\n file_token: z.string().optional().describe('File this comment is about, if any'),\n },\n WRITES,\n async ({ book_token, content, file_token }) => {\n try {\n return json(await client.createComment(book_token, { content, file_token }));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'resolve_comment',\n `Mark a comment thread addressed.\n\nSay what you did when you resolve one. A silent resolve tells the editor nothing about whether you understood the note.`,\n {\n book_token: BOOK_TOKEN,\n comment_token: z.string().describe('Comment token (from list_comments)'),\n },\n WRITES,\n async ({ book_token, comment_token }) => {\n try {\n return json(await client.resolveComment(book_token, comment_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'reopen_comment',\n 'Reopen a comment thread that was resolved but should not have been.',\n {\n book_token: BOOK_TOKEN,\n comment_token: z.string().describe('Comment token (from list_comments)'),\n },\n WRITES,\n async ({ book_token, comment_token }) => {\n try {\n return json(await client.reopenComment(book_token, comment_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n}\n","/**\n * Per-book MCP schema Resource.\n *\n * Registered at URI template `slima://books/{book_token}/schema`. AI clients\n * can read this on demand to learn which paths are writable / read-only for\n * a specific book, without that rule set bloating every tool description.\n *\n * This mirrors the canonical rule enforced by\n * `Mcp::FileOperationService#validate_studio_write_path!` on the Rails side —\n * Rails is the ground truth, this Resource is the ambient-context mirror.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport type { Book } from '../api/types.js';\nimport {\n AI_READONLY_EXTENSIONS,\n AI_STRUCTURED_TOOLS,\n AI_STRUCTURED_VIEWERS,\n AI_WRITABLE_EXTENSIONS,\n} from '../ai-file-policy.js';\n\nconst BOOK_SCHEMA_URI_TEMPLATE = 'slima://books/{book_token}/schema';\n\nexport function registerBookSchemaResource(\n server: McpServer,\n client: SlimaApiClient\n): void {\n server.resource(\n 'slima-book-schema',\n new ResourceTemplate(BOOK_SCHEMA_URI_TEMPLATE, {\n // Intentionally no `list` callback — books may number in the hundreds, so\n // we expose only the URI template via `resources/templates/list` and let\n // clients call `resources/read` with a concrete token.\n list: undefined,\n }),\n {\n description:\n 'Per-book MCP operation schema: which paths are readable/writable, which are structured (read-only), and the structured file catalog. Read this before planning writes on an unfamiliar book.',\n mimeType: 'application/json',\n },\n async (uri, variables) => {\n const rawToken = variables['book_token'];\n const bookToken = Array.isArray(rawToken) ? rawToken[0] : rawToken;\n\n if (!bookToken) {\n throw new Error(\n `Invalid Slima book schema URI: missing book_token in ${uri.toString()}`\n );\n }\n\n const book = await client.getBook(bookToken);\n const schema = buildBookSchema(book);\n\n return {\n contents: [\n {\n uri: uri.toString(),\n mimeType: 'application/json',\n text: JSON.stringify(schema, null, 2),\n },\n ],\n };\n }\n );\n}\n\nexport function buildBookSchema(book: Book): Record<string, unknown> {\n const isScript = book.bookType === 'script';\n const isKnowledge = book.bookType === 'knowledge';\n\n return {\n book_token: book.token,\n book_title: book.title,\n book_type: book.bookType ?? 'book',\n studio_label: isScript\n ? 'Script Studio'\n : isKnowledge\n ? 'Team Knowledge Base'\n : 'Writing Studio',\n mcp_capabilities: {\n // Note: actual reads still go through validate_safe_path!\n // (blocks absolute paths, traversal, control chars) even though it's\n // not reflected in this allowlist.\n readable_paths:\n 'Any path within the book tree (path-traversal and absolute paths still rejected).',\n writable_paths: isScript ? ['.script_studio/planning/**/*'] : ['**/*'],\n writable_exceptions: isScript ? ['.script_studio/planning/.initialized'] : [],\n readonly_paths: isScript\n ? [\n '.script_studio/series.json',\n '.script_studio/seasons/**/season.json',\n '.script_studio/seasons/**/episodes/**/episode.json',\n '.script_studio/seasons/**/scenes/*.scene',\n '.script_studio/characters/**/*.character',\n '.script_studio/locations/**/*.location',\n '.script_studio/storylines/**/*.storyline',\n '.script_studio/notes/**/*.note',\n '.script_studio/planning/.initialized',\n ]\n : [],\n reserved_sentinels: isScript ? ['.script_studio/planning/.initialized'] : [],\n // Path allowlists are not the whole story on a team book: the server also\n // checks the caller's ROLE. An editor gets 403 on every write no matter\n // which path, so \"writable_paths\" above is an upper bound, not a promise.\n write_access_note: isKnowledge\n ? 'Team knowledge base: owner / admin / seated writer may write; an editor is read-only ' +\n '(every write returns 403). Files under .slima/skills/ may be enabled as team skills by a manager.'\n : 'Personal books: the owner and writer collaborators may write; editor collaborators are read-only.',\n },\n // AI file-type policy — mirrors slima_vue `fileTypeRegistry.ts` (`aiPolicy`).\n // Applies to BOTH studio types; the Script Studio path allowlist above is a\n // separate, additional restriction.\n ai_file_policy: {\n writable: [...AI_WRITABLE_EXTENSIONS],\n // Structured types are not one bucket. Collapsing them into `readonly`\n // is what made this resource tell the model `.map` could not be written\n // for weeks after it could, and never mention `.beats` at all.\n field_level_tools: AI_STRUCTURED_TOOLS,\n readonly: [...AI_READONLY_EXTENSIONS],\n readonly_note:\n 'Read-only means the AI must not write these via write_file / edit_file / create_file / ' +\n 'append_to_file / delete_file. Slima owns their structure; overwriting one corrupts it for the ' +\n 'author. Recommend changes in words and point the author at that file type\\'s editor.',\n field_level_note:\n 'These take one change at a time through the tool named above. Every generic write is refused ' +\n '(write_file / edit_file / append_to_file), because a whole-document write drops the parts of ' +\n 'the file you were never shown — a beat board loses its chapter cards, a relationship map loses ' +\n 'a character, and nothing reports it. create_file works: it writes an empty document of the ' +\n 'right shape and ignores the content you send, so you then fill it in with the tool.',\n // ⚠️ This block ships with the client and can lag a Rails deploy.\n // `get_capabilities` is derived from the contract Rails enforces; when the\n // two disagree, that one is right.\n authority: 'get_capabilities',\n viewers: AI_STRUCTURED_VIEWERS,\n },\n structured_file_types: isScript\n ? {\n 'series.json': 'Series metadata (title, genre, logline, theme, synopsis, acts)',\n 'season.json': 'Season metadata',\n 'episode.json': 'Episode metadata',\n '*.scene': 'Scene content with scene-specific format',\n '*.character': 'Character definition with scope (series / season / episode)',\n '*.location': 'Location definition with scope',\n '*.storyline': 'Storyline with tier and ID linkage',\n '*.note': 'Note with scope',\n }\n : {},\n notes: isScript\n ? 'Structured files require schema-aware edits — use the Script Studio UI. MCP write access is restricted to planning/ (free-form reference tree) to prevent schema corruption.'\n : isKnowledge\n ? 'Team knowledge base. Free-form paths, but two team rules apply: writes follow team role ' +\n '(see write_access_note), and a file marked confidential is returned WITHOUT its content — ' +\n 'read_file reports it as withheld rather than empty. That is intentional: the AI may use ' +\n 'such a skill without reading it. Never try to rewrite a withheld file.'\n : 'Writing Studio books are free-form. No schema restrictions; any path is writable.',\n policy_source: 'Rails: Mcp::FileOperationService#validate_studio_write_path!',\n };\n}\n","/**\n * Server-level MCP `instructions` — injected into the `initialize` response so\n * AI clients see it at session start, before any tool call.\n *\n * This text is paid for on EVERY session, including the ones that only read a\n * chapter. So it is a map, not a manual: it says what Slima is, names the one\n * call that answers \"what can I do here\", and stops. Everything it used to\n * spell out — Script Studio's write rules, per-extension policy, the file-type\n * table — now lives behind `get_capabilities` and `load_guide`, which are\n * loaded on demand AND update when Rails deploys.\n *\n * That second property is the one that matters: users pin versions of this\n * package (the `X-Slima-MCP-Upgrade-Hint` mechanism exists because they do), so\n * anything asserted here keeps being asserted long after it stops being true.\n * `.map` was described as AI-writable in Slima's own prompt for months after it\n * stopped being so. Text in a pinned client is that failure with no upper bound\n * on how long it lasts.\n *\n * Keep it short. If you are about to add a paragraph here, it belongs in a\n * guide.\n */\n\nexport const SERVER_INSTRUCTIONS = `Slima is an AI writing IDE for long-form fiction. You are connected to an author's account.\n\n**Start by calling \\`get_capabilities\\`.** It returns what this server supports — which file types you may write, how content types work, the tools available, and the guide index — derived from the contract the server enforces, so it is never out of date.\n\nThen \\`load_guide\\` what the task needs: \\`getting-started\\` for the shape of a book, \\`book-structure\\` before creating files, \\`version-control-basics\\` before a large rewrite, \\`team-knowledge-base\\` inside a publisher's team, \\`beta-reader\\` before spending the author's credits.\n\nThree things worth knowing before your first call:\n\n- **Every write is a commit.** There is no save step, and the author sees your commits beside their own. Write real commit messages.\n- **The author may be editing the same file.** Prefer \\`edit_file\\` over \\`update_file\\`: an edit that cannot find its anchor fails loudly, where a whole-file write silently replaces whatever they just typed.\n- **Not every file type is yours to write.** A refusal naming the file type is information, not an obstacle to route around.\n\nSlima's AI is a writing coach by default. When a request is vague or the author is thinking out loud, ask the question that unblocks them. When they ask plainly — \"write this scene\", \"rewrite this paragraph\" — do it, without lecturing.`;\n","/**\n * Logger 工具\n *\n * 重要:MCP STDIO 模式下,stdout 是協議通道,\n * 所有日誌必須輸出到 stderr\n */\n\nconst isDebug = process.env.DEBUG === 'true' || process.env.DEBUG === '1';\n\nexport const logger = {\n info: (message: string, ...args: unknown[]) => {\n console.error(`[INFO] ${message}`, ...args);\n },\n\n warn: (message: string, ...args: unknown[]) => {\n console.error(`[WARN] ${message}`, ...args);\n },\n\n error: (message: string, error?: unknown) => {\n if (error instanceof Error) {\n console.error(`[ERROR] ${message}:`, error.message);\n if (isDebug && error.stack) {\n console.error(error.stack);\n }\n } else if (error !== undefined) {\n console.error(`[ERROR] ${message}:`, error);\n } else {\n console.error(`[ERROR] ${message}`);\n }\n },\n\n debug: (message: string, ...args: unknown[]) => {\n if (isDebug) {\n console.error(`[DEBUG] ${message}`, ...args);\n }\n },\n};\n","/**\n * Token Storage Utility (CLI)\n *\n * Stores API Token in user's home directory\n * ~/.slima/credentials.json\n */\n\nimport { promises as fs } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst SLIMA_DIR = '.slima';\nconst CREDENTIALS_FILE = 'credentials.json';\n\ninterface Credentials {\n apiToken: string;\n createdAt: string;\n baseUrl?: string;\n}\n\nfunction getCredentialsPath(): string {\n return join(homedir(), SLIMA_DIR, CREDENTIALS_FILE);\n}\n\nfunction getSlimaDir(): string {\n return join(homedir(), SLIMA_DIR);\n}\n\n/**\n * Save API Token\n */\nexport async function saveToken(apiToken: string, baseUrl?: string): Promise<void> {\n const slimaDir = getSlimaDir();\n\n // Ensure directory exists\n await fs.mkdir(slimaDir, { recursive: true });\n\n const credentials: Credentials = {\n apiToken,\n createdAt: new Date().toISOString(),\n ...(baseUrl && { baseUrl }),\n };\n\n const credentialsPath = getCredentialsPath();\n await fs.writeFile(credentialsPath, JSON.stringify(credentials, null, 2), 'utf-8');\n\n // Set file permission to 600 (owner only)\n await fs.chmod(credentialsPath, 0o600);\n}\n\n/**\n * Load API Token\n */\nexport async function loadToken(): Promise<Credentials | null> {\n try {\n const credentialsPath = getCredentialsPath();\n const content = await fs.readFile(credentialsPath, 'utf-8');\n return JSON.parse(content) as Credentials;\n } catch {\n return null;\n }\n}\n\n/**\n * Delete API Token\n */\nexport async function deleteToken(): Promise<void> {\n try {\n const credentialsPath = getCredentialsPath();\n await fs.unlink(credentialsPath);\n } catch {\n // File doesn't exist, ignore\n }\n}\n\n/**\n * Check if authenticated\n */\nexport async function isAuthenticated(): Promise<boolean> {\n const credentials = await loadToken();\n return credentials !== null && !!credentials.apiToken;\n}\n\n/**\n * Get credentials file path (for display)\n */\nexport function getCredentialsFilePath(): string {\n return getCredentialsPath();\n}\n","/**\n * CLI Auth Command\n *\n * 實作類似 Claude Code / GitHub CLI 的認證流程:\n * 1. 使用者執行 `slima-mcp auth`\n * 2. 開啟瀏覽器到 Slima 認證頁面\n * 3. 使用者登入並授權\n * 4. 本地伺服器接收 callback 並儲存 token\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { URL } from 'node:url';\nimport { randomBytes } from 'node:crypto';\nimport { saveToken, loadToken, deleteToken, getCredentialsFilePath } from './token-storage.js';\n\n// 認證使用 API 伺服器(處理 OAuth 和 session)\nconst DEFAULT_API_URL = 'https://api.slima.ai';\nconst CALLBACK_PORT_START = 8765;\nconst CALLBACK_PORT_END = 8775;\n\n/**\n * 開啟瀏覽器\n */\nasync function openBrowser(url: string): Promise<void> {\n const { exec } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execAsync = promisify(exec);\n\n const platform = process.platform;\n\n try {\n if (platform === 'darwin') {\n await execAsync(`open \"${url}\"`);\n } else if (platform === 'win32') {\n await execAsync(`start \"\" \"${url}\"`);\n } else {\n // Linux / WSL\n // 嘗試多個可能的命令\n const commands = ['xdg-open', 'sensible-browser', 'wslview'];\n for (const cmd of commands) {\n try {\n await execAsync(`${cmd} \"${url}\"`);\n return;\n } catch {\n // 繼續嘗試下一個\n }\n }\n // 如果都失敗,顯示 URL 讓使用者手動開啟\n console.log(`\\nPlease open this URL in your browser:\\n${url}\\n`);\n }\n } catch {\n console.log(`\\nPlease open this URL in your browser:\\n${url}\\n`);\n }\n}\n\n/**\n * 尋找可用的 port\n */\nasync function findAvailablePort(): Promise<number> {\n for (let port = CALLBACK_PORT_START; port <= CALLBACK_PORT_END; port++) {\n const available = await isPortAvailable(port);\n if (available) {\n return port;\n }\n }\n throw new Error(`No available ports between ${CALLBACK_PORT_START} and ${CALLBACK_PORT_END}`);\n}\n\nfunction isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n server.once('error', () => resolve(false));\n server.once('listening', () => {\n server.close(() => resolve(true));\n });\n server.listen(port, '127.0.0.1');\n });\n}\n\n/**\n * 產生隨機 state(防止 CSRF)\n */\nfunction generateState(): string {\n return randomBytes(16).toString('hex');\n}\n\n/**\n * 成功頁面 HTML\n * Design follows UIUX_SPEC.md: minimalist, grayscale, Inter font\n */\nfunction getSuccessHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Welcome to Slima</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n background: #FBFBFA;\n color: #1D1D1F;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n .container {\n text-align: center;\n padding: 48px;\n max-width: 420px;\n }\n .logo {\n width: 48px;\n height: 48px;\n margin-bottom: 32px;\n opacity: 0.9;\n }\n .success-icon {\n width: 64px;\n height: 64px;\n margin-bottom: 24px;\n border-radius: 50%;\n background: #1D1D1F;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n margin-right: auto;\n }\n .success-icon svg {\n width: 32px;\n height: 32px;\n stroke: #FBFBFA;\n }\n h1 {\n font-size: 24px;\n font-weight: 600;\n letter-spacing: -0.02em;\n margin-bottom: 12px;\n color: #1D1D1F;\n }\n .subtitle {\n font-size: 15px;\n color: #6E6E73;\n line-height: 1.5;\n margin-bottom: 8px;\n }\n .hint {\n font-size: 13px;\n color: #86868B;\n margin-top: 24px;\n }\n .divider {\n width: 40px;\n height: 1px;\n background: #E5E5E5;\n margin: 32px auto;\n }\n .footer {\n font-size: 12px;\n color: #86868B;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <img src=\"https://app.slima.ai/icons/slima-black.svg\" alt=\"Slima\" class=\"logo\">\n <div class=\"success-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4.5 12.75l6 6 9-13.5\" />\n </svg>\n </div>\n <h1>Welcome to Slima</h1>\n <p class=\"subtitle\">Authentication successful. Your CLI is now connected.</p>\n <p class=\"hint\">You can close this window and return to your terminal.</p>\n <div class=\"divider\"></div>\n <p class=\"footer\">Slima MCP is ready to use</p>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * 錯誤頁面 HTML\n */\n/**\n * HTML escape to prevent XSS\n */\nfunction escapeHtml(text: string): string {\n const map: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n };\n return text.replace(/[&<>\"']/g, (char) => map[char]);\n}\n\n/**\n * 錯誤頁面 HTML\n * Design follows UIUX_SPEC.md: minimalist, grayscale, Inter font\n */\nfunction getErrorHtml(message: string): string {\n const safeMessage = escapeHtml(message);\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Slima - Authentication Error</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n background: #FBFBFA;\n color: #1D1D1F;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n .container {\n text-align: center;\n padding: 48px;\n max-width: 420px;\n }\n .logo {\n width: 48px;\n height: 48px;\n margin-bottom: 32px;\n opacity: 0.9;\n }\n .error-icon {\n width: 64px;\n height: 64px;\n margin-bottom: 24px;\n border-radius: 50%;\n background: #1D1D1F;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n margin-right: auto;\n }\n .error-icon svg {\n width: 32px;\n height: 32px;\n stroke: #FBFBFA;\n }\n h1 {\n font-size: 24px;\n font-weight: 600;\n letter-spacing: -0.02em;\n margin-bottom: 12px;\n color: #1D1D1F;\n }\n .message {\n font-size: 15px;\n color: #6E6E73;\n line-height: 1.5;\n margin-bottom: 8px;\n }\n .hint {\n font-size: 13px;\n color: #86868B;\n margin-top: 24px;\n }\n .divider {\n width: 40px;\n height: 1px;\n background: #E5E5E5;\n margin: 32px auto;\n }\n .retry-link {\n display: inline-block;\n font-size: 14px;\n font-weight: 500;\n color: #1D1D1F;\n text-decoration: none;\n padding: 10px 20px;\n border: 1px solid #E5E5E5;\n border-radius: 8px;\n transition: all 0.2s ease;\n }\n .retry-link:hover {\n background: #F5F5F5;\n border-color: #D5D5D5;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <img src=\"https://app.slima.ai/icons/slima-black.svg\" alt=\"Slima\" class=\"logo\">\n <div class=\"error-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </div>\n <h1>Something went wrong</h1>\n <p class=\"message\">${safeMessage}</p>\n <p class=\"hint\">Please close this window and try again from your terminal.</p>\n <div class=\"divider\"></div>\n <a href=\"javascript:window.close()\" class=\"retry-link\">Close Window</a>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * 執行認證流程\n * @param options.baseUrl - API base URL (default: https://api.slima.ai)\n * @param options.force - Force re-authentication even if already authenticated\n */\nexport async function runAuth(options: { baseUrl?: string; force?: boolean } = {}): Promise<void> {\n const baseUrl = options.baseUrl || process.env.SLIMA_API_URL || DEFAULT_API_URL;\n const forceAuth = options.force || false;\n\n console.log('\\n🔐 Slima MCP Authentication\\n');\n\n // 檢查是否已經認證\n const existingCredentials = await loadToken();\n if (existingCredentials && !forceAuth) {\n console.log('You are already authenticated.');\n console.log(`Token stored at: ${getCredentialsFilePath()}`);\n console.log('\\nTo re-authenticate with a different account, run: slima-mcp auth --force\\n');\n return;\n }\n\n // 如果強制認證,先刪除舊 token\n if (existingCredentials && forceAuth) {\n await deleteToken();\n console.log('Clearing existing credentials for re-authentication...\\n');\n }\n\n const state = generateState();\n const port = await findAvailablePort();\n\n console.log('Starting local server for authentication callback...');\n\n // 建立本地伺服器接收 callback\n const tokenPromise = new Promise<string>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`);\n\n if (url.pathname === '/callback') {\n const token = url.searchParams.get('token');\n const returnedState = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n\n if (error) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml(error));\n server.close();\n reject(new Error(error));\n return;\n }\n\n if (returnedState !== state) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml('Invalid state parameter. Please try again.'));\n server.close();\n reject(new Error('State mismatch'));\n return;\n }\n\n if (!token) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml('No token received. Please try again.'));\n server.close();\n reject(new Error('No token received'));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getSuccessHtml());\n server.close();\n resolve(token);\n } else {\n res.writeHead(404);\n res.end('Not found');\n }\n });\n\n server.listen(port, '127.0.0.1', () => {\n console.log(`Listening on http://127.0.0.1:${port}`);\n });\n\n // 30 秒超時\n setTimeout(() => {\n server.close();\n reject(new Error('Authentication timed out. Please try again.'));\n }, 30000);\n });\n\n // 建立認證 URL(使用 API endpoint)\n // 加入 prompt=login 強制重新登入(清除 Rails session)\n const promptParam = forceAuth ? '&prompt=login' : '';\n const authUrl = `${baseUrl}/api/v1/auth/cli?state=${state}&callback_port=${port}${promptParam}`;\n\n console.log('\\nOpening browser for authentication...');\n await openBrowser(authUrl);\n\n console.log('\\nWaiting for authentication...');\n console.log('(Press Ctrl+C to cancel)\\n');\n\n try {\n const token = await tokenPromise;\n await saveToken(token, baseUrl !== DEFAULT_API_URL ? baseUrl : undefined);\n\n console.log('\\n✅ Authentication successful!');\n console.log(`Token saved to: ${getCredentialsFilePath()}`);\n console.log('\\nYou can now use Slima MCP with any MCP-compatible AI tool.\\n');\n } catch (error) {\n console.error('\\n❌ Authentication failed:', error instanceof Error ? error.message : error);\n process.exit(1);\n }\n}\n\n/**\n * 登出(刪除 token)\n */\nexport async function runLogout(): Promise<void> {\n console.log('\\n🔓 Slima MCP Logout\\n');\n\n const credentials = await loadToken();\n if (!credentials) {\n console.log('You are not authenticated.\\n');\n return;\n }\n\n await deleteToken();\n console.log('✅ Successfully logged out.');\n console.log(`Removed credentials from: ${getCredentialsFilePath()}\\n`);\n}\n\n/**\n * 顯示目前狀態\n */\nexport async function runStatus(): Promise<void> {\n console.log('\\n📊 Slima MCP Status\\n');\n\n const credentials = await loadToken();\n if (!credentials) {\n console.log('Status: Not authenticated');\n console.log('\\nRun `slima-mcp auth` to authenticate.\\n');\n return;\n }\n\n console.log('Status: Authenticated');\n console.log(`Token: ${credentials.apiToken.substring(0, 15)}...`);\n console.log(`Authenticated at: ${credentials.createdAt}`);\n if (credentials.baseUrl) {\n console.log(`API URL: ${credentials.baseUrl}`);\n }\n console.log(`Credentials file: ${getCredentialsFilePath()}\\n`);\n}\n"],"mappings":"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","names":["json","text","z","z","z","defaultLogger","logger","z","result","z","z","z","z","z","z","z","READ_ONLY","failure","z","z","READ_ONLY","z","json","failure","text"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/index.ts","../src/core/schema-compat.ts","../src/cli/server.ts","../src/core/utils/errors.ts","../src/core/api/client.ts","../src/core/tools/books.ts","../src/core/utils/file-tree.ts","../src/core/tools/content.ts","../src/core/tools/file-read-rendering.ts","../src/core/tools/beta-reader.ts","../src/core/tools/files.ts","../src/core/ai-file-policy.ts","../src/core/tools/structured.ts","../src/core/tools/teaching.ts","../src/core/tools/teams.ts","../src/core/tools/workspace.ts","../src/core/resources/book_schema.ts","../src/core/server-instructions.ts","../src/core/utils/logger.ts","../src/cli/token-storage.ts","../src/cli/auth.ts"],"sourcesContent":["/**\n * Slima MCP Server - CLI Entry Point\n *\n * Usage:\n * slima-mcp - Start MCP server (requires SLIMA_API_TOKEN or prior auth)\n * slima-mcp auth - Authenticate with Slima (opens browser)\n * slima-mcp logout - Remove saved credentials\n * slima-mcp status - Show authentication status\n * slima-mcp --help - Show help\n */\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { withGeminiSchemaCompat } from '../core/schema-compat.js';\nimport { createSlimaServer } from './server.js';\nimport { logger } from '../core/utils/logger.js';\nimport { loadToken, getCredentialsFilePath } from './token-storage.js';\nimport { runAuth, runLogout, runStatus } from './auth.js';\n\ndeclare const __VERSION__: string;\nconst VERSION = __VERSION__;\n\nfunction showHelp(): void {\n console.log(`\nSlima MCP v${VERSION}\nMCP Server for Slima - AI Writing IDE for Novel Authors\n\nUSAGE:\n slima-mcp [command]\n\nCOMMANDS:\n (none) Start the MCP server\n auth Authenticate with Slima (opens browser)\n auth --force Re-authenticate (switch account)\n logout Remove saved credentials\n status Show authentication status\n --help Show this help message\n --version Show version\n\nAUTHENTICATION:\n 1. Run: slima-mcp auth\n 2. Complete authentication in browser\n 3. Token is automatically saved\n\n Or set SLIMA_API_TOKEN environment variable.\n\nEXAMPLES:\n slima-mcp auth # Authenticate with browser\n slima-mcp auth --force # Re-authenticate (switch account)\n slima-mcp status # Check auth status\n SLIMA_API_TOKEN=xxx slima-mcp # Use env var\n\nCONFIGURATION (for AI tools):\n Claude Desktop, Cursor, etc.:\n {\n \"mcpServers\": {\n \"slima\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"slima-mcp\"]\n }\n }\n }\n\nMORE INFO:\n https://github.com/slima-ai/slima-mcp\n`);\n}\n\nfunction showVersion(): void {\n console.log(`slima-mcp v${VERSION}`);\n}\n\nasync function startServer(): Promise<void> {\n // Use env var first, then stored token\n let apiToken = process.env.SLIMA_API_TOKEN;\n let baseUrl = process.env.SLIMA_API_URL;\n\n if (!apiToken) {\n const credentials = await loadToken();\n if (credentials) {\n apiToken = credentials.apiToken;\n baseUrl = baseUrl || credentials.baseUrl;\n }\n }\n\n if (!apiToken) {\n logger.error('Not authenticated and SLIMA_API_TOKEN not set');\n logger.error('');\n logger.error('To authenticate, run:');\n logger.error(' slima-mcp auth');\n logger.error('');\n logger.error('Or set environment variable:');\n logger.error(' SLIMA_API_TOKEN=your_token slima-mcp');\n logger.error('');\n logger.error('Get your API token from: https://app.slima.ai/settings/api-tokens');\n process.exit(1);\n }\n\n try {\n const server = createSlimaServer({\n apiToken,\n baseUrl,\n });\n\n const transport = new StdioServerTransport();\n // Same schema sanitising as the Worker — a stdio client can be driven by Gemini too\n // (Gemini CLI, ADK agents), and one behaviour across both transports is one thing to\n // reason about. See src/core/schema-compat.ts.\n await server.connect(withGeminiSchemaCompat(transport));\n\n logger.info('Slima MCP Server running on stdio');\n\n // Graceful shutdown\n process.on('SIGINT', async () => {\n logger.info('Shutting down...');\n await server.close();\n process.exit(0);\n });\n\n process.on('SIGTERM', async () => {\n logger.info('Shutting down...');\n await server.close();\n process.exit(0);\n });\n } catch (error) {\n logger.error('Failed to start server', error);\n process.exit(1);\n }\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const command = args[0];\n\n switch (command) {\n case 'auth': {\n const forceFlag = args.includes('--force') || args.includes('-f');\n await runAuth({ force: forceFlag });\n break;\n }\n\n case 'logout':\n await runLogout();\n break;\n\n case 'status':\n await runStatus();\n break;\n\n case '--help':\n case '-h':\n case 'help':\n showHelp();\n break;\n\n case '--version':\n case '-v':\n showVersion();\n break;\n\n case undefined:\n // Default: start MCP server\n await startServer();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n console.error('Run `slima-mcp --help` for usage.');\n process.exit(1);\n }\n}\n\nmain();\n","/**\n * Tool-schema compatibility with Gemini's function-calling API.\n *\n * Gemini does NOT take JSON Schema for `functionDeclarations[].parameters`. It takes a\n * strict OpenAPI-3.0-subset **proto**, and an unknown field is not ignored — it is a hard\n * HTTP 400 for the ENTIRE request:\n *\n * Invalid JSON payload received. Unknown name \"$schema\" at\n * 'tools[0].function_declarations[0].parameters': Cannot find field.\n *\n * One bad key therefore costs the client *every* tool we publish, not just the tool that\n * carried it — and the failure is invisible from our side: the MCP handshake succeeds,\n * `tools/list` returns 200 with the full catalogue, and only Gemini's own model layer\n * rejects it afterwards.\n *\n * 2026-08-25 incident: a user connected Slima to the Gemini app. OAuth completed, the\n * token was issued, every `POST /mcp` returned 200/202 with zero 4xx — and Gemini told\n * them \"no Slima tools are registered\" and answered book questions from web search.\n * `api_tokens.last_used_at` stayed NULL for all three of their tokens: not one tool call\n * ever reached Rails. The whole cause was `$schema` + `additionalProperties`, which the\n * SDK's zod-to-json-schema emits automatically on every single tool.\n *\n * Both keys are pure metadata for tool-calling purposes — the server validates arguments\n * against the zod shape either way, so dropping them from the *advertised* schema changes\n * no behaviour. Verified against all three real APIs with our actual tool catalogue:\n * Anthropic and OpenAI accept the schemas with or without them; Gemini only without.\n */\n\nimport type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * Keys Gemini's `parameters` proto rejects outright. Measured, not assumed — every key\n * here was fed to `generateContent` and came back as `Unknown name \"<key>\"` (2026-08-25,\n * gemini-3-flash-preview). Kept as data so the guard test below can stay a data check.\n */\nexport const GEMINI_REJECTED_SCHEMA_KEYS = [\n '$ref',\n '$schema',\n 'additionalProperties',\n 'const',\n 'contains',\n 'deprecated',\n 'examples',\n 'exclusiveMaximum',\n 'exclusiveMinimum',\n 'multipleOf',\n 'patternProperties',\n 'readOnly',\n 'uniqueItems',\n 'writeOnly',\n] as const;\n\n/**\n * The subset we strip automatically.\n *\n * Deliberately only these two, NOT all of GEMINI_REJECTED_SCHEMA_KEYS. These two carry no\n * meaning a tool caller can act on, so removing them is lossless. The rest do carry\n * meaning — dropping a `$ref` leaves `{}` (a schema that describes nothing), dropping a\n * `const` silently widens an argument the tool actually constrains. Quietly mangling a\n * schema is worse than the 400 it avoids, so those stay unhandled on purpose and the\n * guard test in tests/worker/tool-schema-compat.test.ts turns red the day a tool starts\n * emitting one. Fix it at the zod shape then, not here.\n */\nexport const STRIPPED_SCHEMA_KEYS = ['$schema', 'additionalProperties'] as const;\n\nconst STRIPPED = new Set<string>(STRIPPED_SCHEMA_KEYS);\n\n/** Recursively copy a JSON Schema without the keys Gemini refuses to parse. */\nexport function stripIncompatibleSchemaKeys<T>(node: T): T {\n if (Array.isArray(node)) {\n return node.map((item) => stripIncompatibleSchemaKeys(item)) as unknown as T;\n }\n if (node !== null && typeof node === 'object') {\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(node as Record<string, unknown>)) {\n if (STRIPPED.has(key)) continue;\n out[key] = stripIncompatibleSchemaKeys(value);\n }\n return out as unknown as T;\n }\n return node;\n}\n\ninterface ToolListResult {\n tools?: Array<Record<string, unknown>>;\n}\n\n/**\n * Sanitize a `tools/list` response in place of the raw one. Any other message passes\n * through untouched (identity, same object) so this stays free on the hot path.\n */\nexport function sanitizeOutgoingMessage(message: JSONRPCMessage): JSONRPCMessage {\n const result = (message as { result?: ToolListResult }).result;\n if (!result || !Array.isArray(result.tools)) return message;\n\n return {\n ...message,\n result: {\n ...result,\n tools: result.tools.map((tool) => {\n const sanitized: Record<string, unknown> = { ...tool };\n for (const field of ['inputSchema', 'outputSchema']) {\n if (sanitized[field]) sanitized[field] = stripIncompatibleSchemaKeys(sanitized[field]);\n }\n return sanitized;\n }),\n },\n } as JSONRPCMessage;\n}\n\ninterface SendCapableTransport {\n send(message: JSONRPCMessage, options?: unknown): Promise<void>;\n}\n\n/**\n * Wrap a transport so every outgoing message goes through {@link sanitizeOutgoingMessage}.\n *\n * Hooking `send` rather than the `tools/list` request handler keeps this transport-shaped:\n * it covers JSON responses, SSE frames and stdio alike, and it does not reach into the\n * SDK's private handler registry. Call it BEFORE `server.connect(transport)`.\n */\nexport function withGeminiSchemaCompat<T extends SendCapableTransport>(transport: T): T {\n const send = transport.send.bind(transport);\n transport.send = (message: JSONRPCMessage, options?: unknown) =>\n send(sanitizeOutgoingMessage(message), options);\n return transport;\n}\n","/**\n * Slima MCP Server (CLI/stdio)\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { SlimaApiClient } from '../core/api/client.js';\nimport {\n registerBookTools,\n registerTeachingTools,\n registerTeamTools,\n registerWorkspaceTools,\n registerContentTools,\n registerBetaReaderTools,\n registerFileTools,\n registerStructuredTools,\n} from '../core/tools/index.js';\nimport { registerBookSchemaResource } from '../core/resources/index.js';\nimport { SERVER_INSTRUCTIONS } from '../core/server-instructions.js';\nimport { logger } from '../core/utils/logger.js';\n\ndeclare const __VERSION__: string;\n\nexport interface ServerConfig {\n apiToken: string;\n baseUrl?: string;\n}\n\n/**\n * Create Slima MCP Server\n */\nexport function createSlimaServer(config: ServerConfig): McpServer {\n // Create API Client with static token getter (for CLI)\n const client = new SlimaApiClient({\n baseUrl: config.baseUrl || 'https://api.slima.ai',\n getToken: async () => config.apiToken,\n logger,\n });\n\n const server = new McpServer(\n {\n name: 'slima',\n version: __VERSION__,\n },\n {\n instructions: SERVER_INSTRUCTIONS,\n }\n );\n\n // Register tools\n // Teaching first: it is what an AI should reach for before anything else,\n // and tool order is the only ordering signal the protocol carries.\n registerTeachingTools(server, client);\n registerBookTools(server, client);\n registerTeamTools(server, client);\n registerWorkspaceTools(server, client);\n registerContentTools(server, client);\n registerBetaReaderTools(server, client, logger);\n registerFileTools(server, client);\n registerStructuredTools(server, client);\n\n // Register resources (per-book schema via URI template)\n registerBookSchemaResource(server, client);\n\n logger.info('Slima MCP Server initialized');\n logger.info(`API endpoint: ${config.baseUrl || 'https://api.slima.ai'}`);\n\n return server;\n}\n","/**\n * 錯誤處理工具\n */\n\nexport class SlimaApiError extends Error {\n constructor(\n public status: number,\n public code?: string,\n message?: string\n ) {\n super(message || `API Error: ${status}`);\n this.name = 'SlimaApiError';\n }\n}\n\nexport class AuthenticationError extends SlimaApiError {\n constructor(message = 'Invalid or expired API token') {\n super(401, 'UNAUTHORIZED', message);\n this.name = 'AuthenticationError';\n }\n}\n\nexport class NotFoundError extends SlimaApiError {\n constructor(message?: string) {\n super(404, 'NOT_FOUND', message || 'Resource not found');\n this.name = 'NotFoundError';\n }\n}\n\nexport class InsufficientCreditsError extends SlimaApiError {\n constructor() {\n super(402, 'INSUFFICIENT_CREDITS', 'Insufficient credits');\n this.name = 'InsufficientCreditsError';\n }\n}\n\n/**\n * 429 from Rails' rack-attack.\n *\n * `retryAfter` is the whole point of having a distinct class: without it the\n * caller is told \"you were throttled\" but not for how long, and an agent's\n * only options are to guess or to give up. Rails runs ~24 throttle rules and\n * sends Retry-After with them; dropping that header turns a recoverable pause\n * into an opaque failure.\n */\nexport class RateLimitedError extends SlimaApiError {\n constructor(\n public retryAfterSeconds?: number,\n message?: string\n ) {\n super(429, 'RATE_LIMITED', message || 'Rate limited');\n this.name = 'RateLimitedError';\n }\n}\n\n/**\n * The request exceeded its own deadline — distinct from the server refusing\n * it. Nothing here used to time out at all, so a hung server meant a tool call\n * that never returned.\n */\nexport class RequestTimeoutError extends SlimaApiError {\n constructor(public timeoutMs: number) {\n super(408, 'REQUEST_TIMEOUT', `Request timed out after ${Math.round(timeoutMs / 1000)}s`);\n this.name = 'RequestTimeoutError';\n }\n}\n\n/**\n * 格式化錯誤訊息供 MCP 回傳\n */\nexport function formatErrorForMcp(error: unknown): string {\n if (error instanceof RateLimitedError) {\n const wait = error.retryAfterSeconds;\n\n return wait\n ? `Rate limited. Wait ${wait} second${wait === 1 ? '' : 's'} and try the same call again — this is a temporary throttle, not a rejected request.`\n : 'Rate limited. Wait a few seconds and try the same call again — this is a temporary throttle, not a rejected request.';\n }\n\n if (error instanceof RequestTimeoutError) {\n return `${error.message}. The server may still be processing it, so re-running a write could duplicate work — prefer re-reading the current state first.`;\n }\n\n if (error instanceof SlimaApiError) {\n switch (error.code) {\n case 'UNAUTHORIZED':\n return 'Invalid API token. Please check your configuration.';\n case 'INSUFFICIENT_CREDITS':\n return 'Insufficient credits. Please top up your account.';\n case 'TOKEN_EXPIRED':\n return 'API token has expired. Please generate a new one.';\n case 'TOKEN_REVOKED':\n return 'API token has been revoked. Please generate a new one.';\n default:\n return `Error: ${error.message}`;\n }\n }\n\n if (error instanceof Error) {\n return error.message;\n }\n\n return 'An unexpected error occurred.';\n}\n","/**\n * Slima API Client\n *\n * Supports dependency injection for token retrieval,\n * allowing different strategies for CLI (file-based) vs Worker (KV-based).\n */\n\n// Version is injected at build time by tsup `define` (CLI + Worker) and mirrored\n// in vitest.config.ts so tests see a real version string too.\ndeclare const __VERSION__: string;\n\nimport {\n type Book,\n type Commit,\n type Branch,\n type Blob,\n type Persona,\n type ReaderTest,\n type ReaderTestProgress,\n type CreateReaderTestParams,\n type ApiResponse,\n type ApiError,\n type CommitsListResponse,\n type BlobsDownloadResponse,\n type McpFileReadResponse,\n type McpFileCreateResponse,\n type McpFileUpdateResponse,\n type McpFileEditResponse,\n type McpFileDeleteResponse,\n type McpFileAppendResponse,\n type McpSearchResponse,\n type McpFileVisualizeResponse,\n type McpStructuredResponse,\n type BooksPage,\n type PaginationMeta,\n} from './types.js';\nimport {\n SlimaApiError,\n AuthenticationError,\n NotFoundError,\n InsufficientCreditsError,\n RateLimitedError,\n RequestTimeoutError,\n} from '../utils/errors.js';\n\n/**\n * Per-request deadline.\n *\n * Sixty seconds is well clear of any single Slima endpoint — the long-running\n * one (Beta Reader) is polled in short calls rather than held open — while\n * still being short enough that a wedged server surfaces as an error the agent\n * can act on instead of a tool call that never comes back.\n */\nconst REQUEST_TIMEOUT_MS = 60_000;\n\n/**\n * Retry-After is either a delta in seconds or an HTTP date (RFC 9110). Both\n * appear in the wild, so handle both and return undefined rather than a wrong\n * number when it is absent or unparseable — a missing wait is better than a\n * confidently incorrect one.\n */\nfunction parseRetryAfter(response: Response): number | undefined {\n // Guarded the same way handleUpgradeHint is: mocked Response objects in tests\n // do not always carry a full Headers interface, and a throttle response is a\n // bad moment to crash on a missing accessor.\n const header = response.headers?.get?.('retry-after');\n if (!header) return undefined;\n\n const seconds = Number(header);\n if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds);\n\n const when = Date.parse(header);\n if (Number.isNaN(when)) return undefined;\n\n return Math.max(0, Math.ceil((when - Date.now()) / 1000));\n}\n\n// Export for logger injection (optional)\nexport interface Logger {\n debug: (message: string, ...args: unknown[]) => void;\n info: (message: string, ...args: unknown[]) => void;\n warn: (message: string, ...args: unknown[]) => void;\n error: (message: string, error?: unknown) => void;\n}\n\n// Default no-op logger\nconst defaultLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n\n/**\n * API Client Configuration (new style with token getter)\n */\nexport interface ApiClientConfig {\n baseUrl: string;\n getToken: () => Promise<string>; // Async token getter (injected)\n logger?: Logger;\n}\n\n/**\n * Legacy config format (backwards compatibility)\n * @deprecated Use ApiClientConfig with getToken instead\n */\nexport interface SlimaConfig {\n baseUrl: string;\n token: string;\n}\n\n// Type guard to check if config is legacy format\nfunction isLegacyConfig(config: ApiClientConfig | SlimaConfig): config is SlimaConfig {\n return 'token' in config && typeof (config as SlimaConfig).token === 'string';\n}\n\nexport class SlimaApiClient {\n private readonly baseUrl: string;\n private readonly getToken: () => Promise<string>;\n private readonly logger: Logger;\n\n constructor(config: ApiClientConfig | SlimaConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, ''); // Remove trailing slash\n\n // Support both legacy and new config formats\n if (isLegacyConfig(config)) {\n const staticToken = config.token;\n this.getToken = async () => staticToken;\n this.logger = defaultLogger;\n } else {\n this.getToken = config.getToken;\n this.logger = config.logger || defaultLogger;\n }\n }\n\n /**\n * Send API request\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n options: { envelope?: boolean } = {}\n ): Promise<T> {\n const url = `${this.baseUrl}${path}`;\n const token = await this.getToken();\n\n this.logger.debug(`${method} ${path}`);\n\n // Nothing here used to bound the call. If Rails stopped responding the\n // fetch would sit open for whatever the host runtime allowed, and the MCP\n // client had no deadline of its own — a hung server became a tool call\n // that never returned.\n const controller = new AbortController();\n const deadline = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n 'User-Agent': `slima-mcp-server/${__VERSION__}`,\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n } catch (error) {\n // Check the signal, not just the error name. `AbortError` is what Node and\n // browsers throw, but this same client runs in Cloudflare Workers, and a\n // runtime that names its abort differently would otherwise slip past into\n // the generic branch and lose the \"the server may still be processing it\"\n // guidance. The name check stays as the fallback for the reverse case —\n // an abort surfacing before the signal flips.\n if (controller.signal.aborted || (error instanceof Error && error.name === 'AbortError')) {\n throw new RequestTimeoutError(REQUEST_TIMEOUT_MS);\n }\n // Anything else — including a platform-level timeout the runtime imposes\n // on its own, which we did not trigger and cannot recognise — is passed\n // through unchanged rather than mislabelled as our deadline.\n throw error;\n } finally {\n clearTimeout(deadline);\n }\n\n // Rails surfaces a soft staleness signal via this header when the client\n // is older than McpVersions::MIN_RECOMMENDED. Log once per request so the\n // user / wrapping AI client notices without hard-failing the call.\n this.handleUpgradeHint(response);\n\n if (!response.ok) {\n await this.handleErrorResponse(response);\n }\n\n const json = (await response.json()) as ApiResponse<T>;\n // `envelope` keeps meta (pagination totals); the default unwraps to `data`.\n return (options.envelope ? json : json.data) as T;\n }\n\n /**\n * Like #request but keeps the whole envelope. Paginated endpoints carry the\n * real total in `meta`, and dropping it is how `list_books` came to report a\n * capped page as if it were the whole library.\n *\n * Delegates to #request rather than re-implementing fetch/auth/error handling:\n * the first version copied those ~20 lines and had already lost request()'s\n * debug log, so list_books was the one call invisible at debug level.\n */\n private async requestWithMeta<T>(\n method: string,\n path: string\n ): Promise<{ data: T; meta?: PaginationMeta }> {\n return this.request<{ data: T; meta?: PaginationMeta }>(method, path, undefined, {\n envelope: true,\n });\n }\n\n /**\n * Surface the X-Slima-MCP-Upgrade-Hint header (if present) as a warn log.\n * Deduped per (hint, baseUrl) to keep stderr quiet during long sessions.\n * Defensive: mocked Response objects in tests may not carry a full Headers\n * interface, so guard the access rather than hard-crashing the request.\n */\n private readonly loggedUpgradeHints = new Set<string>();\n private handleUpgradeHint(response: Response): void {\n const hint = response?.headers?.get?.('X-Slima-MCP-Upgrade-Hint');\n if (!hint) return;\n\n const key = `${hint}::${this.baseUrl}`;\n if (this.loggedUpgradeHints.has(key)) return;\n this.loggedUpgradeHints.add(key);\n\n this.logger.warn(\n `[slima-mcp] ${hint} — upgrade options: npm install -g slima-mcp@latest, ` +\n 'or change your Claude Desktop config to \"npx -y slima-mcp@0\".'\n );\n }\n\n /**\n * Handle error response\n */\n private async handleErrorResponse(response: Response): Promise<never> {\n let errorData: ApiError | undefined;\n\n try {\n errorData = (await response.json()) as ApiError;\n } catch {\n // Cannot parse JSON\n }\n\n const code = errorData?.error?.code;\n const message = errorData?.error?.message;\n\n switch (response.status) {\n case 401:\n throw new AuthenticationError(message);\n case 404:\n throw new NotFoundError(message);\n case 402:\n throw new InsufficientCreditsError();\n case 429:\n // Rails' rack-attack sends Retry-After alongside the 429. Carrying it\n // through is what separates \"wait N seconds, then repeat the call\"\n // from a generic failure the agent has to guess its way out of.\n throw new RateLimitedError(parseRetryAfter(response), message);\n default:\n throw new SlimaApiError(response.status, code, message);\n }\n }\n\n // === Book Operations ===\n\n /**\n * List books (one page). `meta` carries the true total — callers must report\n * it rather than presenting a page as the whole library.\n */\n async listBooks(options: { page?: number } = {}): Promise<BooksPage> {\n const page = options.page && options.page > 1 ? `&page=${options.page}` : '';\n const response = await this.requestWithMeta<Book[]>(\n 'GET',\n `/api/v1/books?status=active${page}`\n );\n return { books: response.data ?? [], meta: response.meta ?? {} };\n }\n\n /**\n * Get book details\n */\n // === Teaching surface =====================================================\n //\n // These are why the client stays thin. Everything they return is authored and\n // served by Rails, so the guidance an AI reads updates when the server\n // deploys — including for the users who pinned an old version of this package,\n // who are exactly the ones stale guidance would mislead.\n\n async getCapabilities(): Promise<unknown> {\n return this.request('GET', '/api/v1/mcp/capabilities');\n }\n\n async listGuides(): Promise<{ guides: Array<{ slug: string; title: string; summary: string }> }> {\n return this.request('GET', '/api/v1/mcp/guides');\n }\n\n async loadGuide(slug: string): Promise<{ slug: string; title: string; body: string }> {\n return this.request('GET', `/api/v1/mcp/guides/${encodeURIComponent(slug)}`);\n }\n\n async listWritingSkills(): Promise<{ skills: Array<{ key: string; name: string; description: string }> }> {\n return this.request('GET', '/api/v1/mcp/skills');\n }\n\n async loadWritingSkill(key: string): Promise<{ key: string; name: string; body: string }> {\n return this.request('GET', `/api/v1/mcp/skills/${encodeURIComponent(key)}`);\n }\n\n // === Teams ================================================================\n\n async listTeams(): Promise<unknown[]> {\n return this.request('GET', '/api/v1/teams');\n }\n\n async getTeam(token: string): Promise<unknown> {\n return this.request('GET', `/api/v1/teams/${token}`);\n }\n\n async listTeamSkills(token: string): Promise<unknown[]> {\n return this.request('GET', `/api/v1/teams/${token}/skills`);\n }\n\n async listTeamMemories(token: string): Promise<unknown> {\n return this.request('GET', `/api/v1/teams/${token}/memories`);\n }\n\n async listTeamRules(token: string): Promise<unknown> {\n return this.request('GET', `/api/v1/teams/${token}/rules`);\n }\n\n async semanticSearch(bookToken: string, query: string, limit?: number): Promise<unknown> {\n return this.request('POST', `/api/v1/books/${bookToken}/semantic-search`, { query, limit });\n }\n\n // === Notes and comments ===================================================\n\n async listNotes(bookToken: string): Promise<unknown> {\n return this.request('GET', `/api/v1/books/${bookToken}/notes`);\n }\n\n async createNote(bookToken: string, note: Record<string, unknown>): Promise<unknown> {\n return this.request('POST', `/api/v1/books/${bookToken}/notes`, { note });\n }\n\n async listComments(bookToken: string): Promise<unknown> {\n return this.request('GET', `/api/v1/books/${bookToken}/comments`);\n }\n\n async createComment(bookToken: string, comment: Record<string, unknown>): Promise<unknown> {\n return this.request('POST', `/api/v1/books/${bookToken}/comments`, { comment });\n }\n\n async resolveComment(bookToken: string, commentToken: string): Promise<unknown> {\n return this.request('POST', `/api/v1/books/${bookToken}/comments/${commentToken}/resolve`);\n }\n\n async reopenComment(bookToken: string, commentToken: string): Promise<unknown> {\n return this.request('POST', `/api/v1/books/${bookToken}/comments/${commentToken}/reopen`);\n }\n\n async getBook(token: string): Promise<Book> {\n return this.request<Book>('GET', `/api/v1/books/${token}`);\n }\n\n /**\n * Create a new book\n */\n async createBook(params: {\n title: string;\n authorName?: string;\n description?: string;\n }): Promise<Book> {\n return this.request<Book>('POST', '/api/v1/books', {\n book: {\n title: params.title,\n author_name: params.authorName,\n description: params.description,\n },\n });\n }\n\n // === Version Control ===\n\n /**\n * List commits\n */\n async listCommits(bookToken: string, limit = 10): Promise<Commit[]> {\n const response = await this.request<CommitsListResponse>(\n 'GET',\n `/api/v1/books/${bookToken}/commits?limit=${limit}`\n );\n return response.commits;\n }\n\n /**\n * Get commit details\n */\n async getCommit(bookToken: string, commitToken: string): Promise<Commit> {\n return this.request<Commit>(\n 'GET',\n `/api/v1/books/${bookToken}/commits/${commitToken}`\n );\n }\n\n /**\n * List branches\n */\n async listBranches(bookToken: string): Promise<Branch[]> {\n return this.request<Branch[]>('GET', `/api/v1/books/${bookToken}/branches`);\n }\n\n /**\n * Download blobs\n */\n async downloadBlobs(bookToken: string, hashes: string[]): Promise<Blob[]> {\n const response = await this.request<BlobsDownloadResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/blobs/download`,\n { hashes }\n );\n return response.blobs;\n }\n\n // === Persona ===\n\n /**\n * List personas\n */\n async listPersonas(genre?: string): Promise<Persona[]> {\n const params = genre ? `?genre=${encodeURIComponent(genre)}` : '';\n return this.request<Persona[]>('GET', `/api/v1/personas${params}`);\n }\n\n /**\n * Get persona details\n */\n async getPersona(token: string): Promise<Persona> {\n return this.request<Persona>('GET', `/api/v1/personas/${token}`);\n }\n\n // === Reader Test ===\n\n /**\n * Create reader test\n */\n async createReaderTest(\n bookToken: string,\n params: CreateReaderTestParams\n ): Promise<ReaderTest> {\n return this.request<ReaderTest>(\n 'POST',\n `/api/v1/books/${bookToken}/reader_tests`,\n {\n reader_test: {\n report_type: params.reportType || 'chapter',\n persona_tokens: params.personaTokens,\n commit_token: params.commitToken,\n model: params.model,\n scope_config: params.scopeConfig,\n },\n content: params.content,\n }\n );\n }\n\n /**\n * Get reader test progress\n */\n async getReaderTestProgress(\n bookToken: string,\n testToken: string\n ): Promise<ReaderTestProgress> {\n return this.request<ReaderTestProgress>(\n 'GET',\n `/api/v1/books/${bookToken}/reader_tests/${testToken}/progress`\n );\n }\n\n /**\n * Get reader test result\n */\n async getReaderTest(bookToken: string, testToken: string): Promise<ReaderTest> {\n return this.request<ReaderTest>(\n 'GET',\n `/api/v1/books/${bookToken}/reader_tests/${testToken}`\n );\n }\n\n // === MCP File Operations ===\n\n /**\n * Read file content\n */\n async readFile(bookToken: string, path: string): Promise<McpFileReadResponse> {\n return this.request<McpFileReadResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/read`,\n { path }\n );\n }\n\n /**\n * Create file\n */\n async createFile(\n bookToken: string,\n params: {\n path: string;\n content?: string;\n parentPath?: string;\n commitMessage?: string;\n /** manuscript | reference | character | location | storyline */\n contentType?: string;\n }\n ): Promise<McpFileCreateResponse> {\n return this.request<McpFileCreateResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/create`,\n {\n path: params.path,\n content: params.content || '',\n parent_path: params.parentPath,\n commit_message: params.commitMessage,\n content_type: params.contentType,\n }\n );\n }\n\n /**\n * Update (overwrite) file content\n */\n async updateFile(\n bookToken: string,\n params: { path: string; content: string; commitMessage?: string }\n ): Promise<McpFileUpdateResponse> {\n return this.request<McpFileUpdateResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/update`,\n {\n path: params.path,\n content: params.content,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * Field-level operations on a structured file — the ONLY write path for\n * `.beats` / `.timeline` / `.script`.\n *\n * One endpoint takes every type; the type comes from the path's extension.\n * The separate tool names on this side exist because a tool that writes must\n * start with a verb, or the desktop agent will not know to ask the human.\n */\n async applyStructuredOps(\n bookToken: string,\n params: { path: string; intent: string; operations: Array<Record<string, unknown>> }\n ): Promise<McpStructuredResponse> {\n return this.request<McpStructuredResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/structured`,\n { path: params.path, intent: params.intent, operations: params.operations }\n );\n }\n\n /**\n * Search-and-replace inside a file, applied SERVER-side.\n *\n * The old client-side read-modify-write reported a replacement count this\n * process computed and blind-wrote the whole file back, so concurrent edits\n * silently clobbered each other while both callers saw a success\n * (iss_gkqwnovhajzl7xvz). Rails re-reads inside its branch lock.\n */\n async editFile(\n bookToken: string,\n params: {\n path: string;\n oldString: string;\n newString: string;\n replaceAll?: boolean;\n commitMessage?: string;\n }\n ): Promise<McpFileEditResponse> {\n return this.request<McpFileEditResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/edit`,\n {\n path: params.path,\n old_string: params.oldString,\n new_string: params.newString,\n replace_all: params.replaceAll,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * Delete file\n */\n async deleteFile(\n bookToken: string,\n params: { path: string; commitMessage?: string; recursive?: boolean }\n ): Promise<McpFileDeleteResponse> {\n return this.request<McpFileDeleteResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/delete`,\n {\n path: params.path,\n commit_message: params.commitMessage,\n recursive: params.recursive,\n }\n );\n }\n\n /**\n * Append content to file\n */\n async appendToFile(\n bookToken: string,\n params: { path: string; content: string; commitMessage?: string }\n ): Promise<McpFileAppendResponse> {\n return this.request<McpFileAppendResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/append`,\n {\n path: params.path,\n content: params.content,\n commit_message: params.commitMessage,\n }\n );\n }\n\n /**\n * Render a structured file (today: .map relationship graphs) as an image\n * plus a text summary.\n */\n async visualizeFile(\n bookToken: string,\n path: string\n ): Promise<McpFileVisualizeResponse> {\n return this.request<McpFileVisualizeResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/visualize`,\n { path }\n );\n }\n\n /**\n * Search file content\n */\n async searchFiles(\n bookToken: string,\n params: { query: string; fileTypes?: string[]; limit?: number }\n ): Promise<McpSearchResponse> {\n return this.request<McpSearchResponse>(\n 'POST',\n `/api/v1/books/${bookToken}/mcp/files/search`,\n {\n query: params.query,\n file_types: params.fileTypes,\n limit: params.limit,\n }\n );\n }\n}\n","/**\n * Book Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport type { Book } from '../api/types.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport { buildFileTree, formatFileTree } from '../utils/file-tree.js';\nimport type { FlatFileSnapshot } from '../utils/file-tree.js';\n\n// Shared Studio presentation helpers. Kept here because they're only useful\n// for books.ts' markdown output formatting; other tools use bookType directly.\nfunction studioLabel(book: Pick<Book, 'bookType'>): string {\n if (book.bookType === 'script') return '📝 Script Studio';\n // A team's shared knowledge base used to fall through to \"Writing Studio\",\n // which hid both that it is team-owned and that some of its files may be\n // present-but-withheld.\n if (book.bookType === 'knowledge') return '📚 Team Knowledge Base';\n return '📖 Writing Studio';\n}\n\nfunction scriptStudioWriteRulesBlock(book: Pick<Book, 'token'>): string {\n return `### 🔒 Write Restrictions (Script Studio)\n\nThis book uses **structured schemas**. Via MCP you can:\n- ✅ **Read** any file (series.json, *.character, *.scene, etc.)\n- ✅ **Write / Edit / Delete** under \\`.script_studio/planning/**/*\\` only (free-form reference tree)\n- ❌ **Cannot modify** structured files (\\`series.json\\`, \\`season.json\\`, \\`episode.json\\`, \\`*.character\\`, \\`*.location\\`, \\`*.scene\\`, \\`*.storyline\\`, \\`*.note\\`)\n- ❌ **Cannot modify** \\`.script_studio/planning/.initialized\\` (frontend bootstrap sentinel)\n\nTo add characters / scenes / storylines, use the Script Studio UI in the Slima app.\n\nFor full per-book details, read the MCP resource: \\`slima://books/${book.token}/schema\\`.`;\n}\n\n/**\n * Register book-related tools\n */\nexport function registerBookTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === create_book ===\n server.tool(\n 'create_book',\n `Create a new **Writing Studio** book in your Slima library.\n\nOnly Writing Studio (free-form novels / long-form prose) books can be created via MCP.\nTo create a **Script Studio** book (screenwriting with structured series/seasons/episodes/scenes),\nplease use the Slima app UI — MCP does not yet support Script Studio scaffolding.`,\n {\n title: z.string().describe('Book title (required)'),\n author_name: z.string().optional().describe('Author name (optional)'),\n description: z.string().optional().describe('Book description (optional)'),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ title, author_name, description }) => {\n try {\n const book = await client.createBook({\n title,\n authorName: author_name,\n description,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `✅ Book created successfully!\\n\\n- **Title**: ${book.title}\\n- **Token**: ${book.token}\\n- **Author**: ${book.authorName || 'N/A'}\\n\\nYou can now add files using create_file with book_token: ${book.token}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === list_books ===\n server.tool(\n 'list_books',\n 'List the books in your Slima library. Results are paginated; if the response says more books exist, call again with the next `page` before concluding a book is not there.',\n {\n page: z.number().optional().describe('Page number (default 1). Only needed when the response says more books exist.'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ page }) => {\n try {\n const { books, meta } = await client.listBooks({ page });\n\n if (books.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No books found in your library.',\n },\n ],\n };\n }\n\n const formatted = books\n .map((book) => {\n // The book itself — what counts toward the author's word count.\n const words = book.manuscriptWordCount?.toLocaleString() || '0';\n return `- **${book.title}** (${book.token}) — ${studioLabel(book)}\\n Author: ${book.authorName || 'N/A'} | Words: ${words}`;\n })\n .join('\\n\\n');\n\n // Never present a page as the whole library (CLAUDE.md Gotcha #12: a\n // silent truncation reads as \"that book does not exist\"). Rails caps an\n // un-paginated /books at 100 and reports the real total in meta.\n //\n // \"Are there more?\" is meta.page < meta.totalPages — NOT total >\n // books.length. The latter is also true on the LAST page (37 of 137),\n // where it sent the AI to fetch a page 3 that does not exist.\n const currentPage = meta.page ?? 1;\n const totalPages = meta.totalPages;\n const hasMore = totalPages !== undefined && currentPage < totalPages;\n const header = hasMore\n ? `Showing ${books.length} of ${meta.total} book(s) — page ${currentPage} of ${totalPages}. Call list_books again with \\`page: ${currentPage + 1}\\` for the rest.`\n : meta.total !== undefined && meta.total > books.length\n ? `Showing ${books.length} of ${meta.total} book(s) — page ${currentPage} of ${totalPages ?? '?'} (this is the last page).`\n : `Found ${books.length} book(s):`;\n\n return {\n content: [\n {\n type: 'text',\n text: `${header}\\n\\n${formatted}\\n\\n_📝 Script Studio books have write restrictions via MCP (see \\`get_book\\` or resource \\`slima://books/{token}/schema\\`). 📖 Writing Studio books are fully writable._`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_book ===\n server.tool(\n 'get_book',\n 'Get details of a specific book',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token }) => {\n try {\n const book = await client.getBook(book_token);\n\n const header = `# ${studioLabel(book)} — ${book.title}\n\n- **Token**: ${book.token}\n- **Author**: ${book.authorName || 'N/A'}\n- **Description**: ${book.description || 'N/A'}\n- **Language**: ${book.language || 'N/A'}\n- **Total Words**: ${book.wordCount?.toLocaleString() || '0'}\n- **Manuscript Words**: ${book.manuscriptWordCount?.toLocaleString() || '0'}\n- **Reference Words**: ${book.referenceWordCount?.toLocaleString() || '0'}\n- **Created**: ${book.createdAt}\n- **Updated**: ${book.updatedAt}`;\n\n const text =\n book.bookType === 'script'\n ? `${header}\\n\\n${scriptStudioWriteRulesBlock(book)}`\n : header;\n\n return {\n content: [{ type: 'text', text }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_book_structure ===\n server.tool(\n 'get_book_structure',\n 'Get the file/folder structure of a book',\n {\n book_token: z.string().describe('Book token'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token }) => {\n try {\n const [book, commits] = await Promise.all([\n client.getBook(book_token),\n client.listCommits(book_token, 1),\n ]);\n\n if (commits.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No commits found. The book may be empty.',\n },\n ],\n };\n }\n\n // API returns flat structure with parentToken, convert to nested for display\n const flatSnapshot = commits[0].filesSnapshot as unknown as FlatFileSnapshot[];\n const nestedTree = buildFileTree(flatSnapshot);\n const tree = formatFileTree(nestedTree);\n\n const footer =\n book.bookType === 'script'\n ? `\\n\\n_📝 This is a Script Studio book. Via MCP, writes are restricted to \\`.script_studio/planning/**/*\\`. For the full write-restriction spec, read resource \\`slima://books/${book.token}/schema\\`._`\n : '';\n\n return {\n content: [\n {\n type: 'text',\n text: `File structure for ${studioLabel(book)} — ${book.title} (${book_token}):\\n\\n${tree}${footer}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === get_writing_stats ===\n server.tool(\n 'get_writing_stats',\n `Get word-count totals for a book.\n\nReports the book's authoritative totals only. Do NOT compute \"progress\", \"words\nwritten today\", or a writing streak from commit history — Slima prunes intra-day\ncommits after 30 days, so any delta derived from \"the last N commits\" silently\nchanges span over time and will not match what the author sees in the app. Those\nfigures are account-level and are not exposed here.`,\n {\n book_token: z.string().describe('Book token'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token }) => {\n try {\n const book = await client.getBook(book_token);\n\n const text = `# Writing Stats: ${book.title}\n\n- **Total Words**: ${book.wordCount?.toLocaleString() || '0'}\n- **Manuscript Words**: ${book.manuscriptWordCount?.toLocaleString() || '0'} (the book itself — what counts toward the author's word count)\n- **Reference Words**: ${book.referenceWordCount?.toLocaleString() || '0'} (notes, worldbuilding — never counted)\n- **Language**: ${book.language || 'N/A'}\n- **Last updated**: ${book.updatedAt}\n\n_Daily word count, writing streak and rest protection are account-level in Slima and are not available through this tool. Do not estimate them from commit history — the figure would not match the author's._`;\n\n return {\n content: [{ type: 'text', text }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n","/**\n * File Tree Utilities\n *\n * Converts flat parentToken-based file structure (from API)\n * to nested children-based structure (for display).\n */\n\n/**\n * File snapshot with nested children (for display)\n */\nexport interface FileSnapshot {\n token: string;\n name: string;\n kind: 'file' | 'folder' | string;\n position: number;\n blobHash?: string;\n wordCount?: number;\n isManuscript?: boolean;\n children?: FileSnapshot[];\n}\n\n/**\n * Flat file snapshot with parentToken (from API)\n * Supports both camelCase and snake_case (API may return either)\n */\nexport interface FlatFileSnapshot {\n token: string;\n name: string;\n kind: 'file' | 'folder' | string;\n position: number;\n // camelCase\n blobHash?: string;\n wordCount?: number;\n isManuscript?: boolean;\n parentToken?: string;\n // snake_case (Rails API)\n blob_hash?: string;\n word_count?: number;\n is_manuscript?: boolean;\n parent_token?: string;\n}\n\n/**\n * Get parent token from file (supports both camelCase and snake_case)\n */\nfunction getParentToken(file: FlatFileSnapshot): string | undefined {\n return file.parentToken || file.parent_token;\n}\n\n/**\n * Get word count from file (supports both camelCase and snake_case)\n */\nfunction getWordCount(file: FlatFileSnapshot): number | undefined {\n return file.wordCount ?? file.word_count;\n}\n\n/**\n * Get isManuscript from file (supports both camelCase and snake_case)\n */\nfunction getIsManuscript(file: FlatFileSnapshot): boolean | undefined {\n return file.isManuscript ?? file.is_manuscript;\n}\n\n/**\n * Get blobHash from file (supports both camelCase and snake_case)\n */\nfunction getBlobHash(file: FlatFileSnapshot): string | undefined {\n return file.blobHash || file.blob_hash;\n}\n\n/**\n * Convert flat parentToken structure to nested children structure\n *\n * @param flatFiles - Array of files with parentToken references\n * @returns Array of root-level files with nested children\n */\nexport function buildFileTree(flatFiles: FlatFileSnapshot[]): FileSnapshot[] {\n if (flatFiles.length === 0) {\n return [];\n }\n\n // Create a map for quick lookup\n const fileMap = new Map<string, FileSnapshot>();\n\n // First pass: create all FileSnapshot objects (without children)\n for (const file of flatFiles) {\n fileMap.set(file.token, {\n token: file.token,\n name: file.name,\n kind: file.kind,\n position: file.position,\n blobHash: getBlobHash(file),\n wordCount: getWordCount(file),\n isManuscript: getIsManuscript(file),\n children: [],\n });\n }\n\n // Second pass: build parent-child relationships\n const rootFiles: FileSnapshot[] = [];\n\n for (const file of flatFiles) {\n const node = fileMap.get(file.token)!;\n const parentToken = getParentToken(file);\n\n if (parentToken) {\n // Has parent - add to parent's children\n const parent = fileMap.get(parentToken);\n if (parent) {\n parent.children = parent.children || [];\n parent.children.push(node);\n } else {\n // Parent not found - treat as root\n rootFiles.push(node);\n }\n } else {\n // No parent - this is a root node\n rootFiles.push(node);\n }\n }\n\n // Sort children by position recursively\n sortChildrenByPosition(rootFiles);\n\n // Clean up empty children arrays\n cleanEmptyChildren(rootFiles);\n\n return rootFiles;\n}\n\n/**\n * Recursively sort children by position\n */\nfunction sortChildrenByPosition(files: FileSnapshot[]): void {\n files.sort((a, b) => a.position - b.position);\n\n for (const file of files) {\n if (file.children && file.children.length > 0) {\n sortChildrenByPosition(file.children);\n }\n }\n}\n\n/**\n * Remove empty children arrays for cleaner output\n */\nfunction cleanEmptyChildren(files: FileSnapshot[]): void {\n for (const file of files) {\n if (file.children && file.children.length === 0) {\n delete file.children;\n } else if (file.children) {\n cleanEmptyChildren(file.children);\n }\n }\n}\n\n/**\n * Format nested file tree as display string\n *\n * @param files - Nested file structure (with children)\n * @param indent - Current indentation level\n * @returns Formatted tree string\n */\nexport function formatFileTree(files: FileSnapshot[], indent = 0): string {\n return files\n .sort((a, b) => a.position - b.position)\n .map((f) => {\n const prefix = ' '.repeat(indent);\n const icon = f.kind === 'folder' ? '📁' : '📄';\n const words = f.kind !== 'folder' && f.wordCount ? ` (${f.wordCount} words)` : '';\n const manuscript = f.isManuscript ? ' [M]' : '';\n let line = `${prefix}${icon} ${f.name}${words}${manuscript}`;\n\n if (f.children && f.children.length > 0) {\n line += '\\n' + formatFileTree(f.children, indent + 1);\n }\n\n return line;\n })\n .join('\\n');\n}\n","/**\n * Content Access Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp, NotFoundError } from '../utils/errors.js';\nimport { renderFileRead } from './file-read-rendering.js';\nimport type { FileSnapshot } from '../api/types.js';\n\n/**\n * Register content access tools\n */\nexport function registerContentTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === get_chapter ===\n server.tool(\n 'get_chapter',\n 'Read a chapter or file from a book. Accepts a full path, or just a filename if you are not sure of the folder. If you already know the exact path, `read_file` is equivalent and more direct.',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n file_path: z.string().describe('File path (e.g., /chapters/01.md) or just a filename (e.g., 01.md)'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token, file_path }) => {\n const normalized = file_path.replace(/^\\//, '');\n\n try {\n // Straight through Rails' audited read. This tool used to reimplement\n // reading — list commits, scan the snapshot, download the blob by hash —\n // which skipped the team-skill audit trail and daily read budget\n // entirely, never saw contentWithheld/throttled, and needed two\n // endpoints a narrowly-scoped mcp:books token cannot reach.\n const result = await client.readFile(book_token, normalized);\n return { content: [{ type: 'text', text: renderFileRead(result) }] };\n } catch (error) {\n if (!(error instanceof NotFoundError)) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n // Fall through: the path may have been a bare filename.\n }\n\n try {\n // One listCommits for both the resolution AND the not-found hint: the\n // hint used to re-fetch the identical snapshot, doubling the cost of the\n // exact case (a miss) that is already the slow path.\n const { resolvedPath, availablePaths } = await resolveFromStructure(client, book_token, normalized);\n\n if (!resolvedPath) {\n const available = availablePaths;\n const hint = available.length\n ? `\\n\\nAvailable files:\\n- ${available.slice(0, 10).join('\\n- ')}${available.length > 10 ? `\\n... and ${available.length - 10} more` : ''}`\n : '';\n return {\n content: [\n {\n type: 'text',\n text: `File not found: ${file_path}${hint}\\n\\nTip: use get_book_structure to see the full file tree.`,\n },\n ],\n isError: true,\n };\n }\n\n // Read the RESOLVED path through the same audited endpoint — resolution\n // decides *which* file, it never becomes a second way to read one.\n const result = await client.readFile(book_token, resolvedPath);\n return { content: [{ type: 'text', text: renderFileRead(result) }] };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n/** Full path of a snapshot entry, walking parentToken up to the root. */\nfunction fullPathOf(entry: FileSnapshot, byToken: Map<string, FileSnapshot>): string {\n const parts = [entry.name];\n let current: FileSnapshot | undefined = entry;\n let depth = 0;\n\n while (current) {\n const parentToken = (current as unknown as { parentToken?: string }).parentToken;\n if (!parentToken) break;\n const parent: FileSnapshot | undefined = byToken.get(parentToken);\n if (!parent || depth++ > byToken.size) break;\n parts.unshift(parent.name);\n current = parent;\n }\n\n return parts.join('/');\n}\n\nfunction flatten(files: FileSnapshot[]): FileSnapshot[] {\n // Snapshots are flat (parentToken) in current data, but older ones nested\n // children — handle both rather than silently seeing half the tree.\n return files.flatMap((f) => [f, ...(f.children ? flatten(f.children) : [])]);\n}\n\n/**\n * Resolve a loose reference (bare filename, partial path, case-insensitive) to\n * one real path, AND return every available path for the not-found hint — both\n * from a single snapshot fetch.\n */\nasync function resolveFromStructure(\n client: SlimaApiClient,\n bookToken: string,\n needle: string\n): Promise<{ resolvedPath: string | null; availablePaths: string[] }> {\n const commits = await client.listCommits(bookToken, 1);\n if (commits.length === 0) return { resolvedPath: null, availablePaths: [] };\n\n const entries = flatten((commits[0].filesSnapshot ?? []) as FileSnapshot[]);\n const byToken = new Map(entries.map((e) => [e.token, e]));\n const wanted = needle.toLowerCase();\n\n const stripExt = (value: string) => value.replace(/\\.[^./]+$/, '');\n const candidates = entries.filter((e) => e.kind !== 'folder');\n\n // Same set of loose forms the previous implementation accepted (exact path,\n // bare name, file token, name without extension) — narrowing it here would be\n // a silent capability regression for anyone whose prompts rely on it.\n const match =\n candidates.find((e) => fullPathOf(e, byToken).toLowerCase() === wanted) ??\n candidates.find((e) => e.name.toLowerCase() === wanted) ??\n candidates.find((e) => e.token.toLowerCase() === wanted) ??\n candidates.find((e) => fullPathOf(e, byToken).toLowerCase().endsWith(`/${wanted}`)) ??\n candidates.find((e) => stripExt(e.name.toLowerCase()) === stripExt(wanted));\n\n return {\n resolvedPath: match ? fullPathOf(match, byToken) : null,\n availablePaths: candidates.map((e) => fullPathOf(e, byToken)),\n };\n}\n\n\n\n","/**\n * How a file read is presented to the model — in ONE place.\n *\n * `read_file` and `get_chapter` both surface the same Rails response\n * (`/mcp/files/read`), and they used to render it independently: read_file said\n * \"(empty file)\" for a withheld confidential skill while get_chapter said \"could\n * not retrieve file content\". Neither was true, and the drift meant fixing one\n * left the other lying.\n *\n * The three outcomes are distinct on purpose. A withheld file is not an error\n * and not empty — the AI may still USE that skill, it just may not read the\n * source, and telling it anything else sends it off to \"repair\" a file it is\n * forbidden to write.\n */\nimport type { McpFileReadResponse } from '../api/types.js';\n\nexport function renderFileRead(result: McpFileReadResponse): string {\n if (result.contentWithheld) {\n return [\n `# ${result.file.name} 🔒`,\n '',\n `**Path:** ${result.file.path}`,\n `**Words:** ${result.file.wordCount}`,\n '',\n '---',\n '',\n `The content of this file is withheld from you (reason: ${result.withheldReason ?? 'confidential'}).`,\n 'It is a confidential team skill: your team allows it to be USED but not READ,',\n 'so Slima applies it on the server without showing you the source.',\n '',\n 'This is intentional, not an error and not an empty file. Do not try to rewrite,',\n 'restore or \"fix\" it — writes to this path are rejected. If you need its contents,',\n 'ask a team manager.',\n ].join('\\n');\n }\n\n if (result.throttled) {\n return [\n `# ${result.file.name} ⏳`,\n '',\n `**Path:** ${result.file.path}`,\n `**Words:** ${result.file.wordCount}`,\n '',\n '---',\n '',\n `This team skill's read budget is exhausted for now (${result.throttledReason ?? 'daily_read_limit'}).`,\n 'The file is not empty — try again later, or ask a team manager.',\n ].join('\\n');\n }\n\n return [\n `# ${result.file.name}`,\n '',\n `**Path:** ${result.file.path}`,\n `**Words:** ${result.file.wordCount}`,\n `**Type:** ${result.file.fileType || 'unknown'}`,\n '',\n '---',\n '',\n result.content || '(empty file)',\n ].join('\\n');\n}\n","/**\n * Beta Reader Tools\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient, Logger } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport type { FileSnapshot, ReaderTest, IndividualFeedback, AggregatedMetrics } from '../api/types.js';\n\n// Default no-op logger\nconst defaultLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n\n/**\n * Register Beta Reader tools\n */\nexport function registerBetaReaderTools(\n server: McpServer,\n client: SlimaApiClient,\n logger: Logger = defaultLogger\n): void {\n // === list_personas ===\n server.tool(\n 'list_personas',\n 'List available virtual reader personas for beta reading feedback',\n {\n genre: z\n .string()\n .optional()\n .describe('Filter by genre (e.g., fantasy, romance, scifi)'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ genre }) => {\n try {\n const personas = await client.listPersonas(genre);\n\n if (personas.length === 0) {\n const genreText = genre ? ` for genre \"${genre}\"` : '';\n return {\n content: [\n {\n type: 'text',\n text: `No personas found${genreText}.`,\n },\n ],\n };\n }\n\n const formatted = personas\n .map((p) => {\n const name = p.displayLabels?.zh || p.displayLabels?.en || p.slug;\n const tags = p.genreTags?.join(', ') || 'General';\n const demo = p.demographics || {};\n const gender = demo.gender || 'N/A';\n const age = demo.ageRange || 'N/A';\n\n return `- **${name}** (\\`${p.token}\\`)\n Gender: ${gender} | Age: ${age}\n Genres: ${tags}`;\n })\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: 'text',\n text: `Available Personas (${personas.length}):\\n\\n${formatted}\\n\\n**Tip**: Use the persona token with \\`analyze_chapter\\` to get feedback from a specific reader.`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === analyze_chapter ===\n server.tool(\n 'analyze_chapter',\n `Get AI Beta Reader feedback on a chapter from a specific reader persona. This may take a moment to complete.\n\n**Not supported for Script Studio books** (\\`book_type: \"script\"\\`) — AI Beta Reader is a Writing Studio feature and does not yet parse \\`.scene\\` or \\`series.json\\` structures. Use the Script Studio UI's built-in analysis for screenplays.`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123) — must be a Writing Studio book (book_type: \"book\").'),\n file_path: z.string().describe('Chapter file path (e.g., /chapters/01.md)'),\n persona_token: z\n .string()\n .describe('Persona token (e.g., psn_xxx). Use list_personas to find available personas.'),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ book_token, file_path, persona_token }) => {\n try {\n // Gate: reject Script Studio books — AI Beta Reader does not parse\n // scene/character schema yet. Fetch before any other work so we don't\n // waste a commits/list call.\n const book = await client.getBook(book_token);\n if (book.bookType === 'script') {\n return {\n content: [\n {\n type: 'text',\n text: `❌ analyze_chapter is not available for Script Studio books.\\n\\nBook \"${book.title}\" (${book.token}) is a Script Studio book. Please use the Script Studio UI's built-in analysis features (structural notes, character consistency, scene-level feedback) — AI Beta Reader is a Writing Studio feature.`,\n },\n ],\n isError: true,\n };\n }\n\n // 1. Get latest commit and chapter content\n const commits = await client.listCommits(book_token, 1);\n\n if (commits.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'No commits found. The book may be empty.',\n },\n ],\n isError: true,\n };\n }\n\n // 2. Find file\n const file = findFileInSnapshot(commits[0].filesSnapshot, file_path);\n\n if (!file) {\n return {\n content: [\n {\n type: 'text',\n text: `File not found: ${file_path}. Use get_book_structure to see available files.`,\n },\n ],\n isError: true,\n };\n }\n\n if (!file.blobHash) {\n return {\n content: [\n {\n type: 'text',\n text: `File \"${file_path}\" has no content (empty file).`,\n },\n ],\n isError: true,\n };\n }\n\n // 3. Download content\n const blobs = await client.downloadBlobs(book_token, [file.blobHash]);\n\n if (blobs.length === 0) {\n return {\n content: [\n {\n type: 'text',\n text: 'Could not retrieve file content.',\n },\n ],\n isError: true,\n };\n }\n\n const content = blobs[0].content;\n\n // 4. Create Reader Test\n logger.info(`Creating reader test for ${file_path} with persona ${persona_token}`);\n\n const test = await client.createReaderTest(book_token, {\n reportType: 'chapter',\n personaTokens: [persona_token],\n commitToken: commits[0].token,\n scopeConfig: { chapters: [file_path] },\n content: content,\n });\n\n // 5. Poll for completion (max 90 seconds)\n const result = await pollForCompletion(\n () => client.getReaderTestProgress(book_token, test.token),\n 90000,\n logger\n );\n\n if (result.status === 'failed') {\n return {\n content: [\n {\n type: 'text',\n text: 'Analysis failed. Please try again later.',\n },\n ],\n isError: true,\n };\n }\n\n if (result.status !== 'completed') {\n // The report is still running server-side — and it has already been\n // paid for. Naming get_reader_test here matters: the previous wording\n // (\"you can check the status later\") pointed at a capability that did\n // not exist, so an agent's only reachable next step was to call\n // analyze_chapter again, which starts a *second* report and charges a\n // second time.\n return {\n content: [\n {\n type: 'text',\n text: `The report is still being written (${result.progress ?? 0}% complete). It is running on the server and has already been paid for.\n\nCall \\`get_reader_test\\` with this token in a little while to collect it:\n\n book_token: ${book_token}\n test_token: ${test.token}\n\nDo NOT call analyze_chapter again for this chapter — that starts a separate report and charges for it a second time.`,\n },\n ],\n };\n }\n\n // 6. Get full result\n const fullResult = await client.getReaderTest(book_token, test.token);\n\n // 7. Format feedback\n const feedback = formatBetaReaderFeedback(fullResult, file.name);\n\n return {\n content: [{ type: 'text', text: feedback }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // Collect a report that analyze_chapter started but could not wait out.\n //\n // Without this, analyze_chapter's timeout branch had nowhere to send the\n // agent: it handed back a test token that no tool accepted. The only move\n // left was to re-run analyze_chapter, which bills a second report for work\n // already in flight. A wrong next step is expensive here, not just unhelpful.\n server.tool(\n 'get_reader_test',\n `Collect an AI Beta Reader report by its test token — for a report that analyze_chapter started but did not finish inside its wait window.\n\nUse this rather than calling analyze_chapter again: the report is already running server-side and has already been charged. Re-running starts a second report and charges again.`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n test_token: z\n .string()\n .describe('Reader test token, as handed back by analyze_chapter when it timed out'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token, test_token }) => {\n try {\n const progress = await client.getReaderTestProgress(book_token, test_token);\n\n if (progress.status === 'failed') {\n return {\n content: [\n {\n type: 'text',\n text: 'That report failed while running. Start a new one with analyze_chapter.',\n },\n ],\n isError: true,\n };\n }\n\n if (progress.status !== 'completed') {\n return {\n content: [\n {\n type: 'text',\n text: `Still being written (${progress.progress ?? 0}% complete). Call get_reader_test again with the same token shortly — do not start a new analysis, this one is still running and already paid for.`,\n },\n ],\n };\n }\n\n const fullResult = await client.getReaderTest(book_token, test_token);\n\n return {\n content: [\n { type: 'text', text: formatBetaReaderFeedback(fullResult, describeScope(fullResult)) },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n/**\n * A heading for a report collected by token rather than by file.\n *\n * analyze_chapter knows the file it was handed; get_reader_test only has the\n * token, and ReaderTest carries no filename. Falling back to the report's own\n * scope keeps the heading honest instead of inventing a name.\n */\nfunction describeScope(test: ReaderTest): string {\n const scopePath = test.scopeConfig?.filePath;\n\n return typeof scopePath === 'string' && scopePath.length > 0\n ? scopePath\n : `${test.reportType} report`;\n}\n\n/**\n * Poll for completion\n */\nasync function pollForCompletion(\n checkFn: () => Promise<{ status: string; progress?: number }>,\n timeoutMs: number,\n logger: Logger\n): Promise<{ status: string; progress?: number }> {\n const startTime = Date.now();\n const pollInterval = 3000; // 3 seconds\n // Carried out of the loop so a timeout can report how far the report actually\n // got. \"Still being written\" is far more useful with a number attached.\n let lastProgress: number | undefined;\n\n while (Date.now() - startTime < timeoutMs) {\n const result = await checkFn();\n lastProgress = result.progress;\n logger.debug(`Poll status: ${result.status}, progress: ${result.progress || 'N/A'}`);\n\n if (result.status === 'completed' || result.status === 'failed') {\n return result;\n }\n\n await sleep(pollInterval);\n }\n\n return { status: 'timeout', progress: lastProgress };\n}\n\n/**\n * Sleep\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Format Beta Reader feedback\n */\nfunction formatBetaReaderFeedback(test: ReaderTest, fileName: string): string {\n const feedbacks = test.individualFeedbacks || [];\n const metrics = test.aggregatedMetrics;\n\n if (feedbacks.length === 0 && !metrics) {\n return 'No feedback available.';\n }\n\n let output = `# Beta Reader Feedback: ${fileName}\\n\\n`;\n\n // Aggregated metrics summary\n if (metrics) {\n output += formatAggregatedMetrics(metrics);\n }\n\n // Individual persona feedback\n if (feedbacks.length > 0) {\n output += `\\n---\\n\\n## Individual Reader Feedback\\n\\n`;\n output += feedbacks.map(formatIndividualFeedback).join('\\n---\\n\\n');\n }\n\n return output;\n}\n\n/**\n * Format aggregated metrics\n */\nfunction formatAggregatedMetrics(metrics: AggregatedMetrics): string {\n let output = `## Summary Metrics\\n\\n`;\n\n // Overall\n if (metrics.overall) {\n const o = metrics.overall;\n output += `### Overall Scores\\n`;\n if (o.avgContinueReading) output += `- Continue Reading Intent: ${o.avgContinueReading.toFixed(1)}/10\\n`;\n if (o.avgRecommendation) output += `- Recommendation Score: ${o.avgRecommendation.toFixed(1)}/10\\n`;\n if (o.avgWantMore) output += `- Want More: ${o.avgWantMore.toFixed(1)}/10\\n`;\n if (o.dnfRisk) output += `- DNF Risk: ${o.dnfRisk}\\n`;\n output += '\\n';\n }\n\n // Kindle Rating\n if (metrics.kindleRating) {\n output += `### Predicted Kindle Rating\\n`;\n output += `- Average Score: ⭐ ${metrics.kindleRating.avgScore.toFixed(1)}/5\\n`;\n if (metrics.kindleRating.individualScores) {\n const scores = metrics.kindleRating.individualScores\n .map(s => `${s.personaName || 'Reader'}: ${s.score}/5 (${s.confidence})`)\n .join(', ');\n output += `- Individual Scores: ${scores}\\n`;\n }\n output += '\\n';\n }\n\n // Characters\n if (metrics.characters) {\n const c = metrics.characters;\n output += `### Character Evaluation\\n`;\n if (c.avgProtagonistLikability) output += `- Protagonist Likability: ${c.avgProtagonistLikability.toFixed(1)}/10\\n`;\n if (c.avgMotivationClarity) output += `- Motivation Clarity: ${c.avgMotivationClarity.toFixed(1)}/10\\n`;\n if (c.avgDialogueNaturalness) output += `- Dialogue Naturalness: ${c.avgDialogueNaturalness.toFixed(1)}/10\\n`;\n output += '\\n';\n }\n\n // Pacing\n if (metrics.pacing) {\n const p = metrics.pacing;\n output += `### Pacing Evaluation\\n`;\n if (p.avgOverall) output += `- Overall Pacing: ${p.avgOverall.toFixed(1)}/10\\n`;\n if (p.consensus) output += `- Consensus: ${p.consensus}\\n`;\n if (p.commonDnfTriggers && p.commonDnfTriggers.length > 0) {\n output += `- Common DNF Triggers:\\n`;\n p.commonDnfTriggers.slice(0, 3).forEach(t => {\n output += ` - ${t.trigger} (${t.count}x)\\n`;\n });\n }\n output += '\\n';\n }\n\n // Market\n if (metrics.market) {\n const m = metrics.market;\n output += `### Market Positioning\\n`;\n if (m.avgWordOfMouth) output += `- Word of Mouth Potential: ${m.avgWordOfMouth.toFixed(1)}/10\\n`;\n if (m.topComparableBooks && m.topComparableBooks.length > 0) {\n const books = m.topComparableBooks.slice(0, 3).map(b => b.title).join(', ');\n output += `- Comparable Books: ${books}\\n`;\n }\n output += '\\n';\n }\n\n return output;\n}\n\n/**\n * Format individual feedback\n */\nfunction formatIndividualFeedback(feedback: IndividualFeedback): string {\n const name = feedback.personaName || feedback.personaToken;\n let output = `### ${name}\\n\\n`;\n\n // Overall scores\n if (feedback.overall) {\n const o = feedback.overall;\n if (o.continueReading) output += `**Continue Reading Intent**: ${o.continueReading}/10\\n`;\n if (o.recommendation) output += `**Recommendation**: ${o.recommendation}/10\\n`;\n if (o.dnfRisk) output += `**DNF Risk**: ${o.dnfRisk}\\n`;\n output += '\\n';\n }\n\n // Kindle Rating\n if (feedback.kindleRating) {\n output += `**Kindle Rating**: ⭐ ${feedback.kindleRating.score}/5`;\n if (feedback.kindleRating.rationale) {\n output += ` - ${feedback.kindleRating.rationale}`;\n }\n output += '\\n\\n';\n }\n\n // Detailed Feedback\n if (feedback.detailedFeedback) {\n const d = feedback.detailedFeedback;\n\n if (d.whatWorked) {\n output += `**What Worked**:\\n${d.whatWorked}\\n\\n`;\n }\n\n if (d.whatDidntWork) {\n output += `**What Didn't Work**:\\n${d.whatDidntWork}\\n\\n`;\n }\n\n if (d.strongestElement) {\n output += `**Strongest Element**: ${d.strongestElement}\\n`;\n }\n\n if (d.weakestElement) {\n output += `**Weakest Element**: ${d.weakestElement}\\n`;\n }\n\n if (d.wouldYouBuy) {\n output += `**Would You Buy**: ${d.wouldYouBuy}\\n`;\n }\n\n if (d.specificSuggestions && d.specificSuggestions.length > 0) {\n output += `\\n**Specific Suggestions**:\\n`;\n d.specificSuggestions.forEach((s, i) => {\n output += `${i + 1}. ${s}\\n`;\n });\n }\n\n if (d.finalThoughts) {\n output += `\\n**Final Thoughts**:\\n${d.finalThoughts}\\n`;\n }\n }\n\n // Emotional Journey\n if (feedback.emotionalJourney) {\n const e = feedback.emotionalJourney;\n if (e.emotionalArc) {\n output += `\\n**Emotional Arc**: ${e.emotionalArc}\\n`;\n }\n if (e.peakMoments && e.peakMoments.length > 0) {\n const moments = e.peakMoments.map(m =>\n typeof m === 'string' ? m : (m as { moment?: string; description?: string }).moment || (m as { moment?: string; description?: string }).description || JSON.stringify(m)\n );\n output += `**Peak Moments**: ${moments.join(', ')}\\n`;\n }\n if (e.overallMood) {\n output += `**Overall Mood**: ${e.overallMood}\\n`;\n }\n }\n\n return output;\n}\n\n/**\n * Find file in snapshot\n */\nfunction findFileInSnapshot(\n files: FileSnapshot[],\n path: string\n): FileSnapshot | null {\n const normalizedPath = path.replace(/^\\//, '').toLowerCase();\n const pathParts = normalizedPath.split('/');\n\n function search(\n items: FileSnapshot[],\n remainingParts: string[]\n ): FileSnapshot | null {\n for (const item of items) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n if (remainingParts.length === 1) {\n const target = remainingParts[0];\n if (\n itemNameLower === target ||\n itemTokenLower === target ||\n itemNameLower.replace(/\\.[^.]+$/, '') === target.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n }\n\n if (item.kind === 'folder' && item.children) {\n if (\n itemNameLower === remainingParts[0] ||\n itemTokenLower === remainingParts[0]\n ) {\n const result = search(item.children, remainingParts.slice(1));\n if (result) return result;\n }\n\n const result = search(item.children, remainingParts);\n if (result) return result;\n }\n }\n\n return null;\n }\n\n let result = search(files, pathParts);\n if (result) return result;\n\n result = searchByName(files, pathParts[pathParts.length - 1]);\n return result;\n}\n\nfunction searchByName(\n files: FileSnapshot[],\n name: string\n): FileSnapshot | null {\n const nameLower = name.toLowerCase();\n\n for (const item of files) {\n const itemNameLower = item.name.toLowerCase();\n const itemTokenLower = item.token.toLowerCase();\n\n if (\n itemNameLower === nameLower ||\n itemTokenLower === nameLower ||\n itemNameLower.replace(/\\.[^.]+$/, '') === nameLower.replace(/\\.[^.]+$/, '')\n ) {\n return item;\n }\n\n if (item.kind === 'folder' && item.children) {\n const result = searchByName(item.children, name);\n if (result) return result;\n }\n }\n\n return null;\n}\n","/**\n * MCP File Operations Tools\n *\n * Provides tools for AI to read, write, create, delete, and search files in books.\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\nimport { renderFileRead } from './file-read-rendering.js';\nimport { AI_WRITE_POLICY_NOTE } from '../ai-file-policy.js';\n\n// Studio-aware write rules shown on every write tool description + path param.\n// Rails (`Mcp::FileOperationService#validate_studio_write_path!`) enforces this\n// as the ground truth; duplicating it here lets the AI plan correctly instead\n// of bouncing off HTTP 400 INVALID_PATH.\nconst SCRIPT_STUDIO_WRITE_WARNING = `\n**IMPORTANT — Script Studio books (\\`book_type: \"script\"\\`):**\nWrites are restricted to \\`.script_studio/planning/**/*\\` only.\nStructured files (\\`series.json\\`, \\`*.character\\`, \\`*.scene\\`, \\`*.storyline\\`, \\`*.note\\`, \\`*.location\\`, \\`season.json\\`, \\`episode.json\\`) are READ-ONLY via MCP — structural edits must go through the Script Studio UI.\nThe sentinel file \\`.script_studio/planning/.initialized\\` is also read-only (frontend bootstrap marker).\nAttempting to write outside \\`planning/\\` returns HTTP 400 with code \\`INVALID_PATH\\`.\nFor per-book details, read resource \\`slima://books/{book_token}/schema\\`.\n\nFor Writing Studio books (\\`book_type: \"book\"\\`), all paths remain writable.\nAlways check \\`book_type\\` via \\`list_books\\` or \\`get_book\\` before writing to an unfamiliar book.`;\n\n// AI file-type policy, mirrored from slima_vue `src/services/fileTypeRegistry.ts`\n// (`aiPolicy`) via Rails `Conversations::SystemPromptBuilder::AI_FILE_POLICY`.\n// The frontend registry is the single source of truth; this is the third mirror,\n// which is exactly why it says as little as possible and points at the tools that\n// DO work instead of restating the whole table.\n//\n// Why it is worth repeating here at all: the in-app coach is told this in its\n// system prompt and is blocked client-side when it forgets. An external agent\n// gets neither, so without this text it will \"helpfully\" overwrite a `.map` with\n// prose and corrupt the author's relationship graph.\n// The write policy now lives in ai-file-policy.ts as one composed paragraph.\n// It used to be assembled here from AI_READONLY_EXTENSIONS alone, which is how\n// `.map` came to be described as read-only for weeks after it became writable\n// and `.beats` came to be described not at all: a list of \"the read-only ones\"\n// cannot express \"writable, but only whole-file\" or \"writable, via this tool\".\nconst AI_READONLY_WARNING = AI_WRITE_POLICY_NOTE;\n\n// Slima classifies every file as manuscript or reference (plus intrinsic planner\n// labels). ONLY manuscript counts toward the author's word count, writing streak,\n// Beta Reader chapter picker and manuscript export. A new file inherits its\n// folder and falls back to `reference` at the root, so prose written into a fresh\n// path is invisible in all of those unless it is declared.\nconst CONTENT_TYPE_HINT =\n 'How Slima classifies this file. ' +\n '\"manuscript\" = the book itself (prose/chapters) — this is what counts toward the author\\'s ' +\n 'word count, daily goal and writing streak. ' +\n '\"reference\" = notes, worldbuilding, research (never counted). ' +\n '\"character\" / \"location\" / \"storyline\" = planner cards, which are reference plus a label. ' +\n 'Omit to inherit the containing folder (root-level files default to \"reference\"). ' +\n 'If you were asked to WRITE a chapter, pass \"manuscript\" — otherwise those words will not ' +\n 'appear in the author\\'s word count.';\n\nconst PATH_PARAM_HINT =\n 'File path relative to book root. ' +\n 'For Script Studio books: MUST be under \".script_studio/planning/\" (e.g., \".script_studio/planning/references/outline.md\"). ' +\n 'For Writing Studio books: any path (e.g., \"characters/protagonist.md\").';\n\n// Structured file extensions owned by Script Studio. Read access still works;\n// search_content filters them by default (override with include_structured: true).\nconst SCRIPT_STRUCTURED_EXTENSIONS = ['character', 'scene', 'storyline', 'note', 'location'];\n\n/**\n * Register file operation tools\n */\nexport function registerFileTools(\n server: McpServer,\n client: SlimaApiClient\n): void {\n // === read_file ===\n server.tool(\n 'read_file',\n 'Read the content of any file in a book by its path. For `.map` relationship graphs, prefer `view_relationship_map` — this tool returns their raw JSON (node ids and pixel coordinates), which is hard to reason about.',\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('File path (e.g., \"characters/protagonist.md\" or \"chapter-01.md\")'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token, path }) => {\n try {\n const result = await client.readFile(book_token, path);\n\n return {\n content: [{ type: 'text', text: renderFileRead(result) }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === write_file ===\n server.tool(\n 'write_file',\n `Replace the entire content of an existing file (creates a new commit).\n${SCRIPT_STUDIO_WRITE_WARNING}\n${AI_READONLY_WARNING}`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(PATH_PARAM_HINT),\n content: z.string().describe('New content to write'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },\n async ({ book_token, path, content, commit_message }) => {\n try {\n const result = await client.updateFile(book_token, {\n path,\n content,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `File updated successfully.\\n\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === edit_file ===\n server.tool(\n 'edit_file',\n `Replace a specific text segment in a file (search and replace). Use this for precise edits instead of rewriting the whole file.\n\nThe replacement is performed by the server inside a lock, so the reported count is what was actually written — not a local guess. If the text is missing or ambiguous the call fails with a message telling you what to do next; act on it rather than retrying the same arguments.\n${SCRIPT_STUDIO_WRITE_WARNING}\n${AI_READONLY_WARNING}`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(PATH_PARAM_HINT),\n old_string: z.string().describe('The exact text to find and replace (must match exactly, including whitespace)'),\n new_string: z.string().describe('The new text to replace it with (pass an empty string to delete)'),\n replace_all: z.boolean().optional().describe('Replace all occurrences (default: false, replaces only the first match)'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },\n async ({ book_token, path, old_string, new_string, replace_all, commit_message }) => {\n try {\n // Delegates outright: no read-then-write here. A local read-modify-write\n // cannot see a concurrent write, which is how two edits used to clobber\n // each other while both reported success (iss_gkqwnovhajzl7xvz).\n const result = await client.editFile(book_token, {\n path,\n oldString: old_string,\n newString: new_string,\n replaceAll: replace_all,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `✅ File edited.\\n\\n**Replacements applied by the server:** ${result.replacements}\\n**Commit:** ${result.commit.token}\\n**Message:** ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n // Rails' edit errors are already written for an AI reader (what went\n // wrong AND what to do next), so pass them through verbatim.\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === create_file ===\n server.tool(\n 'create_file',\n `Create a new file in a book (creates a new commit).\n\n**Writing a chapter? Pass \\`content_type: \"manuscript\"\\`.** Files default to\n\"reference\" (not counted in the author's word count, streak, or manuscript export)\nunless they inherit a manuscript folder or you say otherwise.\n${SCRIPT_STUDIO_WRITE_WARNING}\n${AI_READONLY_WARNING}`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(\n `${PATH_PARAM_HINT} Parent folders are auto-created (mkdir -p behavior).`\n ),\n content: z.string().optional().describe('Initial content (optional, defaults to empty)'),\n content_type: z\n .enum(['manuscript', 'reference', 'character', 'location', 'storyline'])\n .optional()\n .describe(CONTENT_TYPE_HINT),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ book_token, path, content, content_type, commit_message }) => {\n try {\n const result = await client.createFile(book_token, {\n path,\n content: content || '',\n contentType: content_type,\n commitMessage: commit_message,\n });\n\n // Report the classification back: \"created\" alone hid the one thing the\n // author would notice — whether those words count.\n const classification = content_type\n ? `Classified as: ${content_type}${content_type === 'manuscript' ? ' (counts toward the word count)' : ' (not counted in the word count)'}`\n : 'Classified by inheriting the parent folder (root-level defaults to reference, which is NOT counted in the word count).';\n\n // The server replaces the content with an empty skeleton for structured\n // types and says so in `notes`. Dropping that here would leave the model\n // with a 201 and the belief that its content is in the file — the exact\n // silent-success shape this whole surface is built to avoid.\n const notes = (result.notes ?? []).map((n) => `\\n\\n⚠️ ${n}`).join('');\n\n return {\n content: [\n {\n type: 'text',\n text: `File created successfully.\\n\\nFile Token: ${result.fileToken}\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}\\n${classification}${notes}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === delete_file ===\n server.tool(\n 'delete_file',\n `Delete a file or an empty folder from a book (creates a new commit).\n\nA folder that still has anything inside it is refused unless you pass\n\\`recursive: true\\`. That is not bureaucracy: deleting only the folder would\nleave its contents with no parent, and the app then shows the whole subtree at\nthe book's top level — the author sees their files scattered, with no error.\nSay the word and everything inside goes, and you are told how many.\n${SCRIPT_STUDIO_WRITE_WARNING}\n${AI_READONLY_WARNING}`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(PATH_PARAM_HINT),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n recursive: z\n .boolean()\n .optional()\n .describe('Required to delete a folder that is not empty — everything inside goes with it'),\n },\n { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },\n async ({ book_token, path, commit_message, recursive }) => {\n try {\n const result = await client.deleteFile(book_token, {\n path,\n commitMessage: commit_message,\n recursive,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: [\n 'File deleted successfully.',\n // The model asked to delete one path; several may have gone. It has to\n // be able to tell the author that.\n result.deletedDescendants\n ? `${result.deletedDescendants} item(s) inside it were deleted too — tell the author.`\n : null,\n '',\n `Commit: ${result.commit.token}`,\n `Message: ${result.commit.name || result.commit.message}`,\n ]\n .filter((line) => line !== null)\n .join('\\n'),\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === append_to_file ===\n server.tool(\n 'append_to_file',\n `Append content to the end of an existing file (creates a new commit).\n${SCRIPT_STUDIO_WRITE_WARNING}\n${AI_READONLY_WARNING}`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(PATH_PARAM_HINT),\n content: z.string().describe('Content to append'),\n commit_message: z.string().optional().describe('Commit message (optional, auto-generated if not provided)'),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ book_token, path, content, commit_message }) => {\n try {\n const result = await client.appendToFile(book_token, {\n path,\n content,\n commitMessage: commit_message,\n });\n\n return {\n content: [\n {\n type: 'text',\n text: `Content appended successfully.\\n\\nCommit: ${result.commit.token}\\nMessage: ${result.commit.name || result.commit.message}`,\n },\n ],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === view_relationship_map ===\n server.tool(\n 'view_relationship_map',\n `See a \\`.map\\` character-relationship graph as a picture plus a readable summary.\n\nUse this instead of \\`read_file\\` for \\`.map\\` files: their raw JSON is node ids and pixel coordinates.\nThe summary lists every character, every relationship (\"A —childhood friends→ B\") and any faction groups,\nso it is useful even in clients that do not display images.\n\nTo change one, use \\`update_relationship_map\\` (one field at a time). A \\`.map\\` is the relationship graph (關係圖), not a geographic map — that is \\`.geomap\\`.`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe('Path to a .map file (e.g., \"world/relations.map\")'),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token, path }) => {\n try {\n const result = await client.visualizeFile(book_token, path);\n\n if (result.contentWithheld) {\n return {\n content: [\n {\n type: 'text',\n text: `🔒 \"${path}\" is a confidential team skill. Its contents are withheld from you by your team's settings (reason: ${result.withheldReason ?? 'confidential'}). This is intentional, not an error — do not try to rewrite it.`,\n },\n ],\n };\n }\n\n if (result.throttled) {\n return {\n content: [\n {\n type: 'text',\n text: `⏳ Read budget for this team skill is exhausted for today (${result.throttledReason ?? 'daily_read_limit'}). Try again later; the file is not empty.`,\n },\n ],\n };\n }\n\n // The summary goes FIRST and always: image blocks do not reach the model\n // on every MCP client (claude.ai / Claude Desktop remote connectors\n // currently drop them — anthropics/claude-ai-mcp#238), so a picture-only\n // reply would be an empty turn there.\n const content: Array<\n { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }\n > = [{ type: 'text', text: result.summary ?? '(no summary available)' }];\n\n if (result.format === 'png' && result.image) {\n content.push({ type: 'image', data: result.image, mimeType: 'image/png' });\n } else if (result.imageUnavailable) {\n content.push({\n type: 'text',\n text: `_(The image could not be rendered on the server, so only the summary above is available.)_`,\n });\n }\n\n return { content };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n\n // === search_content ===\n server.tool(\n 'search_content',\n `Search for content across all files in a book.\n\n**Script Studio behavior:** By default, structured files (\\`*.character\\`, \\`*.scene\\`, \\`*.storyline\\`, \\`*.note\\`, \\`*.location\\`, and \\`*.json\\` under \\`.script_studio/\\`) are **excluded** from search results — their raw JSON is noisy and not useful for full-text search.\nPass \\`include_structured: true\\` to search those files too.\nWriting Studio books are unaffected by this filter.`,\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n query: z.string().describe('Search query (e.g., \"blue eyes\" or \"protagonist name\")'),\n file_types: z\n .array(z.string())\n .optional()\n .describe('Filter by file extension, with or without the dot: [\"md\", \".txt\", \"beats\"]'),\n limit: z.number().optional().describe('Maximum number of matches to return (default: 20)'),\n include_structured: z\n .boolean()\n .optional()\n .describe(\n 'Script Studio only: set to true to include structured files (*.character / *.scene / etc.) in search results. Default: false (excluded).'\n ),\n },\n { readOnlyHint: true, openWorldHint: true },\n async ({ book_token, query, file_types, limit, include_structured }) => {\n try {\n // Only fetch the book when we actually need to decide about filtering.\n // For Writing Studio books the extra round-trip is wasted work.\n let bookType: string | undefined;\n if (!include_structured) {\n try {\n const book = await client.getBook(book_token);\n bookType = book.bookType;\n } catch {\n // If we can't load the book (eg. network hiccup), fall through without filtering.\n // The downstream search call will surface any real permission / not-found errors.\n }\n }\n\n const result = await client.searchFiles(book_token, {\n query,\n fileTypes: file_types,\n limit,\n });\n\n const shouldFilter = !include_structured && bookType === 'script';\n const filteredMatches = shouldFilter\n ? result.matches.filter((m) => !isStructuredScriptFile(m.file.path))\n : result.matches;\n\n if (filteredMatches.length === 0) {\n const suffix =\n shouldFilter && result.matches.length > 0\n ? ` (${result.matches.length} match(es) in structured files were excluded — pass include_structured: true to see them)`\n : '';\n return {\n content: [\n {\n type: 'text',\n text: `No matches found for \"${query}\"${suffix}.`,\n },\n ],\n };\n }\n\n const output: string[] = [\n `# Search Results for \"${query}\"`,\n '',\n `Found ${filteredMatches.length} file(s) with matches:`,\n '',\n ];\n\n if (shouldFilter && filteredMatches.length < result.matches.length) {\n const excluded = result.matches.length - filteredMatches.length;\n output.push(\n `_Note: excluded ${excluded} Script Studio structured file(s). Pass include_structured: true to include them._`\n );\n output.push('');\n }\n\n for (const match of filteredMatches) {\n output.push(`## ${match.file.path}`);\n output.push(`Words: ${match.file.wordCount} | Matches: ${match.matchCount}`);\n output.push('');\n\n for (const snippet of match.snippets) {\n output.push(`> ${snippet.text}`);\n output.push('');\n }\n }\n\n return {\n content: [{ type: 'text', text: output.join('\\n') }],\n };\n } catch (error) {\n return {\n content: [{ type: 'text', text: formatErrorForMcp(error) }],\n isError: true,\n };\n }\n }\n );\n}\n\n// Internal: path-based Script Studio structured file check.\n// Matches files with structured extensions OR any .json under .script_studio/.\nfunction isStructuredScriptFile(path: string): boolean {\n if (!path) return false;\n const lower = path.toLowerCase();\n if (lower.startsWith('.script_studio/') && lower.endsWith('.json')) return true;\n const ext = lower.split('.').pop();\n if (!ext) return false;\n return SCRIPT_STRUCTURED_EXTENSIONS.includes(ext);\n}\n","/**\n * What an AI may write, as ONE list.\n *\n * The authority is slima_vue `src/services/fileTypeRegistry.ts` (`aiPolicy`),\n * mirrored server-side in `db/contracts/file_type_registry.v1.json`. This file\n * is the third mirror, and it exists as data rather than prose specifically\n * because CLAUDE.md Gotcha #17 records what happens otherwise: `.map` was\n * AI-readonly from day one, the policy was restated in three separate places,\n * and one of them said \"write: Yes\" for months — so users burned credits on\n * writes that could never land.\n *\n * ## It has already drifted once, in both directions at the same time\n *\n * As of 2026-08-25 this file still listed `.map` as read-only (it became\n * writable on 2026-08-22) and did not mention `.beats` at all. So it was\n * simultaneously hiding a capability the author had paid for AND failing to\n * warn about one that could corrupt a file. A comment saying \"keep this in\n * sync\" does not keep anything in sync.\n *\n * ## The real fix, not done yet\n *\n * `GET /api/v1/mcp/capabilities` already returns this table, derived from the\n * contract Rails enforces. This file should be a build-time artifact of that\n * endpoint with CI failing when the committed copy differs. Until then, the\n * only honest thing this file can do is say as little as possible and point at\n * the tools that DO work — and `get_capabilities` remains the authority a model\n * should consult, which is why the server instructions tell it to call that first.\n */\n\n/** Generic write / edit / create / delete are allowed. */\nexport const AI_WRITABLE_EXTENSIONS = ['.md', '.markdown', '.txt'] as const;\n\n/**\n * Structured types with field-level MCP tools — the generic write tools are\n * refused, and the tool named here is the only way in.\n *\n * These are NOT read-only: saying so would hide a capability (the failure this\n * file's header describes, in the direction that costs the author money).\n *\n * All five landed together on 2026-09-06. Before that, `.map` and `.beats` took\n * a whole-document `write_file` and the other three were refused outright — two\n * different half-answers to the same question. A whole-document write is what\n * loses the parts of a file the model was never shown, so it is gone: the server\n * now refuses every generic write on these and names the tool below in the\n * refusal.\n */\nexport const AI_STRUCTURED_TOOLS: Record<string, string> = {\n '.beats': 'update_beat_board',\n '.timeline': 'update_timeline',\n '.script': 'update_script',\n '.map': 'update_relationship_map',\n '.geomap': 'update_world_map',\n};\n\n/**\n * No write tool may touch these — Slima's own editors own their format, and\n * there is no field-level route either.\n */\nexport const AI_READONLY_EXTENSIONS = [\n '.character',\n '.location',\n '.json',\n '.yaml',\n '.yml',\n] as const;\n\n/** Tools that CAN read a structured type usefully, keyed by extension. */\nexport const AI_STRUCTURED_VIEWERS: Record<string, string> = {\n '.map': 'view_relationship_map — renders the graph as an image plus a cast/relationship summary',\n};\n\n/**\n * The one paragraph the write tools carry. Deliberately short: it is paid for\n * on every request, including the conversations that never touch a file.\n */\nexport const AI_WRITE_POLICY_NOTE = `\n**Not every file type takes a generic write.**\n- Field-level tools only: ${Object.entries(AI_STRUCTURED_TOOLS)\n .map(([ext, tool]) => `\\`${ext}\\` → \\`${tool}\\``)\n .join(', ')}\n- Not writable at all: ${AI_READONLY_EXTENSIONS.map((e) => `\\`${e}\\``).join(', ')}\n- \\`.map\\` is the character relationship graph (關係圖) — not a geographic map; \\`.geomap\\` is the world map (地圖).\n\nA refusal names the file type and what to do instead — it is information, not\nan obstacle to route around. Call \\`get_capabilities\\` for the authoritative\ntable; this list ships with the client and can lag a deploy.`;\n","/**\n * Field-level operations on Slima's structured file types.\n *\n * ## Why these exist at all\n *\n * `.beats` / `.timeline` / `.script` are documents with a schema and a\n * reconciliation semantics, not text. Writing one whole-file means the model\n * has to send back every part of it — including the parts it was never given\n * and cannot see. That failure is silent: the file still parses, it just has\n * less of the author's work in it (a beat board loses its chapter cards; a\n * relationship map loses a character). These tools take one change at a time,\n * so the parts the model cannot see are parts it cannot drop.\n *\n * ## Why three tools and not forty-two\n *\n * Tool descriptions are sent on EVERY request, including the conversations\n * that only read a chapter. The individual operations for these three types\n * come to ~33KB of schema; batched, they are ~17KB, and the shape is one the\n * models have already been through Gemini/Grok's schema subset with. Each tool\n * takes `operations[]` — send one to make one change.\n *\n * ## Why the names start with a verb\n *\n * The desktop agent decides whether to ask a human by looking for a verb\n * prefix on the tool name. `beats_batch` does not read as a write, so it would\n * not prompt. `update_beat_board` does.\n * (NOTE: this is second-hand — confirm against the desktop agent's source\n * before treating the prefix list as exhaustive.)\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport type { McpStructuredResponse } from '../api/types.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\n\n/** One entry per structured type that has a server-side executor. */\ninterface StructuredType {\n /** MCP tool name — MUST start with a verb (see file header). */\n tool: string;\n extension: string;\n /** What the author calls it. */\n noun: string;\n /**\n * One line right after the opening sentence, for a type whose extension says the\n * wrong thing: `.map` reads like a geographic map but is the relationship graph (d11).\n */\n what?: string;\n /** The operations this type accepts, for the description. */\n ops: string;\n /** A concrete batch, in this type's language. */\n example: string;\n /** Rules that belong to this type alone. Omit when the shared ones are enough. */\n extra?: string;\n}\n\nconst TYPES: StructuredType[] = [\n {\n tool: 'update_beat_board',\n extension: '.beats',\n noun: 'beat board',\n ops: 'add_act, update_act, remove_act, add_beat, update_beat, remove_beat, move_beat',\n example:\n '{\"op\": \"add_act\", \"name\": \"Act Two\", \"op_ref\": \"A2\"}, ' +\n '{\"op\": \"add_beat\", \"title\": \"The turn\", \"act_id\": \"@A2\"}',\n },\n {\n tool: 'update_timeline',\n extension: '.timeline',\n noun: 'timeline',\n ops: 'add_entry, update_entry, remove_entry, set_config',\n example: '{\"op\": \"add_entry\", \"title\": \"The siege begins\", \"time_mode\": \"label\", \"time_label\": \"Winter\"}',\n },\n {\n tool: 'update_script',\n extension: '.script',\n noun: 'screenplay',\n ops: 'add_scene, update_scene_heading, remove_scene, add_element, update_element, remove_element',\n example:\n '{\"op\": \"add_scene\", \"interior\": true, \"location\": \"HARBOUR\", \"op_ref\": \"S1\"}, ' +\n '{\"op\": \"add_element\", \"scene_id\": \"@S1\", \"type\": \"action\", \"text\": \"The fog lifts.\"}',\n },\n {\n tool: 'update_relationship_map',\n extension: '.map',\n noun: 'relationship map',\n what:\n '**This is the character relationship graph (關係圖) — not a geographic map.** A world map\\n' +\n '(地圖) is `.geomap`, changed with `update_world_map`.',\n ops:\n 'add_node, update_node, move_node, remove_node, add_edge, update_edge, remove_edge, ' +\n 'add_group, update_group, remove_group, set_meta',\n example:\n '{\"op\": \"add_node\", \"label\": \"Lin Shuang\", \"op_ref\": \"N1\", ' +\n '\"position\": {\"near_id\": \"<id of Shen Yan>\", \"direction\": \"right\"}}, ' +\n '{\"op\": \"add_edge\", \"source_id\": \"<id of Shen Yan>\", \"target_id\": \"@N1\", \"label\": \"mentor\"}',\n extra:\n '**Position is content here, not layout.** `update_node` will not move anything — that is\\n' +\n '`move_node`, so that \"I moved her next to him\" is its own reviewable change. Prefer\\n' +\n '`{near_id, direction}` over computing numbers. Existing nodes never move; the final\\n' +\n 'coordinates come back in the notes. Group boxes are computed from `member_ids` — never\\n' +\n 'send x/y/width/height. Removing a node removes its edges, and you are told so.',\n },\n {\n tool: 'update_world_map',\n extension: '.geomap',\n noun: 'world map',\n what:\n '**This is the geographic world map (地圖).** A character relationship graph (關係圖) is\\n' +\n '`.map`, changed with `update_relationship_map`.',\n ops:\n 'add_place, update_place, add_label, update_label, remove_element, add_river, ' +\n 'add_border, paint_region, set_meta',\n example:\n '{\"op\": \"add_place\", \"x\": 210, \"y\": 96, \"name\": \"Starhaven\", \"tier\": \"port\"}, ' +\n '{\"op\": \"add_label\", \"x\": 240, \"y\": 60, \"text\": \"The Cold Sea\", \"size\": \"large\"}',\n extra:\n '**Coordinates are grid cells, not pixels**: x 0-519, y 0-363. `read_file` gives you the\\n' +\n 'map with its three terrain grids replaced by a note — they are machine-encoded and you\\n' +\n 'do not need them; `paint_region` works in cells. `remove_element` takes any element id\\n' +\n '(`pl_` place, `lb_` label, `rv_` river, `bd_` border). Terrain has no undo: paint over it\\n' +\n 'instead. Move a place with `update_place`, never by removing and re-adding it. An unknown\\n' +\n 'place `tier` is refused with the full list of real ones — read the error, do not guess.',\n },\n];\n\n/**\n * Shared rules. Kept in one string because they are the same rules for every\n * type, and three drifting copies is how the `.map` policy ended up being\n * taught three different ways for months.\n */\nconst SHARED_RULES = `\n**Read the file first.** Every id you send must come from that read — never\ninvent one, never retype one from memory. An unknown id is refused with the\nlist of real ones, so a mistake costs a round trip, not the author's data.\n\n**One operation per change.** Each operation takes only its own arguments; a\nfield belonging to a different operation is refused rather than ignored.\n\n**\\`op_ref\\` chains within one batch.** Label what an operation creates with\n\\`op_ref: \"A2\"\\`, then a LATER operation can point at it as \\`\"@A2\"\\`. Backward\nreferences only, and only inside this one batch — a later call must use the\nreal id, which comes back in \\`applied\\`.\n\n**The batch is all-or-nothing.** If one operation fails nothing is written, so\nyou never have to work out how far it got.\n\n**Unlike the in-app assistant, there is no review panel here.** What you send\nlands in the author's version history immediately. Say what you changed, not\n\"please confirm it\".`;\n\nfunction describe(type: StructuredType): string {\n return `Change a Slima ${type.noun} (\\`${type.extension}\\`) one field at a time.${type.what ? `\\n\\n${type.what}` : ''}\n\nThis is the ONLY way to write this file type — \\`write_file\\` and \\`edit_file\\`\nare refused for it, because a whole-file write would drop the parts of the\ndocument you were never shown.\n\nOperations: ${type.ops}\nExample \\`operations\\`: [${type.example}]\n${SHARED_RULES}${type.extra ? `\\n\\n${type.extra}` : ''}`;\n}\n\nfunction render(type: StructuredType, result: McpStructuredResponse): string {\n const lines = [`Applied ${result.applied.length} change(s) to the ${type.noun}.`, ''];\n\n const created = result.applied.filter((a) => a.id);\n if (created.length > 0) {\n lines.push('**New ids** (use these in later calls — do not re-read just for them):');\n for (const a of created) lines.push(`- \\`${a.op}\\` → ${a.id}${a.op_ref ? ` (was @${a.op_ref})` : ''}`);\n lines.push('');\n }\n\n // Side effects the model did not ask for. These are the whole reason the\n // server returns notes: the author will see them, so the author must be told.\n if (result.notes.length > 0) {\n lines.push('**Also happened, tell the author:**');\n for (const note of result.notes) lines.push(`- ${note}`);\n lines.push('');\n }\n\n lines.push(`**Commit:** ${result.commit.token}`);\n return lines.join('\\n');\n}\n\nexport function registerStructuredTools(server: McpServer, client: SlimaApiClient): void {\n for (const type of TYPES) {\n server.tool(\n type.tool,\n describe(type),\n {\n book_token: z.string().describe('Book token (e.g., bk_abc123)'),\n path: z.string().describe(`Path to the ${type.extension} file (e.g., \"plan/board${type.extension}\")`),\n intent: z\n .string()\n .describe(\n \"One line in the author's own language saying what this batch does — it becomes the \" +\n 'commit message they read later. Not \"batch update\".'\n ),\n operations: z\n .array(z.record(z.unknown()))\n .describe(\n 'The changes, in order. Each is a flat object: `op` names the operation and the other ' +\n 'keys are that operation\\'s arguments. There is no nested `args`.'\n ),\n },\n { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ book_token, path, intent, operations }) => {\n try {\n const result = await client.applyStructuredOps(book_token, {\n path,\n intent,\n operations: operations as Array<Record<string, unknown>>,\n });\n return { content: [{ type: 'text', text: render(type, result) }] };\n } catch (error) {\n // Rails' messages for these already say what went wrong AND what to do\n // next (they list the real ids, name which operation owns a field), so\n // pass them through verbatim rather than summarising them away.\n return { content: [{ type: 'text', text: formatErrorForMcp(error) }], isError: true };\n }\n }\n );\n }\n}\n","/**\n * Teaching tools.\n *\n * Slima's operating knowledge — how commits work, what a manuscript file is,\n * which file types an AI may write — is authored and served by Rails, not\n * carried in this package. Users pin versions of this client; the\n * `X-Slima-MCP-Upgrade-Hint` machinery exists because they do. Guidance shipped\n * here would go stale in the field, silently, and would keep telling an AI to do\n * things the server no longer supports.\n *\n * So these tools are deliberately thin: they fetch and hand over text. The\n * judgement about what to say lives with the system being described.\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\n\nconst READ_ONLY = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n} as const;\n\nfunction text(body: string) {\n return { content: [{ type: 'text' as const, text: body }] };\n}\n\nfunction failure(error: unknown) {\n return { content: [{ type: 'text' as const, text: formatErrorForMcp(error) }], isError: true };\n}\n\nexport function registerTeachingTools(server: McpServer, client: SlimaApiClient): void {\n // === get_capabilities ===\n server.tool(\n 'get_capabilities',\n `What this Slima server supports: which file extensions an AI may write, the content types, the available tools, and the guide index.\n\nCall this once at the start of a Slima session, before writing anything. Every field is derived from the contract the server actually enforces, so it cannot drift from real behaviour the way a written description can.`,\n {},\n READ_ONLY,\n async () => {\n try {\n return text(JSON.stringify(await client.getCapabilities(), null, 2));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n // === list_guides ===\n server.tool(\n 'list_guides',\n `List the available Slima guides: titles and one-line summaries of when to load each.\n\nGuides teach how to operate Slima — file structure, version control, team knowledge bases, the relationship-map format. Load one with \\`load_guide\\` when you are about to do the thing it covers.`,\n {},\n READ_ONLY,\n async () => {\n try {\n const result = await client.listGuides();\n const rows = result.guides.map((g) => `- **${g.slug}** — ${g.title}\\n ${g.summary}`);\n return text(`## Slima guides\\n\\n${rows.join('\\n')}`);\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n // === load_guide ===\n server.tool(\n 'load_guide',\n `Load one Slima guide in full.\n\nRead \\`book-structure\\` before creating files, \\`version-control-basics\\` before a large rewrite, \\`team-knowledge-base\\` when working in a publisher's team, \\`beta-reader\\` before spending the author's credits on a report. \\`list_guides\\` has the current set.`,\n {\n slug: z.string().describe('Guide slug, e.g. \"book-structure\" (from list_guides)'),\n },\n READ_ONLY,\n async ({ slug }) => {\n try {\n const guide = await client.loadGuide(slug);\n return text(`# ${guide.title}\\n\\n${guide.body}`);\n } catch (error) {\n // The server answers an unknown slug with the list of real ones, so the\n // error is itself usable — passing it through beats replacing it with\n // \"not found\".\n return failure(error);\n }\n }\n );\n\n // === list_writing_skills ===\n server.tool(\n 'list_writing_skills',\n `List Slima's writing skills — the craft procedures the in-app coach follows for tasks like developing a character, revising a scene, or diagnosing pacing.\n\nUse these when the task is about the WRITING. Use \\`list_guides\\` when the task is about operating Slima.`,\n {},\n READ_ONLY,\n async () => {\n try {\n const result = await client.listWritingSkills();\n const rows = result.skills.map((s) => `- **${s.key}** — ${s.name}: ${s.description}`);\n return text(`## Writing skills\\n\\n${rows.join('\\n')}`);\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n // === load_writing_skill ===\n server.tool(\n 'load_writing_skill',\n `Load one writing skill in full — a step-by-step procedure plus the questions a coach would ask.\n\nFollow it rather than summarising it at the author.`,\n {\n key: z.string().describe('Skill key, e.g. \"character_develop\" (from list_writing_skills)'),\n },\n READ_ONLY,\n async ({ key }) => {\n try {\n const skill = await client.loadWritingSkill(key);\n return text(`# ${skill.name}\\n\\n${skill.body}`);\n } catch (error) {\n return failure(error);\n }\n }\n );\n}\n","/**\n * Team tools — read-only.\n *\n * Governance (enabling a skill, authoring a rule, changing the roster) is not\n * on the server's MCP surface for any role, so there is nothing to expose here.\n * Enabling a skill is what turns an unvetted file into something the AI applies\n * for the whole team; that stays a human decision.\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\n\nconst READ_ONLY = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n} as const;\n\nfunction json(body: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(body, null, 2) }] };\n}\n\nfunction failure(error: unknown) {\n return { content: [{ type: 'text' as const, text: formatErrorForMcp(error) }], isError: true };\n}\n\nconst TEAM_TOKEN = z.string().describe('Team token (from list_teams)');\n\nexport function registerTeamTools(server: McpServer, client: SlimaApiClient): void {\n server.tool(\n 'list_teams',\n `List the teams this account belongs to — publishers, studios, writing groups.\n\nA team book carries house rules and shared knowledge that change how you should write for it. If a book belongs to a team, read \\`list_team_rules\\` before writing.`,\n {},\n READ_ONLY,\n async () => {\n try {\n return json(await client.listTeams());\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'get_team',\n 'Get one team: its members and their roles.',\n { team_token: TEAM_TOKEN },\n READ_ONLY,\n async ({ team_token }) => {\n try {\n return json(await client.getTeam(team_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'list_team_rules',\n `The team's coaching rules — its house style — each marked mandatory or advisory.\n\nRead these BEFORE writing for a team book, not after a draft is rejected. Mandatory rules are not suggestions: prose that ignores one gets re-edited by a person, which is worse than not writing it.`,\n { team_token: TEAM_TOKEN },\n READ_ONLY,\n async ({ team_token }) => {\n try {\n return json(await client.listTeamRules(team_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'list_team_memories',\n `What the team has established over time: recurring characters, naming conventions, continuity spanning books, decisions already argued through.\n\nTreat these as settled. If one contradicts a book's text, say so rather than picking a side.`,\n { team_token: TEAM_TOKEN },\n READ_ONLY,\n async ({ team_token }) => {\n try {\n return json(await client.listTeamMemories(team_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'list_team_skills',\n `The team's own writing procedures.\n\nSome are marked \\`confidential: true\\` and come back WITHOUT a source path; their file contents are withheld from this connection. That is deliberate, and it does not stop the skill working — the server applies it on the team's behalf. Do not try to reconstruct one, reproduce it verbatim, or reach its contents by another route: those routes are closed and audited. Read the \\`team-knowledge-base\\` guide before working in a team knowledge base.`,\n { team_token: TEAM_TOKEN },\n READ_ONLY,\n async ({ team_token }) => {\n try {\n return json(await client.listTeamSkills(team_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n}\n","/**\n * Notes and comments — the author's working material around the manuscript.\n *\n * Destructive operations are absent because the server does not offer them to\n * MCP: a note is not version-controlled (unlike a file, there is no history to\n * recover it from), and accepting a suggested change applies an edit to the\n * manuscript, which is the author's decision rather than the agent's.\n */\n\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport { formatErrorForMcp } from '../utils/errors.js';\n\nconst READ_ONLY = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n} as const;\n\nconst WRITES = {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n} as const;\n\nconst BOOK_TOKEN = z.string().describe('Book token (bk_...)');\n\nfunction json(body: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(body, null, 2) }] };\n}\n\nfunction failure(error: unknown) {\n return { content: [{ type: 'text' as const, text: formatErrorForMcp(error) }], isError: true };\n}\n\nexport function registerWorkspaceTools(server: McpServer, client: SlimaApiClient): void {\n server.tool(\n 'list_notes',\n \"The author's margin notes on this book — book-level and chapter-level, including foreshadowing pairs.\",\n { book_token: BOOK_TOKEN },\n READ_ONLY,\n async ({ book_token }) => {\n try {\n return json(await client.listNotes(book_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'create_note',\n `Leave a note beside the manuscript. Notes do not touch the prose or the word count.\n\nUse \\`note_type: \"chapter\"\\` with the file's token in \\`file_id\\` to anchor it to one chapter; \\`\"book\"\\` for the work as a whole. Do not use notes as your own scratchpad — they appear in the author's interface as their own thinking. See the \\`notes-and-foreshadowing\\` guide.`,\n {\n book_token: BOOK_TOKEN,\n title: z.string().describe('Short title (required)'),\n content: z.string().optional().describe('Body, up to 50,000 characters'),\n note_type: z.enum(['book', 'chapter']).optional().describe('Defaults to \"book\"'),\n file_id: z.string().optional().describe('File token — required when note_type is \"chapter\"'),\n color: z.enum(['gray', 'red', 'blue', 'green', 'yellow', 'purple']).optional(),\n },\n WRITES,\n async ({ book_token, title, content, note_type, file_id, color }) => {\n try {\n // `client_id` is deliberately not exposed: it is the Slima app's local\n // primary key, the server mints one when it is absent, and a non-UUID\n // value is rejected. Nothing an agent could pass here would be better\n // than what the server generates.\n return json(\n await client.createNote(book_token, {\n title,\n content,\n note_type: note_type ?? 'book',\n file_id,\n color,\n })\n );\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'semantic_search',\n `Find passages by MEANING rather than by the words used.\n\n\\`search_content\\` finds the word an author typed; this finds the passage they meant — \"where does the mother's resentment surface?\" returns the scene even when neither word appears in it. On a team knowledge base it is the difference between browsing and asking.\n\nCosts a small embedding charge, unlike literal search. Returns §N paragraph anchors so you can quote them back precisely.`,\n {\n book_token: BOOK_TOKEN,\n query: z.string().describe('What you are looking for, in natural language'),\n limit: z.number().optional().describe('Max results (default 10, cap 50)'),\n },\n { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },\n async ({ book_token, query, limit }) => {\n try {\n return json(await client.semanticSearch(book_token, query, limit));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'list_comments',\n `Editorial comment threads on this book, open and resolved.\n\nRead these before starting work on a book you have been handed — they are where an editor has already said what is wrong, and producing an independent list without reading them usually contradicts a decision someone already made.`,\n { book_token: BOOK_TOKEN },\n READ_ONLY,\n async ({ book_token }) => {\n try {\n return json(await client.listComments(book_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'create_comment',\n `Leave an editorial comment.\n\nComment when the change is a judgement call — pacing, whether a motivation lands, whether a scene earns its length. Edit the file instead when the author asked for the change. When unsure, comment: a comment costs ten seconds to dismiss, an unwanted rewrite costs the author a diff of their own prose.`,\n {\n book_token: BOOK_TOKEN,\n content: z.string().describe('The comment'),\n file_token: z.string().optional().describe('File this comment is about, if any'),\n },\n WRITES,\n async ({ book_token, content, file_token }) => {\n try {\n return json(await client.createComment(book_token, { content, file_token }));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'resolve_comment',\n `Mark a comment thread addressed.\n\nSay what you did when you resolve one. A silent resolve tells the editor nothing about whether you understood the note.`,\n {\n book_token: BOOK_TOKEN,\n comment_token: z.string().describe('Comment token (from list_comments)'),\n },\n WRITES,\n async ({ book_token, comment_token }) => {\n try {\n return json(await client.resolveComment(book_token, comment_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n\n server.tool(\n 'reopen_comment',\n 'Reopen a comment thread that was resolved but should not have been.',\n {\n book_token: BOOK_TOKEN,\n comment_token: z.string().describe('Comment token (from list_comments)'),\n },\n WRITES,\n async ({ book_token, comment_token }) => {\n try {\n return json(await client.reopenComment(book_token, comment_token));\n } catch (error) {\n return failure(error);\n }\n }\n );\n}\n","/**\n * Per-book MCP schema Resource.\n *\n * Registered at URI template `slima://books/{book_token}/schema`. AI clients\n * can read this on demand to learn which paths are writable / read-only for\n * a specific book, without that rule set bloating every tool description.\n *\n * This mirrors the canonical rule enforced by\n * `Mcp::FileOperationService#validate_studio_write_path!` on the Rails side —\n * Rails is the ground truth, this Resource is the ambient-context mirror.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { SlimaApiClient } from '../api/client.js';\nimport type { Book } from '../api/types.js';\nimport {\n AI_READONLY_EXTENSIONS,\n AI_STRUCTURED_TOOLS,\n AI_STRUCTURED_VIEWERS,\n AI_WRITABLE_EXTENSIONS,\n} from '../ai-file-policy.js';\n\nconst BOOK_SCHEMA_URI_TEMPLATE = 'slima://books/{book_token}/schema';\n\nexport function registerBookSchemaResource(\n server: McpServer,\n client: SlimaApiClient\n): void {\n server.resource(\n 'slima-book-schema',\n new ResourceTemplate(BOOK_SCHEMA_URI_TEMPLATE, {\n // Intentionally no `list` callback — books may number in the hundreds, so\n // we expose only the URI template via `resources/templates/list` and let\n // clients call `resources/read` with a concrete token.\n list: undefined,\n }),\n {\n description:\n 'Per-book MCP operation schema: which paths are readable/writable, which are structured (read-only), and the structured file catalog. Read this before planning writes on an unfamiliar book.',\n mimeType: 'application/json',\n },\n async (uri, variables) => {\n const rawToken = variables['book_token'];\n const bookToken = Array.isArray(rawToken) ? rawToken[0] : rawToken;\n\n if (!bookToken) {\n throw new Error(\n `Invalid Slima book schema URI: missing book_token in ${uri.toString()}`\n );\n }\n\n const book = await client.getBook(bookToken);\n const schema = buildBookSchema(book);\n\n return {\n contents: [\n {\n uri: uri.toString(),\n mimeType: 'application/json',\n text: JSON.stringify(schema, null, 2),\n },\n ],\n };\n }\n );\n}\n\nexport function buildBookSchema(book: Book): Record<string, unknown> {\n const isScript = book.bookType === 'script';\n const isKnowledge = book.bookType === 'knowledge';\n\n return {\n book_token: book.token,\n book_title: book.title,\n book_type: book.bookType ?? 'book',\n studio_label: isScript\n ? 'Script Studio'\n : isKnowledge\n ? 'Team Knowledge Base'\n : 'Writing Studio',\n mcp_capabilities: {\n // Note: actual reads still go through validate_safe_path!\n // (blocks absolute paths, traversal, control chars) even though it's\n // not reflected in this allowlist.\n readable_paths:\n 'Any path within the book tree (path-traversal and absolute paths still rejected).',\n writable_paths: isScript ? ['.script_studio/planning/**/*'] : ['**/*'],\n writable_exceptions: isScript ? ['.script_studio/planning/.initialized'] : [],\n readonly_paths: isScript\n ? [\n '.script_studio/series.json',\n '.script_studio/seasons/**/season.json',\n '.script_studio/seasons/**/episodes/**/episode.json',\n '.script_studio/seasons/**/scenes/*.scene',\n '.script_studio/characters/**/*.character',\n '.script_studio/locations/**/*.location',\n '.script_studio/storylines/**/*.storyline',\n '.script_studio/notes/**/*.note',\n '.script_studio/planning/.initialized',\n ]\n : [],\n reserved_sentinels: isScript ? ['.script_studio/planning/.initialized'] : [],\n // Path allowlists are not the whole story on a team book: the server also\n // checks the caller's ROLE. An editor gets 403 on every write no matter\n // which path, so \"writable_paths\" above is an upper bound, not a promise.\n write_access_note: isKnowledge\n ? 'Team knowledge base: owner / admin / seated writer may write; an editor is read-only ' +\n '(every write returns 403). Files under .slima/skills/ may be enabled as team skills by a manager.'\n : 'Personal books: the owner and writer collaborators may write; editor collaborators are read-only.',\n },\n // AI file-type policy — mirrors slima_vue `fileTypeRegistry.ts` (`aiPolicy`).\n // Applies to BOTH studio types; the Script Studio path allowlist above is a\n // separate, additional restriction.\n ai_file_policy: {\n writable: [...AI_WRITABLE_EXTENSIONS],\n // Structured types are not one bucket. Collapsing them into `readonly`\n // is what made this resource tell the model `.map` could not be written\n // for weeks after it could, and never mention `.beats` at all.\n field_level_tools: AI_STRUCTURED_TOOLS,\n readonly: [...AI_READONLY_EXTENSIONS],\n readonly_note:\n 'Read-only means the AI must not write these via write_file / edit_file / create_file / ' +\n 'append_to_file / delete_file. Slima owns their structure; overwriting one corrupts it for the ' +\n 'author. Recommend changes in words and point the author at that file type\\'s editor.',\n field_level_note:\n 'These take one change at a time through the tool named above. Every generic write is refused ' +\n '(write_file / edit_file / append_to_file), because a whole-document write drops the parts of ' +\n 'the file you were never shown — a beat board loses its chapter cards, a relationship map loses ' +\n 'a character, and nothing reports it. create_file works: it writes an empty document of the ' +\n 'right shape and ignores the content you send, so you then fill it in with the tool.',\n // ⚠️ This block ships with the client and can lag a Rails deploy.\n // `get_capabilities` is derived from the contract Rails enforces; when the\n // two disagree, that one is right.\n authority: 'get_capabilities',\n viewers: AI_STRUCTURED_VIEWERS,\n },\n structured_file_types: isScript\n ? {\n 'series.json': 'Series metadata (title, genre, logline, theme, synopsis, acts)',\n 'season.json': 'Season metadata',\n 'episode.json': 'Episode metadata',\n '*.scene': 'Scene content with scene-specific format',\n '*.character': 'Character definition with scope (series / season / episode)',\n '*.location': 'Location definition with scope',\n '*.storyline': 'Storyline with tier and ID linkage',\n '*.note': 'Note with scope',\n }\n : {},\n notes: isScript\n ? 'Structured files require schema-aware edits — use the Script Studio UI. MCP write access is restricted to planning/ (free-form reference tree) to prevent schema corruption.'\n : isKnowledge\n ? 'Team knowledge base. Free-form paths, but two team rules apply: writes follow team role ' +\n '(see write_access_note), and a file marked confidential is returned WITHOUT its content — ' +\n 'read_file reports it as withheld rather than empty. That is intentional: the AI may use ' +\n 'such a skill without reading it. Never try to rewrite a withheld file.'\n : 'Writing Studio books are free-form. No schema restrictions; any path is writable.',\n policy_source: 'Rails: Mcp::FileOperationService#validate_studio_write_path!',\n };\n}\n","/**\n * Server-level MCP `instructions` — injected into the `initialize` response so\n * AI clients see it at session start, before any tool call.\n *\n * This text is paid for on EVERY session, including the ones that only read a\n * chapter. So it is a map, not a manual: it says what Slima is, names the one\n * call that answers \"what can I do here\", and stops. Everything it used to\n * spell out — Script Studio's write rules, per-extension policy, the file-type\n * table — now lives behind `get_capabilities` and `load_guide`, which are\n * loaded on demand AND update when Rails deploys.\n *\n * That second property is the one that matters: users pin versions of this\n * package (the `X-Slima-MCP-Upgrade-Hint` mechanism exists because they do), so\n * anything asserted here keeps being asserted long after it stops being true.\n * `.map` was described as AI-writable in Slima's own prompt for months after it\n * stopped being so. Text in a pinned client is that failure with no upper bound\n * on how long it lasts.\n *\n * Keep it short. If you are about to add a paragraph here, it belongs in a\n * guide.\n */\n\nexport const SERVER_INSTRUCTIONS = `Slima is an AI writing IDE for long-form fiction. You are connected to an author's account.\n\n**Start by calling \\`get_capabilities\\`.** It returns what this server supports — which file types you may write, how content types work, the tools available, and the guide index — derived from the contract the server enforces, so it is never out of date.\n\nThen \\`load_guide\\` what the task needs: \\`getting-started\\` for the shape of a book, \\`book-structure\\` before creating files, \\`version-control-basics\\` before a large rewrite, \\`team-knowledge-base\\` inside a publisher's team, \\`beta-reader\\` before spending the author's credits.\n\nThree things worth knowing before your first call:\n\n- **Every write is a commit.** There is no save step, and the author sees your commits beside their own. Write real commit messages.\n- **The author may be editing the same file.** Prefer \\`edit_file\\` over \\`update_file\\`: an edit that cannot find its anchor fails loudly, where a whole-file write silently replaces whatever they just typed.\n- **Not every file type is yours to write.** A refusal naming the file type is information, not an obstacle to route around.\n\nSlima's AI is a writing coach by default. When a request is vague or the author is thinking out loud, ask the question that unblocks them. When they ask plainly — \"write this scene\", \"rewrite this paragraph\" — do it, without lecturing.`;\n","/**\n * Logger 工具\n *\n * 重要:MCP STDIO 模式下,stdout 是協議通道,\n * 所有日誌必須輸出到 stderr\n */\n\nconst isDebug = process.env.DEBUG === 'true' || process.env.DEBUG === '1';\n\nexport const logger = {\n info: (message: string, ...args: unknown[]) => {\n console.error(`[INFO] ${message}`, ...args);\n },\n\n warn: (message: string, ...args: unknown[]) => {\n console.error(`[WARN] ${message}`, ...args);\n },\n\n error: (message: string, error?: unknown) => {\n if (error instanceof Error) {\n console.error(`[ERROR] ${message}:`, error.message);\n if (isDebug && error.stack) {\n console.error(error.stack);\n }\n } else if (error !== undefined) {\n console.error(`[ERROR] ${message}:`, error);\n } else {\n console.error(`[ERROR] ${message}`);\n }\n },\n\n debug: (message: string, ...args: unknown[]) => {\n if (isDebug) {\n console.error(`[DEBUG] ${message}`, ...args);\n }\n },\n};\n","/**\n * Token Storage Utility (CLI)\n *\n * Stores API Token in user's home directory\n * ~/.slima/credentials.json\n */\n\nimport { promises as fs } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nconst SLIMA_DIR = '.slima';\nconst CREDENTIALS_FILE = 'credentials.json';\n\ninterface Credentials {\n apiToken: string;\n createdAt: string;\n baseUrl?: string;\n}\n\nfunction getCredentialsPath(): string {\n return join(homedir(), SLIMA_DIR, CREDENTIALS_FILE);\n}\n\nfunction getSlimaDir(): string {\n return join(homedir(), SLIMA_DIR);\n}\n\n/**\n * Save API Token\n */\nexport async function saveToken(apiToken: string, baseUrl?: string): Promise<void> {\n const slimaDir = getSlimaDir();\n\n // Ensure directory exists\n await fs.mkdir(slimaDir, { recursive: true });\n\n const credentials: Credentials = {\n apiToken,\n createdAt: new Date().toISOString(),\n ...(baseUrl && { baseUrl }),\n };\n\n const credentialsPath = getCredentialsPath();\n await fs.writeFile(credentialsPath, JSON.stringify(credentials, null, 2), 'utf-8');\n\n // Set file permission to 600 (owner only)\n await fs.chmod(credentialsPath, 0o600);\n}\n\n/**\n * Load API Token\n */\nexport async function loadToken(): Promise<Credentials | null> {\n try {\n const credentialsPath = getCredentialsPath();\n const content = await fs.readFile(credentialsPath, 'utf-8');\n return JSON.parse(content) as Credentials;\n } catch {\n return null;\n }\n}\n\n/**\n * Delete API Token\n */\nexport async function deleteToken(): Promise<void> {\n try {\n const credentialsPath = getCredentialsPath();\n await fs.unlink(credentialsPath);\n } catch {\n // File doesn't exist, ignore\n }\n}\n\n/**\n * Check if authenticated\n */\nexport async function isAuthenticated(): Promise<boolean> {\n const credentials = await loadToken();\n return credentials !== null && !!credentials.apiToken;\n}\n\n/**\n * Get credentials file path (for display)\n */\nexport function getCredentialsFilePath(): string {\n return getCredentialsPath();\n}\n","/**\n * CLI Auth Command\n *\n * 實作類似 Claude Code / GitHub CLI 的認證流程:\n * 1. 使用者執行 `slima-mcp auth`\n * 2. 開啟瀏覽器到 Slima 認證頁面\n * 3. 使用者登入並授權\n * 4. 本地伺服器接收 callback 並儲存 token\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { URL } from 'node:url';\nimport { randomBytes } from 'node:crypto';\nimport { saveToken, loadToken, deleteToken, getCredentialsFilePath } from './token-storage.js';\n\n// 認證使用 API 伺服器(處理 OAuth 和 session)\nconst DEFAULT_API_URL = 'https://api.slima.ai';\nconst CALLBACK_PORT_START = 8765;\nconst CALLBACK_PORT_END = 8775;\n\n/**\n * 開啟瀏覽器\n */\nasync function openBrowser(url: string): Promise<void> {\n const { exec } = await import('node:child_process');\n const { promisify } = await import('node:util');\n const execAsync = promisify(exec);\n\n const platform = process.platform;\n\n try {\n if (platform === 'darwin') {\n await execAsync(`open \"${url}\"`);\n } else if (platform === 'win32') {\n await execAsync(`start \"\" \"${url}\"`);\n } else {\n // Linux / WSL\n // 嘗試多個可能的命令\n const commands = ['xdg-open', 'sensible-browser', 'wslview'];\n for (const cmd of commands) {\n try {\n await execAsync(`${cmd} \"${url}\"`);\n return;\n } catch {\n // 繼續嘗試下一個\n }\n }\n // 如果都失敗,顯示 URL 讓使用者手動開啟\n console.log(`\\nPlease open this URL in your browser:\\n${url}\\n`);\n }\n } catch {\n console.log(`\\nPlease open this URL in your browser:\\n${url}\\n`);\n }\n}\n\n/**\n * 尋找可用的 port\n */\nasync function findAvailablePort(): Promise<number> {\n for (let port = CALLBACK_PORT_START; port <= CALLBACK_PORT_END; port++) {\n const available = await isPortAvailable(port);\n if (available) {\n return port;\n }\n }\n throw new Error(`No available ports between ${CALLBACK_PORT_START} and ${CALLBACK_PORT_END}`);\n}\n\nfunction isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const server = createServer();\n server.once('error', () => resolve(false));\n server.once('listening', () => {\n server.close(() => resolve(true));\n });\n server.listen(port, '127.0.0.1');\n });\n}\n\n/**\n * 產生隨機 state(防止 CSRF)\n */\nfunction generateState(): string {\n return randomBytes(16).toString('hex');\n}\n\n/**\n * 成功頁面 HTML\n * Design follows UIUX_SPEC.md: minimalist, grayscale, Inter font\n */\nfunction getSuccessHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Welcome to Slima</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n background: #FBFBFA;\n color: #1D1D1F;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n .container {\n text-align: center;\n padding: 48px;\n max-width: 420px;\n }\n .logo {\n width: 48px;\n height: 48px;\n margin-bottom: 32px;\n opacity: 0.9;\n }\n .success-icon {\n width: 64px;\n height: 64px;\n margin-bottom: 24px;\n border-radius: 50%;\n background: #1D1D1F;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n margin-right: auto;\n }\n .success-icon svg {\n width: 32px;\n height: 32px;\n stroke: #FBFBFA;\n }\n h1 {\n font-size: 24px;\n font-weight: 600;\n letter-spacing: -0.02em;\n margin-bottom: 12px;\n color: #1D1D1F;\n }\n .subtitle {\n font-size: 15px;\n color: #6E6E73;\n line-height: 1.5;\n margin-bottom: 8px;\n }\n .hint {\n font-size: 13px;\n color: #86868B;\n margin-top: 24px;\n }\n .divider {\n width: 40px;\n height: 1px;\n background: #E5E5E5;\n margin: 32px auto;\n }\n .footer {\n font-size: 12px;\n color: #86868B;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <img src=\"https://app.slima.ai/icons/slima-black.svg\" alt=\"Slima\" class=\"logo\">\n <div class=\"success-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4.5 12.75l6 6 9-13.5\" />\n </svg>\n </div>\n <h1>Welcome to Slima</h1>\n <p class=\"subtitle\">Authentication successful. Your CLI is now connected.</p>\n <p class=\"hint\">You can close this window and return to your terminal.</p>\n <div class=\"divider\"></div>\n <p class=\"footer\">Slima MCP is ready to use</p>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * 錯誤頁面 HTML\n */\n/**\n * HTML escape to prevent XSS\n */\nfunction escapeHtml(text: string): string {\n const map: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n };\n return text.replace(/[&<>\"']/g, (char) => map[char]);\n}\n\n/**\n * 錯誤頁面 HTML\n * Design follows UIUX_SPEC.md: minimalist, grayscale, Inter font\n */\nfunction getErrorHtml(message: string): string {\n const safeMessage = escapeHtml(message);\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>Slima - Authentication Error</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n background: #FBFBFA;\n color: #1D1D1F;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n .container {\n text-align: center;\n padding: 48px;\n max-width: 420px;\n }\n .logo {\n width: 48px;\n height: 48px;\n margin-bottom: 32px;\n opacity: 0.9;\n }\n .error-icon {\n width: 64px;\n height: 64px;\n margin-bottom: 24px;\n border-radius: 50%;\n background: #1D1D1F;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: auto;\n margin-right: auto;\n }\n .error-icon svg {\n width: 32px;\n height: 32px;\n stroke: #FBFBFA;\n }\n h1 {\n font-size: 24px;\n font-weight: 600;\n letter-spacing: -0.02em;\n margin-bottom: 12px;\n color: #1D1D1F;\n }\n .message {\n font-size: 15px;\n color: #6E6E73;\n line-height: 1.5;\n margin-bottom: 8px;\n }\n .hint {\n font-size: 13px;\n color: #86868B;\n margin-top: 24px;\n }\n .divider {\n width: 40px;\n height: 1px;\n background: #E5E5E5;\n margin: 32px auto;\n }\n .retry-link {\n display: inline-block;\n font-size: 14px;\n font-weight: 500;\n color: #1D1D1F;\n text-decoration: none;\n padding: 10px 20px;\n border: 1px solid #E5E5E5;\n border-radius: 8px;\n transition: all 0.2s ease;\n }\n .retry-link:hover {\n background: #F5F5F5;\n border-color: #D5D5D5;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <img src=\"https://app.slima.ai/icons/slima-black.svg\" alt=\"Slima\" class=\"logo\">\n <div class=\"error-icon\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </div>\n <h1>Something went wrong</h1>\n <p class=\"message\">${safeMessage}</p>\n <p class=\"hint\">Please close this window and try again from your terminal.</p>\n <div class=\"divider\"></div>\n <a href=\"javascript:window.close()\" class=\"retry-link\">Close Window</a>\n </div>\n</body>\n</html>`;\n}\n\n/**\n * 執行認證流程\n * @param options.baseUrl - API base URL (default: https://api.slima.ai)\n * @param options.force - Force re-authentication even if already authenticated\n */\nexport async function runAuth(options: { baseUrl?: string; force?: boolean } = {}): Promise<void> {\n const baseUrl = options.baseUrl || process.env.SLIMA_API_URL || DEFAULT_API_URL;\n const forceAuth = options.force || false;\n\n console.log('\\n🔐 Slima MCP Authentication\\n');\n\n // 檢查是否已經認證\n const existingCredentials = await loadToken();\n if (existingCredentials && !forceAuth) {\n console.log('You are already authenticated.');\n console.log(`Token stored at: ${getCredentialsFilePath()}`);\n console.log('\\nTo re-authenticate with a different account, run: slima-mcp auth --force\\n');\n return;\n }\n\n // 如果強制認證,先刪除舊 token\n if (existingCredentials && forceAuth) {\n await deleteToken();\n console.log('Clearing existing credentials for re-authentication...\\n');\n }\n\n const state = generateState();\n const port = await findAvailablePort();\n\n console.log('Starting local server for authentication callback...');\n\n // 建立本地伺服器接收 callback\n const tokenPromise = new Promise<string>((resolve, reject) => {\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || '/', `http://localhost:${port}`);\n\n if (url.pathname === '/callback') {\n const token = url.searchParams.get('token');\n const returnedState = url.searchParams.get('state');\n const error = url.searchParams.get('error');\n\n if (error) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml(error));\n server.close();\n reject(new Error(error));\n return;\n }\n\n if (returnedState !== state) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml('Invalid state parameter. Please try again.'));\n server.close();\n reject(new Error('State mismatch'));\n return;\n }\n\n if (!token) {\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getErrorHtml('No token received. Please try again.'));\n server.close();\n reject(new Error('No token received'));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(getSuccessHtml());\n server.close();\n resolve(token);\n } else {\n res.writeHead(404);\n res.end('Not found');\n }\n });\n\n server.listen(port, '127.0.0.1', () => {\n console.log(`Listening on http://127.0.0.1:${port}`);\n });\n\n // 30 秒超時\n setTimeout(() => {\n server.close();\n reject(new Error('Authentication timed out. Please try again.'));\n }, 30000);\n });\n\n // 建立認證 URL(使用 API endpoint)\n // 加入 prompt=login 強制重新登入(清除 Rails session)\n const promptParam = forceAuth ? '&prompt=login' : '';\n const authUrl = `${baseUrl}/api/v1/auth/cli?state=${state}&callback_port=${port}${promptParam}`;\n\n console.log('\\nOpening browser for authentication...');\n await openBrowser(authUrl);\n\n console.log('\\nWaiting for authentication...');\n console.log('(Press Ctrl+C to cancel)\\n');\n\n try {\n const token = await tokenPromise;\n await saveToken(token, baseUrl !== DEFAULT_API_URL ? baseUrl : undefined);\n\n console.log('\\n✅ Authentication successful!');\n console.log(`Token saved to: ${getCredentialsFilePath()}`);\n console.log('\\nYou can now use Slima MCP with any MCP-compatible AI tool.\\n');\n } catch (error) {\n console.error('\\n❌ Authentication failed:', error instanceof Error ? error.message : error);\n process.exit(1);\n }\n}\n\n/**\n * 登出(刪除 token)\n */\nexport async function runLogout(): Promise<void> {\n console.log('\\n🔓 Slima MCP Logout\\n');\n\n const credentials = await loadToken();\n if (!credentials) {\n console.log('You are not authenticated.\\n');\n return;\n }\n\n await deleteToken();\n console.log('✅ Successfully logged out.');\n console.log(`Removed credentials from: ${getCredentialsFilePath()}\\n`);\n}\n\n/**\n * 顯示目前狀態\n */\nexport async function runStatus(): Promise<void> {\n console.log('\\n📊 Slima MCP Status\\n');\n\n const credentials = await loadToken();\n if (!credentials) {\n console.log('Status: Not authenticated');\n console.log('\\nRun `slima-mcp auth` to authenticate.\\n');\n return;\n }\n\n console.log('Status: Authenticated');\n console.log(`Token: ${credentials.apiToken.substring(0, 15)}...`);\n console.log(`Authenticated at: ${credentials.createdAt}`);\n if (credentials.baseUrl) {\n console.log(`API URL: ${credentials.baseUrl}`);\n }\n console.log(`Credentials file: ${getCredentialsFilePath()}\\n`);\n}\n"],"mappings":"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","names":["json","text","z","z","z","defaultLogger","logger","z","result","z","z","z","z","z","z","z","READ_ONLY","failure","z","z","READ_ONLY","z","json","failure","text"]}
|