slima-mcp 0.1.14 → 0.2.0

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/dist/index.js CHANGED
@@ -95,16 +95,34 @@ var SlimaApiClient = class {
95
95
  headers: {
96
96
  Authorization: `Bearer ${token}`,
97
97
  "Content-Type": "application/json",
98
- "User-Agent": "slima-mcp-server/0.1.0"
98
+ "User-Agent": `slima-mcp-server/${"0.2.0"}`
99
99
  },
100
100
  body: body ? JSON.stringify(body) : void 0
101
101
  });
102
+ this.handleUpgradeHint(response);
102
103
  if (!response.ok) {
103
104
  await this.handleErrorResponse(response);
104
105
  }
105
106
  const json = await response.json();
106
107
  return json.data;
107
108
  }
109
+ /**
110
+ * Surface the X-Slima-MCP-Upgrade-Hint header (if present) as a warn log.
111
+ * Deduped per (hint, baseUrl) to keep stderr quiet during long sessions.
112
+ * Defensive: mocked Response objects in tests may not carry a full Headers
113
+ * interface, so guard the access rather than hard-crashing the request.
114
+ */
115
+ loggedUpgradeHints = /* @__PURE__ */ new Set();
116
+ handleUpgradeHint(response) {
117
+ const hint = response?.headers?.get?.("X-Slima-MCP-Upgrade-Hint");
118
+ if (!hint) return;
119
+ const key = `${hint}::${this.baseUrl}`;
120
+ if (this.loggedUpgradeHints.has(key)) return;
121
+ this.loggedUpgradeHints.add(key);
122
+ this.logger.warn(
123
+ `[slima-mcp] ${hint} \u2014 upgrade options: npm install -g slima-mcp@latest, or change your Claude Desktop config to "npx -y slima-mcp@0".`
124
+ );
125
+ }
108
126
  /**
109
127
  * Handle error response
110
128
  */
@@ -132,7 +150,7 @@ var SlimaApiClient = class {
132
150
  * List all books
133
151
  */
134
152
  async listBooks() {
135
- return this.request("GET", "/api/v1/books");
153
+ return this.request("GET", "/api/v1/books?status=active");
136
154
  }
137
155
  /**
138
156
  * Get book details
@@ -409,10 +427,30 @@ function formatFileTree(files, indent = 0) {
409
427
  }
410
428
 
411
429
  // src/core/tools/books.ts
430
+ function studioLabel(book) {
431
+ return book.bookType === "script" ? "\u{1F4DD} Script Studio" : "\u{1F4D6} Writing Studio";
432
+ }
433
+ function scriptStudioWriteRulesBlock(book) {
434
+ return `### \u{1F512} Write Restrictions (Script Studio)
435
+
436
+ This book uses **structured schemas**. Via MCP you can:
437
+ - \u2705 **Read** any file (series.json, *.character, *.scene, etc.)
438
+ - \u2705 **Write / Edit / Delete** under \`.script_studio/planning/**/*\` only (free-form reference tree)
439
+ - \u274C **Cannot modify** structured files (\`series.json\`, \`season.json\`, \`episode.json\`, \`*.character\`, \`*.location\`, \`*.scene\`, \`*.storyline\`, \`*.note\`)
440
+ - \u274C **Cannot modify** \`.script_studio/planning/.initialized\` (frontend bootstrap sentinel)
441
+
442
+ To add characters / scenes / storylines, use the Script Studio UI in the Slima app.
443
+
444
+ For full per-book details, read the MCP resource: \`slima://books/${book.token}/schema\`.`;
445
+ }
412
446
  function registerBookTools(server, client) {
413
447
  server.tool(
414
448
  "create_book",
415
- "Create a new book in your Slima library",
449
+ `Create a new **Writing Studio** book in your Slima library.
450
+
451
+ Only Writing Studio (free-form novels / long-form prose) books can be created via MCP.
452
+ To create a **Script Studio** book (screenwriting with structured series/seasons/episodes/scenes),
453
+ please use the Slima app UI \u2014 MCP does not yet support Script Studio scaffolding.`,
416
454
  {
417
455
  title: z.string().describe("Book title (required)"),
418
456
  author_name: z.string().optional().describe("Author name (optional)"),
@@ -468,7 +506,7 @@ You can now add files using create_file with book_token: ${book.token}`
468
506
  }
469
507
  const formatted = books.map((book) => {
470
508
  const words = book.totalWordCount?.toLocaleString() || "0";
471
- return `- **${book.title}** (${book.token})
509
+ return `- **${book.title}** (${book.token}) \u2014 ${studioLabel(book)}
472
510
  Author: ${book.authorName || "N/A"} | Words: ${words}`;
473
511
  }).join("\n\n");
474
512
  return {
@@ -477,7 +515,9 @@ You can now add files using create_file with book_token: ${book.token}`
477
515
  type: "text",
478
516
  text: `Found ${books.length} book(s):
479
517
 
480
- ${formatted}`
518
+ ${formatted}
519
+
520
+ _\u{1F4DD} Script Studio books have write restrictions via MCP (see \`get_book\` or resource \`slima://books/{token}/schema\`). \u{1F4D6} Writing Studio books are fully writable._`
481
521
  }
482
522
  ]
