@thallylabs/mcp 0.10.24 → 0.10.27

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
@@ -243,19 +243,25 @@ function formatGroup(group, indent) {
243
243
  }
244
244
  return lines;
245
245
  }
246
+ function formatPages(pages, indent) {
247
+ return pages.flatMap((page) => {
248
+ if (typeof page !== "string") return formatGroup(page, indent);
249
+ const href = page === "introduction" ? "/" : `/${page}`;
250
+ return [`${indent}- ${page.padEnd(30)} \u2192 ${href}`];
251
+ });
252
+ }
246
253
  async function handleListPages(input) {
247
254
  const config = readDocsJson(input.projectDir);
248
255
  const lines = [];
249
256
  for (const tab of config.tabs) {
250
257
  lines.push(`Tab: ${tab.tab}`);
251
- if (tab.href) {
258
+ const navigationPages = [...tab.pages ?? [], ...tab.groups ?? []];
259
+ if (tab.href && navigationPages.length === 0) {
252
260
  lines.push(` \u2192 External: ${tab.href}`);
253
- } else if (tab.api) {
261
+ } else if (tab.api && navigationPages.length === 0) {
254
262
  lines.push(` \u2192 API Reference: ${tab.api.source}`);
255
- } else if (tab.groups && tab.groups.length > 0) {
256
- for (const group of tab.groups) {
257
- lines.push(...formatGroup(group, " "));
258
- }
263
+ } else if (navigationPages.length > 0) {
264
+ lines.push(...formatPages(navigationPages, " "));
259
265
  } else {
260
266
  lines.push(" (no pages)");
261
267
  }
@@ -884,8 +890,16 @@ function collectNavPageIds(groups, seen, duplicates) {
884
890
  }
885
891
  function addOrphanToNav(projectDir, pageId) {
886
892
  const config = readDocsJson(projectDir);
887
- const tab = config.tabs.find((t) => !t.href && !t.api && t.groups && t.groups.length > 0);
888
- if (!tab || !tab.groups) return;
893
+ const tab = config.tabs.find((candidate) => !candidate.href && !candidate.api && (candidate.pages?.length || candidate.groups?.length));
894
+ if (!tab) return;
895
+ if (tab.pages) {
896
+ if (!tab.pages.includes(pageId)) {
897
+ tab.pages.push(pageId);
898
+ writeDocsJson(projectDir, config);
899
+ }
900
+ return;
901
+ }
902
+ if (!tab.groups) return;
889
903
  const lastGroup = tab.groups[tab.groups.length - 1];
890
904
  const existing = lastGroup.pages.filter((p) => typeof p === "string");
891
905
  if (!existing.includes(pageId)) {
@@ -904,12 +918,14 @@ async function handleLintProject(input) {
904
918
  const navPageIds = /* @__PURE__ */ new Set();
905
919
  const duplicates = /* @__PURE__ */ new Set();
906
920
  for (const tab of config.tabs) {
907
- if (tab.href || tab.api) continue;
908
- if (!tab.groups || tab.groups.length === 0) {
921
+ const hasNavigationNodes = Boolean(tab.pages?.length || tab.groups?.length);
922
+ if ((tab.href || tab.api) && !hasNavigationNodes) continue;
923
+ if (!hasNavigationNodes) {
909
924
  issues.push({ severity: "error", message: `Tab "${tab.tab}" has no groups and no href \u2014 it will render empty` });
910
925
  continue;
911
926
  }
912
- collectNavPageIds(tab.groups.map((g) => g), navPageIds, duplicates);
927
+ collectNavPageIds(tab.pages ?? [], navPageIds, duplicates);
928
+ collectNavPageIds(tab.groups ?? [], navPageIds, duplicates);
913
929
  }
914
930
  for (const dup of duplicates) {
915
931
  issues.push({ severity: "error", message: `[duplicate] "${dup}" appears more than once in docs.json` });
@@ -1003,7 +1019,7 @@ async function handleLintProject(input) {
1003
1019
 
1004
1020
  // src/tools/translate-docs.ts
1005
1021
  import { z as z14 } from "zod";
1006
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync2 } from "fs";
1022
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync2 } from "fs";
1007
1023
  import { join as join9, dirname as dirname2 } from "path";
1008
1024
  import Anthropic from "@anthropic-ai/sdk";
1009
1025
  import pLimit from "p-limit";
@@ -1036,8 +1052,8 @@ function getAllPageIds(config) {
1036
1052
  const seen = /* @__PURE__ */ new Set();
1037
1053
  const hrefOnlyPages = [];
1038
1054
  for (const tab of config.tabs) {
1039
- if (tab.api && !tab.groups) continue;
1040
- if (!tab.groups && tab.href) {
1055
+ if (tab.api && !tab.pages && !tab.groups) continue;
1056
+ if (!tab.pages && !tab.groups && tab.href) {
1041
1057
  const pageId = tab.href.replace(/^\//, "");
1042
1058
  if (pageId && !seen.has(pageId)) {
1043
1059
  seen.add(pageId);
@@ -1046,13 +1062,10 @@ function getAllPageIds(config) {
1046
1062
  }
1047
1063
  continue;
1048
1064
  }
1049
- if (!tab.groups) continue;
1050
- for (const group of tab.groups) {
1051
- for (const id of collectPageIds(group.pages)) {
1052
- if (!seen.has(id)) {
1053
- seen.add(id);
1054
- ids.push(id);
1055
- }
1065
+ for (const id of collectPageIds([...tab.pages ?? [], ...tab.groups ?? []])) {
1066
+ if (!seen.has(id)) {
1067
+ seen.add(id);
1068
+ ids.push(id);
1056
1069
  }
1057
1070
  }
1058
1071
  }
@@ -1150,7 +1163,7 @@ async function handleTranslateDocs(input) {
1150
1163
  }
1151
1164
  const translated = await translatePage(sourceContent, targetLocale.label, locale, model, client);
1152
1165
  mkdirSync2(dirname2(targetFile), { recursive: true });
1153
- writeFileSync6(targetFile, translated + "\n", "utf8");
1166
+ writeFileSync5(targetFile, translated + "\n", "utf8");
1154
1167
  results.push({ pageId, success: true });
1155
1168
  } catch (err) {
1156
1169
  const msg = err instanceof Error ? err.message : String(err);
@@ -1573,7 +1586,7 @@ async function handleReadApiSpec(input) {
1573
1586
  }
1574
1587
 
1575
1588
  // src/tools/update-api-spec.ts
1576
- import { writeFileSync as writeFileSync7 } from "fs";
1589
+ import { writeFileSync as writeFileSync6 } from "fs";
1577
1590
  import { extname as extname2 } from "path";
1578
1591
  import { z as z17 } from "zod";
1579
1592
  import { parse as parseYaml } from "yaml";
@@ -1599,7 +1612,7 @@ async function handleUpdateApiSpec(input) {
1599
1612
  }
1600
1613
  const content = input.content.endsWith("\n") ? input.content : `${input.content}
1601
1614
  `;
1602
- writeFileSync7(source.path, content, "utf8");
1615
+ writeFileSync6(source.path, content, "utf8");
1603
1616
  return `\u2705 API source updated: ${source.source}`;
1604
1617
  }
1605
1618
 
package/dist/tools.js CHANGED
@@ -234,19 +234,25 @@ function formatGroup(group, indent) {
234
234
  }
235
235
  return lines;
236
236
  }
237
+ function formatPages(pages, indent) {
238
+ return pages.flatMap((page) => {
239
+ if (typeof page !== "string") return formatGroup(page, indent);
240
+ const href = page === "introduction" ? "/" : `/${page}`;
241
+ return [`${indent}- ${page.padEnd(30)} \u2192 ${href}`];
242
+ });
243
+ }
237
244
  async function handleListPages(input) {
238
245
  const config = readDocsJson(input.projectDir);
239
246
  const lines = [];
240
247
  for (const tab of config.tabs) {
241
248
  lines.push(`Tab: ${tab.tab}`);
242
- if (tab.href) {
249
+ const navigationPages = [...tab.pages ?? [], ...tab.groups ?? []];
250
+ if (tab.href && navigationPages.length === 0) {
243
251
  lines.push(` \u2192 External: ${tab.href}`);
244
- } else if (tab.api) {
252
+ } else if (tab.api && navigationPages.length === 0) {
245
253
  lines.push(` \u2192 API Reference: ${tab.api.source}`);
246
- } else if (tab.groups && tab.groups.length > 0) {
247
- for (const group of tab.groups) {
248
- lines.push(...formatGroup(group, " "));
249
- }
254
+ } else if (navigationPages.length > 0) {
255
+ lines.push(...formatPages(navigationPages, " "));
250
256
  } else {
251
257
  lines.push(" (no pages)");
252
258
  }
@@ -875,8 +881,16 @@ function collectNavPageIds(groups, seen, duplicates) {
875
881
  }
876
882
  function addOrphanToNav(projectDir, pageId) {
877
883
  const config = readDocsJson(projectDir);
878
- const tab = config.tabs.find((t) => !t.href && !t.api && t.groups && t.groups.length > 0);
879
- if (!tab || !tab.groups) return;
884
+ const tab = config.tabs.find((candidate) => !candidate.href && !candidate.api && (candidate.pages?.length || candidate.groups?.length));
885
+ if (!tab) return;
886
+ if (tab.pages) {
887
+ if (!tab.pages.includes(pageId)) {
888
+ tab.pages.push(pageId);
889
+ writeDocsJson(projectDir, config);
890
+ }
891
+ return;
892
+ }
893
+ if (!tab.groups) return;
880
894
  const lastGroup = tab.groups[tab.groups.length - 1];
881
895
  const existing = lastGroup.pages.filter((p) => typeof p === "string");
882
896
  if (!existing.includes(pageId)) {
@@ -895,12 +909,14 @@ async function handleLintProject(input) {
895
909
  const navPageIds = /* @__PURE__ */ new Set();
896
910
  const duplicates = /* @__PURE__ */ new Set();
897
911
  for (const tab of config.tabs) {
898
- if (tab.href || tab.api) continue;
899
- if (!tab.groups || tab.groups.length === 0) {
912
+ const hasNavigationNodes = Boolean(tab.pages?.length || tab.groups?.length);
913
+ if ((tab.href || tab.api) && !hasNavigationNodes) continue;
914
+ if (!hasNavigationNodes) {
900
915
  issues.push({ severity: "error", message: `Tab "${tab.tab}" has no groups and no href \u2014 it will render empty` });
901
916
  continue;
902
917
  }
903
- collectNavPageIds(tab.groups.map((g) => g), navPageIds, duplicates);
918
+ collectNavPageIds(tab.pages ?? [], navPageIds, duplicates);
919
+ collectNavPageIds(tab.groups ?? [], navPageIds, duplicates);
904
920
  }
905
921
  for (const dup of duplicates) {
906
922
  issues.push({ severity: "error", message: `[duplicate] "${dup}" appears more than once in docs.json` });
@@ -994,7 +1010,7 @@ async function handleLintProject(input) {
994
1010
 
995
1011
  // src/tools/translate-docs.ts
996
1012
  import { z as z14 } from "zod";
997
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync2 } from "fs";
1013
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, existsSync as existsSync8, mkdirSync as mkdirSync2 } from "fs";
998
1014
  import { join as join9, dirname as dirname2 } from "path";
999
1015
  import Anthropic from "@anthropic-ai/sdk";
1000
1016
  import pLimit from "p-limit";
@@ -1027,8 +1043,8 @@ function getAllPageIds(config) {
1027
1043
  const seen = /* @__PURE__ */ new Set();
1028
1044
  const hrefOnlyPages = [];
1029
1045
  for (const tab of config.tabs) {
1030
- if (tab.api && !tab.groups) continue;
1031
- if (!tab.groups && tab.href) {
1046
+ if (tab.api && !tab.pages && !tab.groups) continue;
1047
+ if (!tab.pages && !tab.groups && tab.href) {
1032
1048
  const pageId = tab.href.replace(/^\//, "");
1033
1049
  if (pageId && !seen.has(pageId)) {
1034
1050
  seen.add(pageId);
@@ -1037,13 +1053,10 @@ function getAllPageIds(config) {
1037
1053
  }
1038
1054
  continue;
1039
1055
  }
1040
- if (!tab.groups) continue;
1041
- for (const group of tab.groups) {
1042
- for (const id of collectPageIds(group.pages)) {
1043
- if (!seen.has(id)) {
1044
- seen.add(id);
1045
- ids.push(id);
1046
- }
1056
+ for (const id of collectPageIds([...tab.pages ?? [], ...tab.groups ?? []])) {
1057
+ if (!seen.has(id)) {
1058
+ seen.add(id);
1059
+ ids.push(id);
1047
1060
  }
1048
1061
  }
1049
1062
  }
@@ -1141,7 +1154,7 @@ async function handleTranslateDocs(input) {
1141
1154
  }
1142
1155
  const translated = await translatePage(sourceContent, targetLocale.label, locale, model, client);
1143
1156
  mkdirSync2(dirname2(targetFile), { recursive: true });
1144
- writeFileSync6(targetFile, translated + "\n", "utf8");
1157
+ writeFileSync5(targetFile, translated + "\n", "utf8");
1145
1158
  results.push({ pageId, success: true });
1146
1159
  } catch (err) {
1147
1160
  const msg = err instanceof Error ? err.message : String(err);
@@ -1564,7 +1577,7 @@ async function handleReadApiSpec(input) {
1564
1577
  }
1565
1578
 
1566
1579
  // src/tools/update-api-spec.ts
1567
- import { writeFileSync as writeFileSync7 } from "fs";
1580
+ import { writeFileSync as writeFileSync6 } from "fs";
1568
1581
  import { extname as extname2 } from "path";
1569
1582
  import { z as z17 } from "zod";
1570
1583
  import { parse as parseYaml } from "yaml";
@@ -1590,7 +1603,7 @@ async function handleUpdateApiSpec(input) {
1590
1603
  }
1591
1604
  const content = input.content.endsWith("\n") ? input.content : `${input.content}
1592
1605
  `;
1593
- writeFileSync7(source.path, content, "utf8");
1606
+ writeFileSync6(source.path, content, "utf8");
1594
1607
  return `\u2705 API source updated: ${source.source}`;
1595
1608
  }
1596
1609
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thallylabs/mcp",
3
- "version": "0.10.24",
3
+ "version": "0.10.27",
4
4
  "description": "MCP server for managing Thally knowledge surfaces and tracing product changes into documentation work.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -38,7 +38,7 @@
38
38
  "dependencies": {
39
39
  "@anthropic-ai/sdk": "^0.36.0",
40
40
  "@modelcontextprotocol/sdk": "^1.15.0",
41
- "create-thally-docs": "0.10.22",
41
+ "create-thally-docs": "0.10.25",
42
42
  "gray-matter": "^4.0.3",
43
43
  "p-limit": "^6.1.0",
44
44
  "yaml": "^2.8.2",