@thallylabs/mcp 0.7.5 → 0.8.1
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 +12 -5
- package/dist/index.js +89 -29
- package/dist/tools.js +89 -29
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# @thallylabs/mcp
|
|
2
2
|
|
|
3
3
|
A [Model Context Protocol](https://modelcontextprotocol.io) server that lets AI
|
|
4
|
-
tools — Claude Code, Claude Desktop, Cursor, Windsurf —
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
tools — Claude Code, Claude Desktop, Cursor, Windsurf — manage
|
|
5
|
+
[Thally](https://github.com/thallylabs/thally) knowledge surfaces through
|
|
6
|
+
natural language. Tools can create, read, update, search, and migrate
|
|
7
|
+
documentation, then trace a product-repository change into reviewable docs
|
|
8
|
+
work.
|
|
7
9
|
|
|
8
10
|
## Setup
|
|
9
11
|
|
|
@@ -28,17 +30,22 @@ Or in a `mcp.json` / client config:
|
|
|
28
30
|
|
|
29
31
|
## Tools
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
15 tools, including:
|
|
32
34
|
|
|
33
35
|
- **Authoring** — `create_project`, `add_page`, `update_page`, `read_page`, `list_pages`, `add_tab`
|
|
34
36
|
- **Context & search** — `get_context`, `search_docs`, `semantic_search` (against a deployed site)
|
|
35
37
|
- **Quality** — `lint_project`, `agent_readiness` (the Agent Readiness Score of a deployed site)
|
|
36
|
-
- **Migration** — `migrate_docs`, `translate_docs`
|
|
38
|
+
- **Migration** — `migrate_docs`, `import_docs`, `translate_docs`
|
|
39
|
+
- **Product changes** — `sync_from_repo`
|
|
37
40
|
|
|
38
41
|
`search_docs`, `read_page`, and `get_context` work against a local project on
|
|
39
42
|
disk; `semantic_search` and `agent_readiness` run against any **deployed** Thally
|
|
40
43
|
site over HTTP.
|
|
41
44
|
|
|
45
|
+
`migrate_docs` always scaffolds a fresh canonical Thally template before it
|
|
46
|
+
imports content. To preserve an existing Thally runtime and import content in
|
|
47
|
+
place, the caller must explicitly choose `import_docs`.
|
|
48
|
+
|
|
42
49
|
## License
|
|
43
50
|
|
|
44
51
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -329,6 +329,34 @@ function writeDocsJson(projectDir, config) {
|
|
|
329
329
|
writeFileSync2(docsPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
// src/lib/page-echo.ts
|
|
333
|
+
var BODY_DELIMITER = "--- MDX body ---";
|
|
334
|
+
function readPageBodyDelimiter() {
|
|
335
|
+
return BODY_DELIMITER;
|
|
336
|
+
}
|
|
337
|
+
function stripEchoedPageHeader(content, pageId) {
|
|
338
|
+
const lines = content.split("\n");
|
|
339
|
+
const delimiterIndex = lines.findIndex((line) => line.trim() === BODY_DELIMITER);
|
|
340
|
+
if (delimiterIndex !== -1 && delimiterIndex <= 6) {
|
|
341
|
+
return lines.slice(delimiterIndex + 1).join("\n").replace(/^\n+/, "");
|
|
342
|
+
}
|
|
343
|
+
const first = lines[0]?.trim() ?? "";
|
|
344
|
+
const second = lines[1]?.trim() ?? "";
|
|
345
|
+
if (first.startsWith("# ") && second === `*${pageId}*`) {
|
|
346
|
+
let index = 2;
|
|
347
|
+
while (index < lines.length && lines[index].trim() === "") index += 1;
|
|
348
|
+
if (lines[index]?.trim().startsWith("> ")) {
|
|
349
|
+
index += 1;
|
|
350
|
+
while (index < lines.length && lines[index].trim() === "") index += 1;
|
|
351
|
+
}
|
|
352
|
+
if (lines[index]?.trim() === "---") {
|
|
353
|
+
index += 1;
|
|
354
|
+
}
|
|
355
|
+
return lines.slice(index).join("\n").replace(/^\n+/, "");
|
|
356
|
+
}
|
|
357
|
+
return content;
|
|
358
|
+
}
|
|
359
|
+
|
|
332
360
|
// src/tools/add-page.ts
|
|
333
361
|
var addPageSchema = z2.object({
|
|
334
362
|
projectDir: z2.string().describe("Path to the Thally project root"),
|
|
@@ -359,7 +387,7 @@ async function handleAddPage(input) {
|
|
|
359
387
|
if (description) {
|
|
360
388
|
frontmatterLines.push(`description: ${description}`);
|
|
361
389
|
}
|
|
362
|
-
const bodyContent = content
|
|
390
|
+
const bodyContent = content ? stripEchoedPageHeader(content, pageId) : `## ${title}
|
|
363
391
|
|
|
364
392
|
Add your content here.`;
|
|
365
393
|
const mdxContent = `---
|
|
@@ -483,7 +511,23 @@ async function handleListPages(input) {
|
|
|
483
511
|
import { z as z5 } from "zod";
|
|
484
512
|
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
485
513
|
import { join as join4 } from "path";
|
|
514
|
+
|
|
515
|
+
// src/lib/frontmatter.ts
|
|
486
516
|
import matter from "gray-matter";
|
|
517
|
+
var FRONTMATTER_OPTIONS = {
|
|
518
|
+
engines: {
|
|
519
|
+
javascript: () => ({}),
|
|
520
|
+
js: () => ({})
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
function parseFrontmatter(raw) {
|
|
524
|
+
return matter(raw, FRONTMATTER_OPTIONS);
|
|
525
|
+
}
|
|
526
|
+
function stringifyFrontmatter(body, data) {
|
|
527
|
+
return matter.stringify(body, data);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/tools/update-page.ts
|
|
487
531
|
var updatePageSchema = z5.object({
|
|
488
532
|
projectDir: z5.string().describe("Path to the Thally project root"),
|
|
489
533
|
pageId: z5.string().describe('Page identifier (e.g. "guides/auth"). No .mdx extension.'),
|
|
@@ -513,15 +557,15 @@ async function handleUpdatePage(input) {
|
|
|
513
557
|
);
|
|
514
558
|
}
|
|
515
559
|
const raw = readFileSync3(filePath, "utf8");
|
|
516
|
-
const parsed =
|
|
560
|
+
const parsed = parseFrontmatter(raw);
|
|
517
561
|
const newFm = { ...parsed.data };
|
|
518
562
|
if (input.title !== void 0) newFm["title"] = input.title;
|
|
519
563
|
if (input.description !== void 0) newFm["description"] = input.description;
|
|
520
564
|
if (input.mergeFrontmatter) {
|
|
521
565
|
Object.assign(newFm, input.mergeFrontmatter);
|
|
522
566
|
}
|
|
523
|
-
const newBody = input.content !== void 0 ? input.content : parsed.content;
|
|
524
|
-
const newContent =
|
|
567
|
+
const newBody = input.content !== void 0 ? stripEchoedPageHeader(input.content, pageId) : parsed.content;
|
|
568
|
+
const newContent = stringifyFrontmatter(newBody.trim(), newFm);
|
|
525
569
|
writeFileSync4(filePath, newContent, "utf8");
|
|
526
570
|
return [
|
|
527
571
|
`\u2705 Page updated: ${filePath}`,
|
|
@@ -535,35 +579,51 @@ async function handleUpdatePage(input) {
|
|
|
535
579
|
// src/tools/migrate-docs.ts
|
|
536
580
|
import { z as z6 } from "zod";
|
|
537
581
|
import { migrateDocs } from "create-thally-docs/migrate";
|
|
538
|
-
var
|
|
582
|
+
var migrationSourceShape = {
|
|
539
583
|
sourceUrl: z6.string().describe("GitHub repository URL or public documentation URL to migrate"),
|
|
540
|
-
projectDir: z6.string().describe("Path for new project or existing project dir"),
|
|
541
|
-
into: z6.boolean().optional().default(false).describe("Migrate into existing project instead of scaffolding"),
|
|
542
584
|
branch: z6.string().optional().describe("Git branch (default: auto-detect)"),
|
|
543
585
|
docsDir: z6.string().optional().describe("Docs subdirectory in repo (default: auto-detect)"),
|
|
544
586
|
apiKey: z6.string().optional().describe("Anthropic API key for non-Markdown file conversion"),
|
|
545
|
-
maxPages: z6.number().int().min(1).max(1e3).optional().describe("Maximum public URL pages to import")
|
|
587
|
+
maxPages: z6.number().int().min(1).max(1e3).optional().describe("Maximum public URL pages to import"),
|
|
588
|
+
platform: z6.enum(["mintlify", "docusaurus"]).optional().describe("Source platform (default: auto-detect)")
|
|
589
|
+
};
|
|
590
|
+
var migrateDocsSchema = z6.object({
|
|
591
|
+
...migrationSourceShape,
|
|
592
|
+
projectDir: z6.string().describe("Path for the new canonical Thally project; the directory must be absent or empty")
|
|
546
593
|
});
|
|
547
|
-
|
|
594
|
+
var importDocsSchema = z6.object({
|
|
595
|
+
...migrationSourceShape,
|
|
596
|
+
projectDir: z6.string().describe("Path to an existing Thally project whose runtime should be preserved")
|
|
597
|
+
});
|
|
598
|
+
async function runMigration(input, isInPlaceImport) {
|
|
548
599
|
const apiKey = input.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
549
|
-
|
|
600
|
+
return migrateDocs({
|
|
550
601
|
sourceUrl: input.sourceUrl,
|
|
551
602
|
projectDir: input.projectDir,
|
|
552
|
-
|
|
603
|
+
// Keep this decision inside the adapter so stale or adversarial callers
|
|
604
|
+
// cannot turn a template-first migration into an in-place mutation.
|
|
605
|
+
into: isInPlaceImport,
|
|
553
606
|
apiKey,
|
|
554
607
|
branch: input.branch,
|
|
555
608
|
docsDir: input.docsDir,
|
|
556
609
|
maxPages: input.maxPages,
|
|
610
|
+
platform: input.platform,
|
|
557
611
|
yes: true
|
|
558
612
|
});
|
|
559
|
-
|
|
613
|
+
}
|
|
614
|
+
async function handleMigrateDocs(input) {
|
|
615
|
+
const result = await runMigration(input, false);
|
|
616
|
+
return `Migration complete! Created a fresh Thally template at ${result.projectDir} and imported ${result.pagesWritten} pages.`;
|
|
617
|
+
}
|
|
618
|
+
async function handleImportDocs(input) {
|
|
619
|
+
const result = await runMigration(input, true);
|
|
620
|
+
return `Import complete! Imported ${result.pagesWritten} pages into the existing Thally project at ${result.projectDir}.`;
|
|
560
621
|
}
|
|
561
622
|
|
|
562
623
|
// src/tools/search-docs.ts
|
|
563
624
|
import { z as z7 } from "zod";
|
|
564
625
|
import { readdirSync as readdirSync2, statSync, readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
565
626
|
import { join as join5, relative, extname } from "path";
|
|
566
|
-
import matter2 from "gray-matter";
|
|
567
627
|
var searchDocsSchema = z7.object({
|
|
568
628
|
projectDir: z7.string().describe("Path to the Thally project root"),
|
|
569
629
|
query: z7.string().describe("Search query"),
|
|
@@ -599,7 +659,7 @@ function scoreFiles(files, contentDir, query) {
|
|
|
599
659
|
} catch {
|
|
600
660
|
continue;
|
|
601
661
|
}
|
|
602
|
-
const { data, content } =
|
|
662
|
+
const { data, content } = parseFrontmatter(raw);
|
|
603
663
|
const title = data.title ?? "";
|
|
604
664
|
const description = data.description ?? "";
|
|
605
665
|
const keywords = data.keywords ?? [];
|
|
@@ -718,7 +778,6 @@ async function handleAgentReadiness(input) {
|
|
|
718
778
|
import { z as z10 } from "zod";
|
|
719
779
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
720
780
|
import { join as join6 } from "path";
|
|
721
|
-
import matter3 from "gray-matter";
|
|
722
781
|
var readPageSchema = z10.object({
|
|
723
782
|
projectDir: z10.string().describe("Path to the Thally project root"),
|
|
724
783
|
pageId: z10.string().describe('Page ID, e.g. "guides/authentication"')
|
|
@@ -741,15 +800,12 @@ async function handleReadPage(input) {
|
|
|
741
800
|
throw new Error(`Page not found: "${pageId}". No file at src/content/${pageId}.mdx`);
|
|
742
801
|
}
|
|
743
802
|
const raw = readFileSync5(filePath, "utf8");
|
|
744
|
-
const { data, content } =
|
|
803
|
+
const { data, content } = parseFrontmatter(raw);
|
|
745
804
|
const title = data.title ?? pageId;
|
|
746
805
|
const description = data.description ?? "";
|
|
747
|
-
const lines = [
|
|
748
|
-
if (description) {
|
|
749
|
-
|
|
750
|
-
lines.push("");
|
|
751
|
-
}
|
|
752
|
-
lines.push("---", "", content.trim());
|
|
806
|
+
const lines = [`id: ${pageId}`, `title: ${title}`];
|
|
807
|
+
if (description) lines.push(`description: ${description}`);
|
|
808
|
+
lines.push("", readPageBodyDelimiter(), "", content.trim());
|
|
753
809
|
return lines.join("\n");
|
|
754
810
|
}
|
|
755
811
|
|
|
@@ -757,7 +813,6 @@ async function handleReadPage(input) {
|
|
|
757
813
|
import { z as z11 } from "zod";
|
|
758
814
|
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
759
815
|
import { join as join7 } from "path";
|
|
760
|
-
import matter4 from "gray-matter";
|
|
761
816
|
var getContextSchema = z11.object({
|
|
762
817
|
projectDir: z11.string().describe("Path to the Thally project root"),
|
|
763
818
|
topic: z11.string().describe("Topic or question to find relevant docs for"),
|
|
@@ -787,7 +842,7 @@ async function handleGetContext(input) {
|
|
|
787
842
|
for (const c of candidates) {
|
|
788
843
|
if (existsSync6(c)) {
|
|
789
844
|
const raw = readFileSync6(c, "utf8");
|
|
790
|
-
const { content: body } =
|
|
845
|
+
const { content: body } = parseFrontmatter(raw);
|
|
791
846
|
content = body.trim();
|
|
792
847
|
break;
|
|
793
848
|
}
|
|
@@ -813,7 +868,6 @@ async function handleGetContext(input) {
|
|
|
813
868
|
import { z as z12 } from "zod";
|
|
814
869
|
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
815
870
|
import { join as join8 } from "path";
|
|
816
|
-
import matter5 from "gray-matter";
|
|
817
871
|
var lintProjectSchema = z12.object({
|
|
818
872
|
projectDir: z12.string().describe("Path to the Thally project root"),
|
|
819
873
|
fix: z12.boolean().optional().default(false).describe("Auto-fix issues where possible (adds orphan pages to nav)")
|
|
@@ -896,7 +950,7 @@ async function handleLintProject(input) {
|
|
|
896
950
|
let content = "";
|
|
897
951
|
try {
|
|
898
952
|
const raw = readFileSync7(filePath, "utf8");
|
|
899
|
-
const parsed =
|
|
953
|
+
const parsed = parseFrontmatter(raw);
|
|
900
954
|
data = parsed.data;
|
|
901
955
|
content = parsed.content;
|
|
902
956
|
} catch {
|
|
@@ -954,7 +1008,6 @@ async function handleLintProject(input) {
|
|
|
954
1008
|
import { z as z13 } from "zod";
|
|
955
1009
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
956
1010
|
import { join as join9, dirname as dirname2 } from "path";
|
|
957
|
-
import matter6 from "gray-matter";
|
|
958
1011
|
import Anthropic from "@anthropic-ai/sdk";
|
|
959
1012
|
import pLimit from "p-limit";
|
|
960
1013
|
var translateDocsSchema = z13.object({
|
|
@@ -1094,7 +1147,7 @@ async function handleTranslateDocs(input) {
|
|
|
1094
1147
|
({ pageId, sourceFile, targetFile }) => limit(async () => {
|
|
1095
1148
|
try {
|
|
1096
1149
|
const sourceContent = readFileSync8(sourceFile, "utf8");
|
|
1097
|
-
const parsed =
|
|
1150
|
+
const parsed = parseFrontmatter(sourceContent);
|
|
1098
1151
|
if (!parsed.data.title) {
|
|
1099
1152
|
console.warn(`[translate] ${pageId}: missing title in frontmatter`);
|
|
1100
1153
|
}
|
|
@@ -1449,11 +1502,18 @@ var tools = [
|
|
|
1449
1502
|
}),
|
|
1450
1503
|
defineTool({
|
|
1451
1504
|
name: "migrate_docs",
|
|
1452
|
-
description: "
|
|
1505
|
+
description: "Create a fresh canonical Thally template, then migrate a GitHub repository or public docs site into it; the target must be new or empty",
|
|
1453
1506
|
scope: "project",
|
|
1454
1507
|
schema: migrateDocsSchema,
|
|
1455
1508
|
handler: handleMigrateDocs
|
|
1456
1509
|
}),
|
|
1510
|
+
defineTool({
|
|
1511
|
+
name: "import_docs",
|
|
1512
|
+
description: "Import content into an existing Thally project without scaffolding; use only when an in-place import is explicitly requested",
|
|
1513
|
+
scope: "project",
|
|
1514
|
+
schema: importDocsSchema,
|
|
1515
|
+
handler: handleImportDocs
|
|
1516
|
+
}),
|
|
1457
1517
|
defineTool({
|
|
1458
1518
|
name: "search_docs",
|
|
1459
1519
|
description: "Search documentation pages by keyword \u2014 returns ranked list of matching pages",
|
package/dist/tools.js
CHANGED
|
@@ -320,6 +320,34 @@ function writeDocsJson(projectDir, config) {
|
|
|
320
320
|
writeFileSync2(docsPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
+
// src/lib/page-echo.ts
|
|
324
|
+
var BODY_DELIMITER = "--- MDX body ---";
|
|
325
|
+
function readPageBodyDelimiter() {
|
|
326
|
+
return BODY_DELIMITER;
|
|
327
|
+
}
|
|
328
|
+
function stripEchoedPageHeader(content, pageId) {
|
|
329
|
+
const lines = content.split("\n");
|
|
330
|
+
const delimiterIndex = lines.findIndex((line) => line.trim() === BODY_DELIMITER);
|
|
331
|
+
if (delimiterIndex !== -1 && delimiterIndex <= 6) {
|
|
332
|
+
return lines.slice(delimiterIndex + 1).join("\n").replace(/^\n+/, "");
|
|
333
|
+
}
|
|
334
|
+
const first = lines[0]?.trim() ?? "";
|
|
335
|
+
const second = lines[1]?.trim() ?? "";
|
|
336
|
+
if (first.startsWith("# ") && second === `*${pageId}*`) {
|
|
337
|
+
let index = 2;
|
|
338
|
+
while (index < lines.length && lines[index].trim() === "") index += 1;
|
|
339
|
+
if (lines[index]?.trim().startsWith("> ")) {
|
|
340
|
+
index += 1;
|
|
341
|
+
while (index < lines.length && lines[index].trim() === "") index += 1;
|
|
342
|
+
}
|
|
343
|
+
if (lines[index]?.trim() === "---") {
|
|
344
|
+
index += 1;
|
|
345
|
+
}
|
|
346
|
+
return lines.slice(index).join("\n").replace(/^\n+/, "");
|
|
347
|
+
}
|
|
348
|
+
return content;
|
|
349
|
+
}
|
|
350
|
+
|
|
323
351
|
// src/tools/add-page.ts
|
|
324
352
|
var addPageSchema = z2.object({
|
|
325
353
|
projectDir: z2.string().describe("Path to the Thally project root"),
|
|
@@ -350,7 +378,7 @@ async function handleAddPage(input) {
|
|
|
350
378
|
if (description) {
|
|
351
379
|
frontmatterLines.push(`description: ${description}`);
|
|
352
380
|
}
|
|
353
|
-
const bodyContent = content
|
|
381
|
+
const bodyContent = content ? stripEchoedPageHeader(content, pageId) : `## ${title}
|
|
354
382
|
|
|
355
383
|
Add your content here.`;
|
|
356
384
|
const mdxContent = `---
|
|
@@ -474,7 +502,23 @@ async function handleListPages(input) {
|
|
|
474
502
|
import { z as z5 } from "zod";
|
|
475
503
|
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
476
504
|
import { join as join4 } from "path";
|
|
505
|
+
|
|
506
|
+
// src/lib/frontmatter.ts
|
|
477
507
|
import matter from "gray-matter";
|
|
508
|
+
var FRONTMATTER_OPTIONS = {
|
|
509
|
+
engines: {
|
|
510
|
+
javascript: () => ({}),
|
|
511
|
+
js: () => ({})
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
function parseFrontmatter(raw) {
|
|
515
|
+
return matter(raw, FRONTMATTER_OPTIONS);
|
|
516
|
+
}
|
|
517
|
+
function stringifyFrontmatter(body, data) {
|
|
518
|
+
return matter.stringify(body, data);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/tools/update-page.ts
|
|
478
522
|
var updatePageSchema = z5.object({
|
|
479
523
|
projectDir: z5.string().describe("Path to the Thally project root"),
|
|
480
524
|
pageId: z5.string().describe('Page identifier (e.g. "guides/auth"). No .mdx extension.'),
|
|
@@ -504,15 +548,15 @@ async function handleUpdatePage(input) {
|
|
|
504
548
|
);
|
|
505
549
|
}
|
|
506
550
|
const raw = readFileSync3(filePath, "utf8");
|
|
507
|
-
const parsed =
|
|
551
|
+
const parsed = parseFrontmatter(raw);
|
|
508
552
|
const newFm = { ...parsed.data };
|
|
509
553
|
if (input.title !== void 0) newFm["title"] = input.title;
|
|
510
554
|
if (input.description !== void 0) newFm["description"] = input.description;
|
|
511
555
|
if (input.mergeFrontmatter) {
|
|
512
556
|
Object.assign(newFm, input.mergeFrontmatter);
|
|
513
557
|
}
|
|
514
|
-
const newBody = input.content !== void 0 ? input.content : parsed.content;
|
|
515
|
-
const newContent =
|
|
558
|
+
const newBody = input.content !== void 0 ? stripEchoedPageHeader(input.content, pageId) : parsed.content;
|
|
559
|
+
const newContent = stringifyFrontmatter(newBody.trim(), newFm);
|
|
516
560
|
writeFileSync4(filePath, newContent, "utf8");
|
|
517
561
|
return [
|
|
518
562
|
`\u2705 Page updated: ${filePath}`,
|
|
@@ -526,35 +570,51 @@ async function handleUpdatePage(input) {
|
|
|
526
570
|
// src/tools/migrate-docs.ts
|
|
527
571
|
import { z as z6 } from "zod";
|
|
528
572
|
import { migrateDocs } from "create-thally-docs/migrate";
|
|
529
|
-
var
|
|
573
|
+
var migrationSourceShape = {
|
|
530
574
|
sourceUrl: z6.string().describe("GitHub repository URL or public documentation URL to migrate"),
|
|
531
|
-
projectDir: z6.string().describe("Path for new project or existing project dir"),
|
|
532
|
-
into: z6.boolean().optional().default(false).describe("Migrate into existing project instead of scaffolding"),
|
|
533
575
|
branch: z6.string().optional().describe("Git branch (default: auto-detect)"),
|
|
534
576
|
docsDir: z6.string().optional().describe("Docs subdirectory in repo (default: auto-detect)"),
|
|
535
577
|
apiKey: z6.string().optional().describe("Anthropic API key for non-Markdown file conversion"),
|
|
536
|
-
maxPages: z6.number().int().min(1).max(1e3).optional().describe("Maximum public URL pages to import")
|
|
578
|
+
maxPages: z6.number().int().min(1).max(1e3).optional().describe("Maximum public URL pages to import"),
|
|
579
|
+
platform: z6.enum(["mintlify", "docusaurus"]).optional().describe("Source platform (default: auto-detect)")
|
|
580
|
+
};
|
|
581
|
+
var migrateDocsSchema = z6.object({
|
|
582
|
+
...migrationSourceShape,
|
|
583
|
+
projectDir: z6.string().describe("Path for the new canonical Thally project; the directory must be absent or empty")
|
|
537
584
|
});
|
|
538
|
-
|
|
585
|
+
var importDocsSchema = z6.object({
|
|
586
|
+
...migrationSourceShape,
|
|
587
|
+
projectDir: z6.string().describe("Path to an existing Thally project whose runtime should be preserved")
|
|
588
|
+
});
|
|
589
|
+
async function runMigration(input, isInPlaceImport) {
|
|
539
590
|
const apiKey = input.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
540
|
-
|
|
591
|
+
return migrateDocs({
|
|
541
592
|
sourceUrl: input.sourceUrl,
|
|
542
593
|
projectDir: input.projectDir,
|
|
543
|
-
|
|
594
|
+
// Keep this decision inside the adapter so stale or adversarial callers
|
|
595
|
+
// cannot turn a template-first migration into an in-place mutation.
|
|
596
|
+
into: isInPlaceImport,
|
|
544
597
|
apiKey,
|
|
545
598
|
branch: input.branch,
|
|
546
599
|
docsDir: input.docsDir,
|
|
547
600
|
maxPages: input.maxPages,
|
|
601
|
+
platform: input.platform,
|
|
548
602
|
yes: true
|
|
549
603
|
});
|
|
550
|
-
|
|
604
|
+
}
|
|
605
|
+
async function handleMigrateDocs(input) {
|
|
606
|
+
const result = await runMigration(input, false);
|
|
607
|
+
return `Migration complete! Created a fresh Thally template at ${result.projectDir} and imported ${result.pagesWritten} pages.`;
|
|
608
|
+
}
|
|
609
|
+
async function handleImportDocs(input) {
|
|
610
|
+
const result = await runMigration(input, true);
|
|
611
|
+
return `Import complete! Imported ${result.pagesWritten} pages into the existing Thally project at ${result.projectDir}.`;
|
|
551
612
|
}
|
|
552
613
|
|
|
553
614
|
// src/tools/search-docs.ts
|
|
554
615
|
import { z as z7 } from "zod";
|
|
555
616
|
import { readdirSync as readdirSync2, statSync, readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
556
617
|
import { join as join5, relative, extname } from "path";
|
|
557
|
-
import matter2 from "gray-matter";
|
|
558
618
|
var searchDocsSchema = z7.object({
|
|
559
619
|
projectDir: z7.string().describe("Path to the Thally project root"),
|
|
560
620
|
query: z7.string().describe("Search query"),
|
|
@@ -590,7 +650,7 @@ function scoreFiles(files, contentDir, query) {
|
|
|
590
650
|
} catch {
|
|
591
651
|
continue;
|
|
592
652
|
}
|
|
593
|
-
const { data, content } =
|
|
653
|
+
const { data, content } = parseFrontmatter(raw);
|
|
594
654
|
const title = data.title ?? "";
|
|
595
655
|
const description = data.description ?? "";
|
|
596
656
|
const keywords = data.keywords ?? [];
|
|
@@ -709,7 +769,6 @@ async function handleAgentReadiness(input) {
|
|
|
709
769
|
import { z as z10 } from "zod";
|
|
710
770
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
711
771
|
import { join as join6 } from "path";
|
|
712
|
-
import matter3 from "gray-matter";
|
|
713
772
|
var readPageSchema = z10.object({
|
|
714
773
|
projectDir: z10.string().describe("Path to the Thally project root"),
|
|
715
774
|
pageId: z10.string().describe('Page ID, e.g. "guides/authentication"')
|
|
@@ -732,15 +791,12 @@ async function handleReadPage(input) {
|
|
|
732
791
|
throw new Error(`Page not found: "${pageId}". No file at src/content/${pageId}.mdx`);
|
|
733
792
|
}
|
|
734
793
|
const raw = readFileSync5(filePath, "utf8");
|
|
735
|
-
const { data, content } =
|
|
794
|
+
const { data, content } = parseFrontmatter(raw);
|
|
736
795
|
const title = data.title ?? pageId;
|
|
737
796
|
const description = data.description ?? "";
|
|
738
|
-
const lines = [
|
|
739
|
-
if (description) {
|
|
740
|
-
|
|
741
|
-
lines.push("");
|
|
742
|
-
}
|
|
743
|
-
lines.push("---", "", content.trim());
|
|
797
|
+
const lines = [`id: ${pageId}`, `title: ${title}`];
|
|
798
|
+
if (description) lines.push(`description: ${description}`);
|
|
799
|
+
lines.push("", readPageBodyDelimiter(), "", content.trim());
|
|
744
800
|
return lines.join("\n");
|
|
745
801
|
}
|
|
746
802
|
|
|
@@ -748,7 +804,6 @@ async function handleReadPage(input) {
|
|
|
748
804
|
import { z as z11 } from "zod";
|
|
749
805
|
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
750
806
|
import { join as join7 } from "path";
|
|
751
|
-
import matter4 from "gray-matter";
|
|
752
807
|
var getContextSchema = z11.object({
|
|
753
808
|
projectDir: z11.string().describe("Path to the Thally project root"),
|
|
754
809
|
topic: z11.string().describe("Topic or question to find relevant docs for"),
|
|
@@ -778,7 +833,7 @@ async function handleGetContext(input) {
|
|
|
778
833
|
for (const c of candidates) {
|
|
779
834
|
if (existsSync6(c)) {
|
|
780
835
|
const raw = readFileSync6(c, "utf8");
|
|
781
|
-
const { content: body } =
|
|
836
|
+
const { content: body } = parseFrontmatter(raw);
|
|
782
837
|
content = body.trim();
|
|
783
838
|
break;
|
|
784
839
|
}
|
|
@@ -804,7 +859,6 @@ async function handleGetContext(input) {
|
|
|
804
859
|
import { z as z12 } from "zod";
|
|
805
860
|
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
806
861
|
import { join as join8 } from "path";
|
|
807
|
-
import matter5 from "gray-matter";
|
|
808
862
|
var lintProjectSchema = z12.object({
|
|
809
863
|
projectDir: z12.string().describe("Path to the Thally project root"),
|
|
810
864
|
fix: z12.boolean().optional().default(false).describe("Auto-fix issues where possible (adds orphan pages to nav)")
|
|
@@ -887,7 +941,7 @@ async function handleLintProject(input) {
|
|
|
887
941
|
let content = "";
|
|
888
942
|
try {
|
|
889
943
|
const raw = readFileSync7(filePath, "utf8");
|
|
890
|
-
const parsed =
|
|
944
|
+
const parsed = parseFrontmatter(raw);
|
|
891
945
|
data = parsed.data;
|
|
892
946
|
content = parsed.content;
|
|
893
947
|
} catch {
|
|
@@ -945,7 +999,6 @@ async function handleLintProject(input) {
|
|
|
945
999
|
import { z as z13 } from "zod";
|
|
946
1000
|
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
947
1001
|
import { join as join9, dirname as dirname2 } from "path";
|
|
948
|
-
import matter6 from "gray-matter";
|
|
949
1002
|
import Anthropic from "@anthropic-ai/sdk";
|
|
950
1003
|
import pLimit from "p-limit";
|
|
951
1004
|
var translateDocsSchema = z13.object({
|
|
@@ -1085,7 +1138,7 @@ async function handleTranslateDocs(input) {
|
|
|
1085
1138
|
({ pageId, sourceFile, targetFile }) => limit(async () => {
|
|
1086
1139
|
try {
|
|
1087
1140
|
const sourceContent = readFileSync8(sourceFile, "utf8");
|
|
1088
|
-
const parsed =
|
|
1141
|
+
const parsed = parseFrontmatter(sourceContent);
|
|
1089
1142
|
if (!parsed.data.title) {
|
|
1090
1143
|
console.warn(`[translate] ${pageId}: missing title in frontmatter`);
|
|
1091
1144
|
}
|
|
@@ -1440,11 +1493,18 @@ var tools = [
|
|
|
1440
1493
|
}),
|
|
1441
1494
|
defineTool({
|
|
1442
1495
|
name: "migrate_docs",
|
|
1443
|
-
description: "
|
|
1496
|
+
description: "Create a fresh canonical Thally template, then migrate a GitHub repository or public docs site into it; the target must be new or empty",
|
|
1444
1497
|
scope: "project",
|
|
1445
1498
|
schema: migrateDocsSchema,
|
|
1446
1499
|
handler: handleMigrateDocs
|
|
1447
1500
|
}),
|
|
1501
|
+
defineTool({
|
|
1502
|
+
name: "import_docs",
|
|
1503
|
+
description: "Import content into an existing Thally project without scaffolding; use only when an in-place import is explicitly requested",
|
|
1504
|
+
scope: "project",
|
|
1505
|
+
schema: importDocsSchema,
|
|
1506
|
+
handler: handleImportDocs
|
|
1507
|
+
}),
|
|
1448
1508
|
defineTool({
|
|
1449
1509
|
name: "search_docs",
|
|
1450
1510
|
description: "Search documentation pages by keyword \u2014 returns ranked list of matching pages",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thallylabs/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for
|
|
3
|
+
"version": "0.8.1",
|
|
4
|
+
"description": "MCP server for managing Thally knowledge surfaces and tracing product changes into documentation work.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=18"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@anthropic-ai/sdk": "^0.36.0",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
37
|
-
"create-thally-docs": "0.
|
|
37
|
+
"create-thally-docs": "0.8.0",
|
|
38
38
|
"gray-matter": "^4.0.3",
|
|
39
39
|
"p-limit": "^6.1.0",
|
|
40
40
|
"tar": "^6.2.0",
|