483
523
  };
@@ -499,7 +539,7 @@ ${formatted}`
499
539
  async ({ book_token }) => {
500
540
  try {
501
541
  const book = await client.getBook(book_token);
502
- const text = `# ${book.title}
542
+ const header = `# ${studioLabel(book)} \u2014 ${book.title}
503
543
 
504
544
  - **Token**: ${book.token}
505
545
  - **Author**: ${book.authorName || "N/A"}
@@ -507,8 +547,12 @@ ${formatted}`
507
547
  - **Language**: ${book.language || "N/A"}
508
548
  - **Total Words**: ${book.totalWordCount?.toLocaleString() || "0"}
509
549
  - **Manuscript Words**: ${book.manuscriptWordCount?.toLocaleString() || "0"}
550
+ - **Reference Words**: ${book.referenceWordCount?.toLocaleString() || "0"}
510
551
  - **Created**: ${book.createdAt}
511
552
  - **Updated**: ${book.updatedAt}`;
553
+ const text = book.bookType === "script" ? `${header}
554
+
555
+ ${scriptStudioWriteRulesBlock(book)}` : header;
512
556
  return {
513
557
  content: [{ type: "text", text }]
514
558
  };
@@ -529,7 +573,10 @@ ${formatted}`
529
573
  { readOnlyHint: true, openWorldHint: true },
530
574
  async ({ book_token }) => {
531
575
  try {
532
- const commits = await client.listCommits(book_token, 1);
576
+ const [book, commits] = await Promise.all([
577
+ client.getBook(book_token),
578
+ client.listCommits(book_token, 1)
579
+ ]);
533
580
  if (commits.length === 0) {
534
581
  return {
535
582
  content: [
@@ -543,13 +590,16 @@ ${formatted}`
543
590
  const flatSnapshot = commits[0].filesSnapshot;
544
591
  const nestedTree = buildFileTree(flatSnapshot);
545
592
  const tree = formatFileTree(nestedTree);
593
+ const footer = book.bookType === "script" ? `
594
+
595
+ _\u{1F4DD} 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\`._` : "";
546
596
  return {
547
597
  content: [
548
598
  {
549
599
  type: "text",
550
- text: `File structure for book ${book_token}:
600
+ text: `File structure for ${studioLabel(book)} \u2014 ${book.title} (${book_token}):
551
601
 
552
- ${tree}`
602
+ ${tree}${footer}`
553
603
  }
554
604
  ]
555
605
  };
@@ -586,6 +636,7 @@ ${tree}`
586
636
 
587
637
  - **Total Words**: ${book.totalWordCount?.toLocaleString() || "0"}
588
638
  - **Manuscript Words**: ${book.manuscriptWordCount?.toLocaleString() || "0"}
639
+ - **Reference Words**: ${book.referenceWordCount?.toLocaleString() || "0"}
589
640
  - **Recent Progress**: ${progressText}
590
641
  - **Commits Analyzed**: ${commits.length}
591
642
  - **Language**: ${book.language || "N/A"}`;
@@ -824,15 +875,31 @@ ${formatted}
824
875
  );
825
876
  server.tool(
826
877
  "analyze_chapter",
827
- "Get AI Beta Reader feedback on a chapter from a specific reader persona. This may take a moment to complete.",
878
+ `Get AI Beta Reader feedback on a chapter from a specific reader persona. This may take a moment to complete.
879
+
880
+ **Not supported for Script Studio books** (\`book_type: "script"\`) \u2014 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.`,
828
881
  {
829
- book_token: z3.string().describe("Book token (e.g., bk_abc123)"),
882
+ book_token: z3.string().describe('Book token (e.g., bk_abc123) \u2014 must be a Writing Studio book (book_type: "book").'),
830
883
  file_path: z3.string().describe("Chapter file path (e.g., /chapters/01.md)"),
831
884
  persona_token: z3.string().describe("Persona token (e.g., psn_xxx). Use list_personas to find available personas.")
832
885
  },
833
886
  { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
834
887
  async ({ book_token, file_path, persona_token }) => {
835
888
  try {
889
+ const book = await client.getBook(book_token);
890
+ if (book.bookType === "script") {
891
+ return {
892
+ content: [
893
+ {
894
+ type: "text",
895
+ text: `\u274C analyze_chapter is not available for Script Studio books.
896
+
897
+ Book "${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) \u2014 AI Beta Reader is a Writing Studio feature.`
898
+ }
899
+ ],
900
+ isError: true
901
+ };
902
+ }
836
903
  const commits = await client.listCommits(book_token, 1);
837
904
  if (commits.length === 0) {
838
905
  return {
@@ -1177,6 +1244,18 @@ function searchByName2(files, name) {
1177
1244
 
1178
1245
  // src/core/tools/files.ts
1179
1246
  import { z as z4 } from "zod";
1247
+ var SCRIPT_STUDIO_WRITE_WARNING = `
1248
+ **IMPORTANT \u2014 Script Studio books (\`book_type: "script"\`):**
1249
+ Writes are restricted to \`.script_studio/planning/**/*\` only.
1250
+ Structured files (\`series.json\`, \`*.character\`, \`*.scene\`, \`*.storyline\`, \`*.note\`, \`*.location\`, \`season.json\`, \`episode.json\`) are READ-ONLY via MCP \u2014 structural edits must go through the Script Studio UI.
1251
+ The sentinel file \`.script_studio/planning/.initialized\` is also read-only (frontend bootstrap marker).
1252
+ Attempting to write outside \`planning/\` returns HTTP 400 with code \`INVALID_PATH\`.
1253
+ For per-book details, read resource \`slima://books/{book_token}/schema\`.
1254
+
1255
+ For Writing Studio books (\`book_type: "book"\`), all paths remain writable.
1256
+ Always check \`book_type\` via \`list_books\` or \`get_book\` before writing to an unfamiliar book.`;
1257
+ var PATH_PARAM_HINT = 'File path relative to book root. For Script Studio books: MUST be under ".script_studio/planning/" (e.g., ".script_studio/planning/references/outline.md"). For Writing Studio books: any path (e.g., "characters/protagonist.md").';
1258
+ var SCRIPT_STRUCTURED_EXTENSIONS = ["character", "scene", "storyline", "note", "location"];
1180
1259
  function registerFileTools(server, client) {
1181
1260
  server.tool(
1182
1261
  "read_file",
@@ -1213,10 +1292,11 @@ function registerFileTools(server, client) {
1213
1292
  );
1214
1293
  server.tool(
1215
1294
  "write_file",
1216
- "Replace the entire content of an existing file (creates a new commit)",
1295
+ `Replace the entire content of an existing file (creates a new commit).
1296
+ ${SCRIPT_STUDIO_WRITE_WARNING}`,
1217
1297
  {
1218
1298
  book_token: z4.string().describe("Book token (e.g., bk_abc123)"),
1219
- path: z4.string().describe('File path (e.g., "characters/protagonist.md")'),
1299
+ path: z4.string().describe(PATH_PARAM_HINT),
1220
1300
  content: z4.string().describe("New content to write"),
1221
1301
  commit_message: z4.string().optional().describe("Commit message (optional, auto-generated if not provided)")
1222
1302
  },
@@ -1249,10 +1329,11 @@ Message: ${result.commit.name || result.commit.message}`
1249
1329
  );
1250
1330
  server.tool(
1251
1331
  "edit_file",
1252
- "Edit a file by replacing a specific text segment (search and replace). Use this for precise edits instead of rewriting the entire file.",
1332
+ `Edit a file by replacing a specific text segment (search and replace). Use this for precise edits instead of rewriting the entire file.
1333
+ ${SCRIPT_STUDIO_WRITE_WARNING}`,
1253
1334
  {
1254
1335
  book_token: z4.string().describe("Book token (e.g., bk_abc123)"),
1255
- path: z4.string().describe('File path (e.g., "characters/protagonist.md")'),
1336
+ path: z4.string().describe(PATH_PARAM_HINT),
1256
1337
  old_string: z4.string().describe("The exact text to find and replace (must match exactly, including whitespace)"),
1257
1338
  new_string: z4.string().describe("The new text to replace it with"),
1258
1339
  replace_all: z4.boolean().optional().describe("Replace all occurrences (default: false, replaces only first match)"),
@@ -1324,10 +1405,13 @@ ${old_string.slice(0, 200)}${old_string.length > 200 ? "..." : ""}
1324
1405
  );
1325
1406
  server.tool(
1326
1407
  "create_file",
1327
- "Create a new file in a book (creates a new commit)",
1408
+ `Create a new file in a book (creates a new commit).
1409
+ ${SCRIPT_STUDIO_WRITE_WARNING}`,
1328
1410
  {
1329
1411
  book_token: z4.string().describe("Book token (e.g., bk_abc123)"),
1330
- path: z4.string().describe('File path including parent folders (e.g., "characters/new-character.md")'),
1412
+ path: z4.string().describe(
1413
+ `${PATH_PARAM_HINT} Parent folders are auto-created (mkdir -p behavior).`
1414
+ ),
1331
1415
  content: z4.string().optional().describe("Initial content (optional, defaults to empty)"),
1332
1416
  commit_message: z4.string().optional().describe("Commit message (optional, auto-generated if not provided)")
1333
1417
  },
@@ -1361,10 +1445,11 @@ Message: ${result.commit.name || result.commit.message}`
1361
1445
  );
1362
1446
  server.tool(
1363
1447
  "delete_file",
1364
- "Delete a file from a book (creates a new commit)",
1448
+ `Delete a file from a book (creates a new commit).
1449
+ ${SCRIPT_STUDIO_WRITE_WARNING}`,
1365
1450
  {
1366
1451
  book_token: z4.string().describe("Book token (e.g., bk_abc123)"),
1367
- path: z4.string().describe('File path to delete (e.g., "old-notes.md")'),
1452
+ path: z4.string().describe(PATH_PARAM_HINT),
1368
1453
  commit_message: z4.string().optional().describe("Commit message (optional, auto-generated if not provided)")
1369
1454
  },
1370
1455
  { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
@@ -1395,10 +1480,11 @@ Message: ${result.commit.name || result.commit.message}`
1395
1480
  );
1396
1481
  server.tool(
1397
1482
  "append_to_file",
1398
- "Append content to the end of an existing file (creates a new commit)",
1483
+ `Append content to the end of an existing file (creates a new commit).
1484
+ ${SCRIPT_STUDIO_WRITE_WARNING}`,
1399
1485
  {
1400
1486
  book_token: z4.string().describe("Book token (e.g., bk_abc123)"),
1401
- path: z4.string().describe('File path (e.g., "notes.md")'),
1487
+ path: z4.string().describe(PATH_PARAM_HINT),
1402
1488
  content: z4.string().describe("Content to append"),
1403
1489
  commit_message: z4.string().optional().describe("Commit message (optional, auto-generated if not provided)")
1404
1490
  },
@@ -1431,27 +1517,45 @@ Message: ${result.commit.name || result.commit.message}`
1431
1517
  );
1432
1518
  server.tool(
1433
1519
  "search_content",
1434
- "Search for content across all files in a book",
1520
+ `Search for content across all files in a book.
1521
+
1522
+ **Script Studio behavior:** By default, structured files (\`*.character\`, \`*.scene\`, \`*.storyline\`, \`*.note\`, \`*.location\`, and \`*.json\` under \`.script_studio/\`) are **excluded** from search results \u2014 their raw JSON is noisy and not useful for full-text search.
1523
+ Pass \`include_structured: true\` to search those files too.
1524
+ Writing Studio books are unaffected by this filter.`,
1435
1525
  {
1436
1526
  book_token: z4.string().describe("Book token (e.g., bk_abc123)"),
1437
1527
  query: z4.string().describe('Search query (e.g., "blue eyes" or "protagonist name")'),
1438
1528
  file_types: z4.array(z4.string()).optional().describe('Filter by file types (e.g., ["markdown", "txt"])'),
1439
- limit: z4.number().optional().describe("Maximum number of matches to return (default: 20)")
1529
+ limit: z4.number().optional().describe("Maximum number of matches to return (default: 20)"),
1530
+ include_structured: z4.boolean().optional().describe(
1531
+ "Script Studio only: set to true to include structured files (*.character / *.scene / etc.) in search results. Default: false (excluded)."
1532
+ )
1440
1533
  },
1441
1534
  { readOnlyHint: true, openWorldHint: true },
1442
- async ({ book_token, query, file_types, limit }) => {
1535
+ async ({ book_token, query, file_types, limit, include_structured }) => {
1443
1536
  try {
1537
+ let bookType;
1538
+ if (!include_structured) {
1539
+ try {
1540
+ const book = await client.getBook(book_token);
1541
+ bookType = book.bookType;
1542
+ } catch {
1543
+ }
1544
+ }
1444
1545
  const result = await client.searchFiles(book_token, {
1445
1546
  query,
1446
1547
  fileTypes: file_types,
1447
1548
  limit
1448
1549
  });
1449
- if (result.matches.length === 0) {
1550
+ const shouldFilter = !include_structured && bookType === "script";
1551
+ const filteredMatches = shouldFilter ? result.matches.filter((m) => !isStructuredScriptFile(m.file.path)) : result.matches;
1552
+ if (filteredMatches.length === 0) {
1553
+ const suffix = shouldFilter && result.matches.length > 0 ? ` (${result.matches.length} match(es) in structured files were excluded \u2014 pass include_structured: true to see them)` : "";
1450
1554
  return {
1451
1555
  content: [
1452
1556
  {
1453
1557
  type: "text",
1454
- text: `No matches found for "${query}".`
1558
+ text: `No matches found for "${query}"${suffix}.`
1455
1559
  }
1456
1560
  ]
1457
1561
  };
@@ -1459,10 +1563,17 @@ Message: ${result.commit.name || result.commit.message}`
1459
1563
  const output = [
1460
1564
  `# Search Results for "${query}"`,
1461
1565
  "",
1462
- `Found ${result.matches.length} file(s) with matches:`,
1566
+ `Found ${filteredMatches.length} file(s) with matches:`,
1463
1567
  ""
1464
1568
  ];
1465
- for (const match of result.matches) {
1569
+ if (shouldFilter && filteredMatches.length < result.matches.length) {
1570
+ const excluded = result.matches.length - filteredMatches.length;
1571
+ output.push(
1572
+ `_Note: excluded ${excluded} Script Studio structured file(s). Pass include_structured: true to include them._`
1573
+ );
1574
+ output.push("");
1575
+ }
1576
+ for (const match of filteredMatches) {
1466
1577
  output.push(`## ${match.file.path}`);
1467
1578
  output.push(`Words: ${match.file.wordCount} | Matches: ${match.matchCount}`);
1468
1579
  output.push("");
@@ -1483,6 +1594,105 @@ Message: ${result.commit.name || result.commit.message}`
1483
1594
  }
1484
1595
  );
1485
1596
  }
1597
+ function isStructuredScriptFile(path) {
1598
+ if (!path) return false;
1599
+ const lower = path.toLowerCase();
1600
+ if (lower.startsWith(".script_studio/") && lower.endsWith(".json")) return true;
1601
+ const ext = lower.split(".").pop();
1602
+ if (!ext) return false;
1603
+ return SCRIPT_STRUCTURED_EXTENSIONS.includes(ext);
1604
+ }
1605
+
1606
+ // src/core/resources/book_schema.ts
1607
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
1608
+ var BOOK_SCHEMA_URI_TEMPLATE = "slima://books/{book_token}/schema";
1609
+ function registerBookSchemaResource(server, client) {
1610
+ server.resource(
1611
+ "slima-book-schema",
1612
+ new ResourceTemplate(BOOK_SCHEMA_URI_TEMPLATE, {
1613
+ // Intentionally no `list` callback — books may number in the hundreds, so
1614
+ // we expose only the URI template via `resources/templates/list` and let
1615
+ // clients call `resources/read` with a concrete token.
1616
+ list: void 0
1617
+ }),
1618
+ {
1619
+ description: "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.",
1620
+ mimeType: "application/json"
1621
+ },
1622
+ async (uri, variables) => {
1623
+ const rawToken = variables["book_token"];
1624
+ const bookToken = Array.isArray(rawToken) ? rawToken[0] : rawToken;
1625
+ if (!bookToken) {
1626
+ throw new Error(
1627
+ `Invalid Slima book schema URI: missing book_token in ${uri.toString()}`
1628
+ );
1629
+ }
1630
+ const book = await client.getBook(bookToken);
1631
+ const schema = buildBookSchema(book);
1632
+ return {
1633
+ contents: [
1634
+ {
1635
+ uri: uri.toString(),
1636
+ mimeType: "application/json",
1637
+ text: JSON.stringify(schema, null, 2)
1638
+ }
1639
+ ]
1640
+ };
1641
+ }
1642
+ );
1643
+ }
1644
+ function buildBookSchema(book) {
1645
+ const isScript = book.bookType === "script";
1646
+ return {
1647
+ book_token: book.token,
1648
+ book_title: book.title,
1649
+ book_type: book.bookType ?? "book",
1650
+ studio_label: isScript ? "Script Studio" : "Writing Studio",
1651
+ mcp_capabilities: {
1652
+ // Note: actual reads still go through validate_safe_path!
1653
+ // (blocks absolute paths, traversal, control chars) even though it's
1654
+ // not reflected in this allowlist.
1655
+ readable_paths: "Any path within the book tree (path-traversal and absolute paths still rejected).",
1656
+ writable_paths: isScript ? [".script_studio/planning/**/*"] : ["**/*"],
1657
+ writable_exceptions: isScript ? [".script_studio/planning/.initialized"] : [],
1658
+ readonly_paths: isScript ? [
1659
+ ".script_studio/series.json",
1660
+ ".script_studio/seasons/**/season.json",
1661
+ ".script_studio/seasons/**/episodes/**/episode.json",
1662
+ ".script_studio/seasons/**/scenes/*.scene",
1663
+ ".script_studio/characters/**/*.character",
1664
+ ".script_studio/locations/**/*.location",
1665
+ ".script_studio/storylines/**/*.storyline",
1666
+ ".script_studio/notes/**/*.note",
1667
+ ".script_studio/planning/.initialized"
1668
+ ] : [],
1669
+ reserved_sentinels: isScript ? [".script_studio/planning/.initialized"] : []
1670
+ },
1671
+ structured_file_types: isScript ? {
1672
+ "series.json": "Series metadata (title, genre, logline, theme, synopsis, acts)",
1673
+ "season.json": "Season metadata",
1674
+ "episode.json": "Episode metadata",
1675
+ "*.scene": "Scene content with scene-specific format",
1676
+ "*.character": "Character definition with scope (series / season / episode)",
1677
+ "*.location": "Location definition with scope",
1678
+ "*.storyline": "Storyline with tier and ID linkage",
1679
+ "*.note": "Note with scope"
1680
+ } : {},
1681
+ notes: isScript ? "Structured files require schema-aware edits \u2014 use the Script Studio UI. MCP write access is restricted to planning/ (free-form reference tree) to prevent schema corruption." : "Writing Studio books are free-form. No schema restrictions; any path is writable.",
1682
+ policy_source: "Rails: Mcp::FileOperationService#validate_studio_write_path!"
1683
+ };
1684
+ }
1685
+
1686
+ // src/core/server-instructions.ts
1687
+ var SERVER_INSTRUCTIONS = `Slima is an AI writing IDE with two product lines, distinguished by the \`book_type\` field on every book:
1688
+
1689
+ 1. **Writing Studio** (\`book_type: "book"\`) \u2014 free-form markdown file tree for novels and long-form prose. All paths are writable via MCP.
1690
+
1691
+ 2. **Script Studio** (\`book_type: "script"\`) \u2014 structured schema for screenwriting with series / seasons / episodes / scenes / characters / locations / storylines / notes. Via MCP, only \`.script_studio/planning/**/*\` is writable; all structured files (series.json, *.character, *.scene, *.storyline, *.note, *.location, season.json, episode.json) and the \`.script_studio/planning/.initialized\` sentinel are READ-ONLY. Structural edits must happen through the Slima app UI.
1692
+
1693
+ **Before writing to a book you haven't inspected this session:** call \`list_books\` or \`get_book\` to see \`book_type\`. For per-book write rules, read the resource \`slima://books/{book_token}/schema\`.
1694
+
1695
+ Reads (\`read_file\`, \`search_content\`, \`get_book_structure\`) work the same way for both studio types \u2014 AI coaching, critique, and analysis are fully supported even on Script Studio books.`;
1486
1696
 
1487
1697
  // src/core/utils/logger.ts
1488
1698
  var isDebug = process.env.DEBUG === "true" || process.env.DEBUG === "1";
@@ -1519,14 +1729,20 @@ function createSlimaServer(config) {
1519
1729
  getToken: async () => config.apiToken,
1520
1730
  logger
1521
1731
  });
1522
- const server = new McpServer({
1523
- name: "slima",
1524
- version: "0.1.14"
1525
- });
1732
+ const server = new McpServer(
1733
+ {
1734
+ name: "slima",
1735
+ version: "0.2.0"
1736
+ },
1737
+ {
1738
+ instructions: SERVER_INSTRUCTIONS
1739
+ }
1740
+ );
1526
1741
  registerBookTools(server, client);
1527
1742
  registerContentTools(server, client);
1528
1743
  registerBetaReaderTools(server, client, logger);
1529
1744
  registerFileTools(server, client);
1745
+ registerBookSchemaResource(server, client);
1530
1746
  logger.info("Slima MCP Server initialized");
1531
1747
  logger.info(`API endpoint: ${config.baseUrl || "https://api.slima.ai"}`);
1532
1748
  return server;
@@ -1973,7 +2189,7 @@ async function runStatus() {
1973
2189
  }
1974
2190
 
1975
2191
  // src/cli/index.ts
1976
- var VERSION = "0.1.14";
2192
+ var VERSION = "0.2.0";
1977
2193
  function showHelp() {
1978
2194
  console.log(`
1979
2195
  Slima MCP v${VERSION}