@imenam/mcp-documentor 1.0.55 → 1.0.57

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.
Files changed (64) hide show
  1. package/README.md +70 -0
  2. package/dist/shared/read-only.d.ts +4 -0
  3. package/dist/shared/read-only.js +14 -0
  4. package/dist/src/core/git-sync.d.ts +56 -0
  5. package/dist/src/core/git-sync.js +215 -0
  6. package/dist/src/core/storage-engine.d.ts +22 -0
  7. package/dist/src/core/storage-engine.js +62 -3
  8. package/dist/src/gui-worker.js +18 -0
  9. package/dist/src/index.js +450 -332
  10. package/dist/src/tools/sync.d.ts +17 -0
  11. package/dist/src/tools/sync.js +113 -0
  12. package/dist/src/version.d.ts +1 -1
  13. package/dist/src/version.js +1 -1
  14. package/gui/dist/assets/{arc-CCHCB-wD.js → arc-hl-9jVa3.js} +1 -1
  15. package/gui/dist/assets/{architectureDiagram-3BPJPVTR-CY9HTuIq.js → architectureDiagram-3BPJPVTR-Cv0e-QlI.js} +1 -1
  16. package/gui/dist/assets/{blockDiagram-GPEHLZMM-BOVxufIP.js → blockDiagram-GPEHLZMM-DmqN7ioQ.js} +1 -1
  17. package/gui/dist/assets/{c4Diagram-AAUBKEIU-D4gw0Vvh.js → c4Diagram-AAUBKEIU-CCmx3y3D.js} +1 -1
  18. package/gui/dist/assets/channel-Be8bJJxH.js +1 -0
  19. package/gui/dist/assets/{chunk-2J33WTMH-D9MZap0G.js → chunk-2J33WTMH-DkiBzADG.js} +1 -1
  20. package/gui/dist/assets/{chunk-4BX2VUAB-DZp7afnZ.js → chunk-4BX2VUAB-DNbC5-R6.js} +1 -1
  21. package/gui/dist/assets/{chunk-55IACEB6--z45PUIk.js → chunk-55IACEB6-BDo8xvgP.js} +1 -1
  22. package/gui/dist/assets/{chunk-727SXJPM-BpzsGHwS.js → chunk-727SXJPM-BXbVbdMj.js} +1 -1
  23. package/gui/dist/assets/{chunk-AQP2D5EJ-BHrif5nf.js → chunk-AQP2D5EJ-BYle6aB3.js} +1 -1
  24. package/gui/dist/assets/{chunk-FMBD7UC4-BJnvL7mL.js → chunk-FMBD7UC4-BdIrLn1p.js} +1 -1
  25. package/gui/dist/assets/{chunk-ND2GUHAM-B30rNlK9.js → chunk-ND2GUHAM-BkjdsZf2.js} +1 -1
  26. package/gui/dist/assets/{chunk-QZHKN3VN-C30k0U0w.js → chunk-QZHKN3VN-pwVXVLDb.js} +1 -1
  27. package/gui/dist/assets/classDiagram-4FO5ZUOK-DxtDCVWj.js +1 -0
  28. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-DxtDCVWj.js +1 -0
  29. package/gui/dist/assets/{cose-bilkent-S5V4N54A-D_ZPLp5P.js → cose-bilkent-S5V4N54A-COfRIcXU.js} +1 -1
  30. package/gui/dist/assets/{dagre-BM42HDAG-2XyC0YBj.js → dagre-BM42HDAG-BnD4nd21.js} +1 -1
  31. package/gui/dist/assets/{diagram-2AECGRRQ-fNv6yQF_.js → diagram-2AECGRRQ-E9oTEn4C.js} +1 -1
  32. package/gui/dist/assets/{diagram-5GNKFQAL-BUlxr0NW.js → diagram-5GNKFQAL-oSJwag8_.js} +1 -1
  33. package/gui/dist/assets/{diagram-KO2AKTUF-CtusNGfh.js → diagram-KO2AKTUF-B5ycmD2n.js} +1 -1
  34. package/gui/dist/assets/{diagram-LMA3HP47-Dkq3FOEe.js → diagram-LMA3HP47-DUo9wv_e.js} +1 -1
  35. package/gui/dist/assets/{diagram-OG6HWLK6-rjhcMJNr.js → diagram-OG6HWLK6-C-0r9-AW.js} +1 -1
  36. package/gui/dist/assets/{erDiagram-TEJ5UH35-Bqmogkqv.js → erDiagram-TEJ5UH35-C8w0nNRI.js} +1 -1
  37. package/gui/dist/assets/{flowDiagram-I6XJVG4X-BcYYq0Bh.js → flowDiagram-I6XJVG4X-CPZzt4CO.js} +1 -1
  38. package/gui/dist/assets/{ganttDiagram-6RSMTGT7-BGmJltMK.js → ganttDiagram-6RSMTGT7-B0TRw-bC.js} +1 -1
  39. package/gui/dist/assets/{gitGraphDiagram-PVQCEYII-JIYjcd0T.js → gitGraphDiagram-PVQCEYII-CxuhZqVM.js} +1 -1
  40. package/gui/dist/assets/{index-BwW5YDD1.js → index-84YuA9I8.js} +75 -75
  41. package/gui/dist/assets/{infoDiagram-5YYISTIA-CJKrfqe4.js → infoDiagram-5YYISTIA-qLgkATyo.js} +1 -1
  42. package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-Bu4wBvlL.js → ishikawaDiagram-YF4QCWOH-Bv8EcedY.js} +1 -1
  43. package/gui/dist/assets/{journeyDiagram-JHISSGLW-B2CtojGV.js → journeyDiagram-JHISSGLW-CO1wpi9j.js} +1 -1
  44. package/gui/dist/assets/{kanban-definition-UN3LZRKU-CKbuCL_h.js → kanban-definition-UN3LZRKU-INpnb6ij.js} +1 -1
  45. package/gui/dist/assets/{linear-BdHWi7h7.js → linear-yh3g20kh.js} +1 -1
  46. package/gui/dist/assets/{mindmap-definition-RKZ34NQL-Dbf71zzi.js → mindmap-definition-RKZ34NQL-BzSJP5cs.js} +1 -1
  47. package/gui/dist/assets/{pieDiagram-4H26LBE5-P8RSqdBA.js → pieDiagram-4H26LBE5-DH9i355H.js} +1 -1
  48. package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-mVsJmQan.js → quadrantDiagram-W4KKPZXB-CNmW8Mck.js} +1 -1
  49. package/gui/dist/assets/{requirementDiagram-4Y6WPE33-BfHNLWFb.js → requirementDiagram-4Y6WPE33-DIxitFst.js} +1 -1
  50. package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-CF28kVcG.js → sankeyDiagram-5OEKKPKP-BI4KHeTZ.js} +1 -1
  51. package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-Bvs2r29Y.js → sequenceDiagram-3UESZ5HK-CbMtHFNi.js} +1 -1
  52. package/gui/dist/assets/{stateDiagram-AJRCARHV-Cbg-3kIx.js → stateDiagram-AJRCARHV-DWXkOhjJ.js} +1 -1
  53. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-Dv9pwdh3.js +1 -0
  54. package/gui/dist/assets/{timeline-definition-PNZ67QCA-ChYm8r8v.js → timeline-definition-PNZ67QCA-DGsBQPbg.js} +1 -1
  55. package/gui/dist/assets/{vennDiagram-CIIHVFJN-CPcYOWG6.js → vennDiagram-CIIHVFJN-Dw5U_iYl.js} +1 -1
  56. package/gui/dist/assets/{wardley-L42UT6IY-KWdiWaFE.js → wardley-L42UT6IY-DGOHyaD_.js} +1 -1
  57. package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-DAgBnDcZ.js → wardleyDiagram-YWT4CUSO-DpOyo37L.js} +1 -1
  58. package/gui/dist/assets/{xychartDiagram-2RQKCTM6-DwOk_srp.js → xychartDiagram-2RQKCTM6-BM5vPGiE.js} +1 -1
  59. package/gui/dist/index.html +1 -1
  60. package/package.json +2 -1
  61. package/gui/dist/assets/channel-V0oafPxe.js +0 -1
  62. package/gui/dist/assets/classDiagram-4FO5ZUOK-BZlXnsXv.js +0 -1
  63. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-BZlXnsXv.js +0 -1
  64. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-Cg1sS-MT.js +0 -1
package/dist/src/index.js CHANGED
@@ -39,6 +39,9 @@ import { prepareExportBundle } from "./tools/prepare-export-bundle.js";
39
39
  import { deleteExportBundle } from "./tools/delete-export-bundle.js";
40
40
  import { uploadImage } from "./tools/upload-image.js";
41
41
  import { getImage } from "./tools/get-image.js";
42
+ import { SyncEngine } from "./core/git-sync.js";
43
+ import { syncStatus, pullDocs, pushDocs, syncDocs, resolveSyncConflict } from "./tools/sync.js";
44
+ import { isReadOnly, WRITE_TOOLS } from "../shared/read-only.js";
42
45
  const __filename = fileURLToPath(import.meta.url);
43
46
  const __dirname = path.dirname(__filename);
44
47
  function findProjectRoot(startDir) {
@@ -74,6 +77,7 @@ embeddingStore.init(storageEngine.getDataDir(), openaiModel);
74
77
  const historyStore = new HistoryStore();
75
78
  historyStore.init(storageEngine.getDataDir());
76
79
  storageEngine.setHistoryStore(historyStore);
80
+ const syncEngine = new SyncEngine(storageEngine);
77
81
  const server = new Server({ name: "mcp-documentor", version: APP_VERSION }, { capabilities: { tools: {} } });
78
82
  let guiLauncher;
79
83
  const sseControllers = new Set();
@@ -162,7 +166,7 @@ async function handleGetInitialState(msg) {
162
166
  broadcastToGUI({
163
167
  type: "INITIAL_STATE",
164
168
  correlationId: msg.correlationId,
165
- data: { config, docs, projects: config.projects, bundles },
169
+ data: { config, docs, projects: config.projects, bundles, readOnly: isReadOnly() },
166
170
  timestamp: new Date().toISOString(),
167
171
  });
168
172
  }
@@ -175,7 +179,7 @@ async function handleGetConfig(msg) {
175
179
  broadcastToGUI({
176
180
  type: "CONFIG_RESPONSE",
177
181
  correlationId: msg.correlationId,
178
- data: { ...config, version: getConfig().version },
182
+ data: { ...config, version: getConfig().version, readOnly: isReadOnly() },
179
183
  timestamp: new Date().toISOString(),
180
184
  });
181
185
  }
@@ -302,6 +306,15 @@ async function handleListFoldersIPC(msg) {
302
306
  }
303
307
  }
304
308
  async function handleCreateDocIPC(msg) {
309
+ if (isReadOnly()) {
310
+ broadcastToGUI({
311
+ type: "CREATE_DOC_RESPONSE",
312
+ correlationId: msg.correlationId,
313
+ error: "READ_ONLY mode: creation disabled",
314
+ timestamp: new Date().toISOString(),
315
+ });
316
+ return;
317
+ }
305
318
  try {
306
319
  const { title, content, folder, tags, project } = msg.data ?? {};
307
320
  if (!title || !content)
@@ -356,6 +369,15 @@ async function handleCreateDocIPC(msg) {
356
369
  }
357
370
  }
358
371
  async function handleUpdateDocIPC(msg) {
372
+ if (isReadOnly()) {
373
+ broadcastToGUI({
374
+ type: "UPDATE_DOC_RESPONSE",
375
+ correlationId: msg.correlationId,
376
+ error: "READ_ONLY mode: update disabled",
377
+ timestamp: new Date().toISOString(),
378
+ });
379
+ return;
380
+ }
359
381
  try {
360
382
  const result = await updateDoc(storageEngine, embeddingEngine, {
361
383
  path: msg.data.id,
@@ -516,6 +538,15 @@ async function handleGitDiff(msg) {
516
538
  }
517
539
  }
518
540
  async function handleUpdateConfig(msg) {
541
+ if (isReadOnly()) {
542
+ broadcastToGUI({
543
+ type: "CONFIG_UPDATED",
544
+ correlationId: msg.correlationId,
545
+ error: "READ_ONLY mode: configuration changes disabled",
546
+ timestamp: new Date().toISOString(),
547
+ });
548
+ return;
549
+ }
519
550
  try {
520
551
  const { key, value } = msg.data;
521
552
  const envPath = path.join(rootDir, ".env");
@@ -611,403 +642,452 @@ function launchGUI() {
611
642
  guiLauncher.start();
612
643
  }
613
644
  server.setRequestHandler(ListToolsRequestSchema, async () => {
614
- return {
615
- tools: [
616
- {
617
- name: "create_doc",
618
- description: "Create a new documentation entry with markdown content, optional tracked files, and tags.",
619
- inputSchema: {
620
- type: "object",
621
- properties: {
622
- title: { type: "string", description: "Title of the documentation entry" },
623
- content: { type: "string", description: "Documentation content in markdown. To link to another document inline, use the syntax [text](doc://target-doc-id) — the viewer will navigate to that document on click. To link to a heading within the same document, use [text](#heading-slug). Use check_links to validate both types. For code blocks, always specify a language identifier for syntax highlighting, e.g. ```typescript, ```python, ```bash, ```json, ```yaml, ```sql. To embed a code excerpt directly from a source file, use [copy-from-codebase|/absolute/path/to/file.ts|startLine|endLine] — the tag is replaced at save time by a fenced code block with automatic language detection, and the file+range is automatically added to tracked_files. IMPORTANT: always read the source file first to identify the exact line numbers before writing the tag." },
624
- content_file: { type: "string", description: "Absolute path to a text file whose content will be used instead of the content parameter. Use this to reuse a previously persisted draft (path is returned in the error message when a previous call failed)." },
625
- tracked_files: {
626
- type: "array",
627
- items: {
628
- oneOf: [
629
- { type: "string", description: "Plain file path for whole-file tracking" },
630
- {
631
- type: "object",
632
- properties: {
633
- path: { type: "string", description: "File path to track" },
634
- lines: {
635
- type: "array",
636
- items: {
637
- type: "object",
638
- properties: {
639
- start: { type: "integer", minimum: 1, description: "First line of the section (1-indexed, inclusive)" },
640
- end: { type: "integer", minimum: 1, description: "Last line of the section (1-indexed, inclusive)" },
641
- },
642
- required: ["start", "end"],
645
+ const tools = [
646
+ {
647
+ name: "create_doc",
648
+ description: "Create a new documentation entry with markdown content, optional tracked files, and tags.",
649
+ inputSchema: {
650
+ type: "object",
651
+ properties: {
652
+ title: { type: "string", description: "Title of the documentation entry" },
653
+ content: { type: "string", description: "Documentation content in markdown. To link to another document inline, use the syntax [text](doc://target-doc-id) — the viewer will navigate to that document on click. To link to a heading within the same document, use [text](#heading-slug). Use check_links to validate both types. For code blocks, always specify a language identifier for syntax highlighting, e.g. ```typescript, ```python, ```bash, ```json, ```yaml, ```sql. To embed a code excerpt directly from a source file, use [copy-from-codebase|/absolute/path/to/file.ts|startLine|endLine] — the tag is replaced at save time by a fenced code block with automatic language detection, and the file+range is automatically added to tracked_files. IMPORTANT: always read the source file first to identify the exact line numbers before writing the tag." },
654
+ content_file: { type: "string", description: "Absolute path to a text file whose content will be used instead of the content parameter. Use this to reuse a previously persisted draft (path is returned in the error message when a previous call failed)." },
655
+ tracked_files: {
656
+ type: "array",
657
+ items: {
658
+ oneOf: [
659
+ { type: "string", description: "Plain file path for whole-file tracking" },
660
+ {
661
+ type: "object",
662
+ properties: {
663
+ path: { type: "string", description: "File path to track" },
664
+ lines: {
665
+ type: "array",
666
+ items: {
667
+ type: "object",
668
+ properties: {
669
+ start: { type: "integer", minimum: 1, description: "First line of the section (1-indexed, inclusive)" },
670
+ end: { type: "integer", minimum: 1, description: "Last line of the section (1-indexed, inclusive)" },
643
671
  },
644
- minItems: 1,
645
- description: "One or more line ranges to track within the file",
672
+ required: ["start", "end"],
646
673
  },
674
+ minItems: 1,
675
+ description: "One or more line ranges to track within the file",
647
676
  },
648
- required: ["path", "lines"],
649
- description: "Granular section tracking: only the specified line ranges are monitored",
650
677
  },
651
- ],
652
- },
653
- description: "Files to track for staleness detection. Use a plain path string for whole-file tracking, or { path, lines: [{ start, end }] } to track specific sections only (reduces false positives when unrelated parts of the file change).",
654
- },
655
- tags: {
656
- type: "array",
657
- items: { type: "string" },
658
- description: "Tags for categorization",
678
+ required: ["path", "lines"],
679
+ description: "Granular section tracking: only the specified line ranges are monitored",
680
+ },
681
+ ],
659
682
  },
660
- folder: { type: "string", description: "Folder path for organization, e.g. 'frontend/components'" },
661
- related_docs: {
662
- type: "array",
663
- items: { type: "string" },
664
- description: "IDs of related documentation entries",
665
- },
666
- project: { type: "string", description: "Project to create the doc in (defaults to current project if omitted). If the project name does not exist yet, it will be created automatically." },
683
+ description: "Files to track for staleness detection. Use a plain path string for whole-file tracking, or { path, lines: [{ start, end }] } to track specific sections only (reduces false positives when unrelated parts of the file change).",
684
+ },
685
+ tags: {
686
+ type: "array",
687
+ items: { type: "string" },
688
+ description: "Tags for categorization",
667
689
  },
668
- required: ["title"],
690
+ folder: { type: "string", description: "Folder path for organization, e.g. 'frontend/components'" },
691
+ related_docs: {
692
+ type: "array",
693
+ items: { type: "string" },
694
+ description: "IDs of related documentation entries",
695
+ },
696
+ project: { type: "string", description: "Project to create the doc in (defaults to current project if omitted). If the project name does not exist yet, it will be created automatically." },
669
697
  },
698
+ required: ["title"],
670
699
  },
671
- {
672
- name: "get_doc",
673
- description: "Retrieve a documentation entry by its path, including full content, tracked files, and metadata.",
674
- inputSchema: {
675
- type: "object",
676
- properties: {
677
- path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication' or 'authentication' for root docs" },
678
- },
679
- required: ["path"],
700
+ },
701
+ {
702
+ name: "get_doc",
703
+ description: "Retrieve a documentation entry by its path, including full content, tracked files, and metadata.",
704
+ inputSchema: {
705
+ type: "object",
706
+ properties: {
707
+ path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication' or 'authentication' for root docs" },
680
708
  },
709
+ required: ["path"],
681
710
  },
682
- {
683
- name: "list_docs",
684
- description: "List all documentation entries in the current project with staleness indicators.",
685
- inputSchema: {
686
- type: "object",
687
- properties: {
688
- project: { type: "string", description: "Project to list (lists all projects if omitted)" },
689
- },
711
+ },
712
+ {
713
+ name: "list_docs",
714
+ description: "List all documentation entries in the current project with staleness indicators.",
715
+ inputSchema: {
716
+ type: "object",
717
+ properties: {
718
+ project: { type: "string", description: "Project to list (lists all projects if omitted)" },
690
719
  },
691
720
  },
692
- {
693
- name: "update_doc",
694
- description: "Fully replace the content and/or metadata of a documentation entry. WARNING: This overwrites the entire content. Prefer patch_doc for targeted edits — only use update_doc when the changes are so extensive that a patch is impractical. To move a doc to another folder, use move_doc instead.",
695
- inputSchema: {
696
- type: "object",
697
- properties: {
698
- path: { type: "string", description: "Path of the documentation entry to update, e.g. 'backend/auth/authentication' or 'authentication' for root docs" },
699
- title: {
700
- type: "string",
701
- description: "New display title stored in frontmatter. Does not rename the file or change the document path — use move_doc to relocate or rename on disk.",
702
- },
703
- content: { type: "string", description: "New content (replaces existing). Supports [text](doc://target-doc-path) to link inline to other documents, and [text](#heading-slug) to link to headings within this document. For code blocks, always specify a language identifier for syntax highlighting, e.g. ```typescript, ```python, ```bash, ```json, ```yaml, ```sql. To embed a code excerpt directly from a source file, use [copy-from-codebase|/absolute/path/to/file.ts|startLine|endLine] — the tag is replaced at save time by a fenced code block with automatic language detection, and the file+range is automatically added to tracked_files. IMPORTANT: always read the source file first to identify the exact line numbers before writing the tag." },
704
- content_file: { type: "string", description: "Absolute path to a text file whose content will be used instead of the content parameter. Use this to reuse a previously persisted draft (path is returned in the error message when a previous call failed)." },
705
- tracked_files: {
706
- type: "array",
707
- items: {
708
- oneOf: [
709
- { type: "string", description: "Plain file path for whole-file tracking" },
710
- {
711
- type: "object",
712
- properties: {
713
- path: { type: "string", description: "File path to track" },
714
- lines: {
715
- type: "array",
716
- items: {
717
- type: "object",
718
- properties: {
719
- start: { type: "integer", minimum: 1, description: "First line of the section (1-indexed, inclusive)" },
720
- end: { type: "integer", minimum: 1, description: "Last line of the section (1-indexed, inclusive)" },
721
- },
722
- required: ["start", "end"],
721
+ },
722
+ {
723
+ name: "update_doc",
724
+ description: "Fully replace the content and/or metadata of a documentation entry. WARNING: This overwrites the entire content. Prefer patch_doc for targeted edits — only use update_doc when the changes are so extensive that a patch is impractical. To move a doc to another folder, use move_doc instead.",
725
+ inputSchema: {
726
+ type: "object",
727
+ properties: {
728
+ path: { type: "string", description: "Path of the documentation entry to update, e.g. 'backend/auth/authentication' or 'authentication' for root docs" },
729
+ title: {
730
+ type: "string",
731
+ description: "New display title stored in frontmatter. Does not rename the file or change the document path — use move_doc to relocate or rename on disk.",
732
+ },
733
+ content: { type: "string", description: "New content (replaces existing). Supports [text](doc://target-doc-path) to link inline to other documents, and [text](#heading-slug) to link to headings within this document. For code blocks, always specify a language identifier for syntax highlighting, e.g. ```typescript, ```python, ```bash, ```json, ```yaml, ```sql. To embed a code excerpt directly from a source file, use [copy-from-codebase|/absolute/path/to/file.ts|startLine|endLine] — the tag is replaced at save time by a fenced code block with automatic language detection, and the file+range is automatically added to tracked_files. IMPORTANT: always read the source file first to identify the exact line numbers before writing the tag." },
734
+ content_file: { type: "string", description: "Absolute path to a text file whose content will be used instead of the content parameter. Use this to reuse a previously persisted draft (path is returned in the error message when a previous call failed)." },
735
+ tracked_files: {
736
+ type: "array",
737
+ items: {
738
+ oneOf: [
739
+ { type: "string", description: "Plain file path for whole-file tracking" },
740
+ {
741
+ type: "object",
742
+ properties: {
743
+ path: { type: "string", description: "File path to track" },
744
+ lines: {
745
+ type: "array",
746
+ items: {
747
+ type: "object",
748
+ properties: {
749
+ start: { type: "integer", minimum: 1, description: "First line of the section (1-indexed, inclusive)" },
750
+ end: { type: "integer", minimum: 1, description: "Last line of the section (1-indexed, inclusive)" },
723
751
  },
724
- minItems: 1,
725
- description: "One or more line ranges to track within the file",
752
+ required: ["start", "end"],
726
753
  },
754
+ minItems: 1,
755
+ description: "One or more line ranges to track within the file",
727
756
  },
728
- required: ["path", "lines"],
729
- description: "Granular section tracking: only the specified line ranges are monitored",
730
757
  },
731
- ],
732
- },
733
- description: "New list of tracked files (replaces existing). Use a plain path string for whole-file tracking, or { path, lines: [{ start, end }] } to track specific sections only (reduces false positives when unrelated parts of the file change).",
734
- },
735
- tags: {
736
- type: "array",
737
- items: { type: "string" },
738
- description: "New tags (replaces existing)",
739
- },
740
- related_docs: {
741
- type: "array",
742
- items: { type: "string" },
743
- description: "Paths of related documentation entries (replaces existing), e.g. ['backend/auth/authentication']",
758
+ required: ["path", "lines"],
759
+ description: "Granular section tracking: only the specified line ranges are monitored",
760
+ },
761
+ ],
744
762
  },
763
+ description: "New list of tracked files (replaces existing). Use a plain path string for whole-file tracking, or { path, lines: [{ start, end }] } to track specific sections only (reduces false positives when unrelated parts of the file change).",
764
+ },
765
+ tags: {
766
+ type: "array",
767
+ items: { type: "string" },
768
+ description: "New tags (replaces existing)",
769
+ },
770
+ related_docs: {
771
+ type: "array",
772
+ items: { type: "string" },
773
+ description: "Paths of related documentation entries (replaces existing), e.g. ['backend/auth/authentication']",
745
774
  },
746
- required: ["path"],
747
775
  },
776
+ required: ["path"],
748
777
  },
749
- {
750
- name: "move_doc",
751
- description: "Move a documentation entry to a new path (changes its folder and/or name). All existing doc:// links and related_docs references pointing to the old path are automatically rewritten across the project. This is the only way to relocate a document — update_doc does not change the location.",
752
- inputSchema: {
753
- type: "object",
754
- properties: {
755
- path: { type: "string", description: "Current path of the documentation entry, e.g. 'backend/authentication'" },
756
- new_path: { type: "string", description: "New path for the documentation entry, e.g. 'backend/v2/authentication'. Can change folder, name, or both." },
757
- },
758
- required: ["path", "new_path"],
778
+ },
779
+ {
780
+ name: "move_doc",
781
+ description: "Move a documentation entry to a new path (changes its folder and/or name). All existing doc:// links and related_docs references pointing to the old path are automatically rewritten across the project. This is the only way to relocate a document — update_doc does not change the location.",
782
+ inputSchema: {
783
+ type: "object",
784
+ properties: {
785
+ path: { type: "string", description: "Current path of the documentation entry, e.g. 'backend/authentication'" },
786
+ new_path: { type: "string", description: "New path for the documentation entry, e.g. 'backend/v2/authentication'. Can change folder, name, or both." },
759
787
  },
788
+ required: ["path", "new_path"],
760
789
  },
761
- {
762
- name: "search_docs",
763
- description: "Semantic search across documentation entries using natural language. Requires OpenAI API key.",
764
- inputSchema: {
765
- type: "object",
766
- properties: {
767
- query: { type: "string", description: "Search query in natural language" },
768
- top_k: { type: "number", description: "Number of results to return (default: 5)" },
769
- },
770
- required: ["query"],
790
+ },
791
+ {
792
+ name: "search_docs",
793
+ description: "Semantic search across documentation entries using natural language. Requires OpenAI API key.",
794
+ inputSchema: {
795
+ type: "object",
796
+ properties: {
797
+ query: { type: "string", description: "Search query in natural language" },
798
+ top_k: { type: "number", description: "Number of results to return (default: 5)" },
771
799
  },
800
+ required: ["query"],
772
801
  },
773
- {
774
- name: "check_freshness",
775
- description: "Check freshness of documentation. Pass a path to check a single entry, or omit it to scan all docs in a project and get a full staleness report.",
776
- inputSchema: {
777
- type: "object",
778
- properties: {
779
- path: { type: "string", description: "Path of a single documentation entry to check, e.g. 'backend/auth/authentication'. If omitted, scans all docs in the project." },
780
- project: { type: "string", description: "Project to scan (scans all projects if omitted). Only used when path is omitted." },
781
- },
782
- required: [],
802
+ },
803
+ {
804
+ name: "check_freshness",
805
+ description: "Check freshness of documentation. Pass a path to check a single entry, or omit it to scan all docs in a project and get a full staleness report.",
806
+ inputSchema: {
807
+ type: "object",
808
+ properties: {
809
+ path: { type: "string", description: "Path of a single documentation entry to check, e.g. 'backend/auth/authentication'. If omitted, scans all docs in the project." },
810
+ project: { type: "string", description: "Project to scan (scans all projects if omitted). Only used when path is omitted." },
783
811
  },
812
+ required: [],
813
+ },
814
+ },
815
+ {
816
+ name: "set_project",
817
+ description: "Switch the active project and update it as the default for all subsequent MCP tool operations. If the project name does not exist yet, it is created automatically. Also controls what the GUI displays.",
818
+ inputSchema: {
819
+ type: "object",
820
+ properties: {
821
+ project: { type: "string", description: "Project name to switch to (created if it does not exist)" },
822
+ },
823
+ required: ["project"],
824
+ },
825
+ },
826
+ {
827
+ name: "list_projects",
828
+ description: "List all available documentation projects and show which one is currently active.",
829
+ inputSchema: {
830
+ type: "object",
831
+ properties: {},
784
832
  },
785
- {
786
- name: "set_project",
787
- description: "Switch the active project and update it as the default for all subsequent MCP tool operations. If the project name does not exist yet, it is created automatically. Also controls what the GUI displays.",
788
- inputSchema: {
789
- type: "object",
790
- properties: {
791
- project: { type: "string", description: "Project name to switch to (created if it does not exist)" },
833
+ },
834
+ {
835
+ name: "refresh_file_snapshot",
836
+ description: "Mark tracked files as up-to-date by updating their snapshot timestamps. Use when file changes don't affect the documentation. Accepts one or more doc paths.",
837
+ inputSchema: {
838
+ type: "object",
839
+ properties: {
840
+ paths: {
841
+ type: "array",
842
+ items: { type: "string" },
843
+ description: "Paths of documentation entries to refresh, e.g. ['backend/auth/authentication', 'setup']",
844
+ },
845
+ file_paths: {
846
+ type: "array",
847
+ items: { type: "string" },
848
+ description: "Specific tracked file paths to refresh (all tracked files if omitted)",
792
849
  },
793
- required: ["project"],
794
850
  },
851
+ required: ["paths"],
795
852
  },
796
- {
797
- name: "list_projects",
798
- description: "List all available documentation projects and show which one is currently active.",
799
- inputSchema: {
800
- type: "object",
801
- properties: {},
853
+ },
854
+ {
855
+ name: "get_doc_metadata",
856
+ description: "Get metadata of a documentation entry (path, title, tags, dates, tracked files with freshness) without loading the full content.",
857
+ inputSchema: {
858
+ type: "object",
859
+ properties: {
860
+ path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
802
861
  },
862
+ required: ["path"],
803
863
  },
804
- {
805
- name: "refresh_file_snapshot",
806
- description: "Mark tracked files as up-to-date by updating their snapshot timestamps. Use when file changes don't affect the documentation. Accepts one or more doc paths.",
807
- inputSchema: {
808
- type: "object",
809
- properties: {
810
- paths: {
811
- type: "array",
812
- items: { type: "string" },
813
- description: "Paths of documentation entries to refresh, e.g. ['backend/auth/authentication', 'setup']",
814
- },
815
- file_paths: {
816
- type: "array",
817
- items: { type: "string" },
818
- description: "Specific tracked file paths to refresh (all tracked files if omitted)",
819
- },
820
- },
821
- required: ["paths"],
864
+ },
865
+ {
866
+ name: "patch_doc",
867
+ description: "PREFERRED method for modifying documentation. Replaces a specific portion of a documentation entry's content using an exact search-and-replace, without touching the rest of the doc. Always use this instead of update_doc when making partial changes.",
868
+ inputSchema: {
869
+ type: "object",
870
+ properties: {
871
+ path: { type: "string", description: "Path of the documentation entry to patch, e.g. 'backend/auth/authentication'" },
872
+ search: { type: "string", description: "Exact text to find in the current content" },
873
+ search_file: { type: "string", description: "Absolute path to a text file whose content will be used as the search text. Use this to reuse a previously persisted draft (path is returned in the error message when a previous call failed)." },
874
+ replacement: { type: "string", description: "Text to replace it with. Supports [copy-from-codebase|/absolute/path/to/file.ts|startLine|endLine] — the tag is replaced at save time by a fenced code block, and the file+range is automatically added to tracked_files. IMPORTANT: always read the source file first to identify the exact line numbers before writing the tag." },
875
+ replacement_file: { type: "string", description: "Absolute path to a text file whose content will be used as the replacement. Use this to reuse a previously persisted draft (path is returned in the error message when a previous call failed)." },
822
876
  },
877
+ required: ["path", "search"],
823
878
  },
824
- {
825
- name: "get_doc_metadata",
826
- description: "Get metadata of a documentation entry (path, title, tags, dates, tracked files with freshness) without loading the full content.",
827
- inputSchema: {
828
- type: "object",
829
- properties: {
830
- path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
879
+ },
880
+ {
881
+ name: "git_diff_doc",
882
+ description: "Show the git diff for each tracked file of one or more docs, between their current state and the last commit before the stored snapshot date.",
883
+ inputSchema: {
884
+ type: "object",
885
+ properties: {
886
+ paths: {
887
+ type: "array",
888
+ items: { type: "string" },
889
+ description: "Paths of documentation entries to diff, e.g. ['backend/auth/authentication']",
890
+ },
891
+ file_paths: {
892
+ type: "array",
893
+ items: { type: "string" },
894
+ description: "Specific tracked file paths to diff (all tracked files if omitted)",
831
895
  },
832
- required: ["path"],
833
896
  },
897
+ required: ["paths"],
834
898
  },
835
- {
836
- name: "patch_doc",
837
- description: "PREFERRED method for modifying documentation. Replaces a specific portion of a documentation entry's content using an exact search-and-replace, without touching the rest of the doc. Always use this instead of update_doc when making partial changes.",
838
- inputSchema: {
839
- type: "object",
840
- properties: {
841
- path: { type: "string", description: "Path of the documentation entry to patch, e.g. 'backend/auth/authentication'" },
842
- search: { type: "string", description: "Exact text to find in the current content" },
843
- search_file: { type: "string", description: "Absolute path to a text file whose content will be used as the search text. Use this to reuse a previously persisted draft (path is returned in the error message when a previous call failed)." },
844
- replacement: { type: "string", description: "Text to replace it with. Supports [copy-from-codebase|/absolute/path/to/file.ts|startLine|endLine] — the tag is replaced at save time by a fenced code block, and the file+range is automatically added to tracked_files. IMPORTANT: always read the source file first to identify the exact line numbers before writing the tag." },
845
- replacement_file: { type: "string", description: "Absolute path to a text file whose content will be used as the replacement. Use this to reuse a previously persisted draft (path is returned in the error message when a previous call failed)." },
846
- },
847
- required: ["path", "search"],
899
+ },
900
+ {
901
+ name: "check_links",
902
+ description: "Check all links across a project and report broken references. Validates: related_docs IDs, inline doc links ([text](doc://id)) in content, and anchor links ([text](#heading-slug)) in content.",
903
+ inputSchema: {
904
+ type: "object",
905
+ properties: {
906
+ project: { type: "string", description: "Project to check (defaults to current project)" },
848
907
  },
908
+ required: [],
849
909
  },
850
- {
851
- name: "git_diff_doc",
852
- description: "Show the git diff for each tracked file of one or more docs, between their current state and the last commit before the stored snapshot date.",
853
- inputSchema: {
854
- type: "object",
855
- properties: {
856
- paths: {
857
- type: "array",
858
- items: { type: "string" },
859
- description: "Paths of documentation entries to diff, e.g. ['backend/auth/authentication']",
860
- },
861
- file_paths: {
862
- type: "array",
863
- items: { type: "string" },
864
- description: "Specific tracked file paths to diff (all tracked files if omitted)",
865
- },
866
- },
867
- required: ["paths"],
910
+ },
911
+ {
912
+ name: "delete_doc",
913
+ description: "Permanently delete a documentation entry. Works across all projects — no need to set the active project first.",
914
+ inputSchema: {
915
+ type: "object",
916
+ properties: {
917
+ path: { type: "string", description: "Path of the documentation entry to delete, e.g. 'backend/auth/authentication'" },
868
918
  },
919
+ required: ["path"],
869
920
  },
870
- {
871
- name: "check_links",
872
- description: "Check all links across a project and report broken references. Validates: related_docs IDs, inline doc links ([text](doc://id)) in content, and anchor links ([text](#heading-slug)) in content.",
873
- inputSchema: {
874
- type: "object",
875
- properties: {
876
- project: { type: "string", description: "Project to check (defaults to current project)" },
877
- },
878
- required: [],
921
+ },
922
+ {
923
+ name: "get_doc_tree",
924
+ description: "Get the full documentation tree showing folders, subfolders, and documentation names (not file names). Useful to quickly understand the documentation structure of a project.",
925
+ inputSchema: {
926
+ type: "object",
927
+ properties: {
928
+ project: { type: "string", description: "Project to display (shows all projects if omitted)" },
879
929
  },
880
930
  },
881
- {
882
- name: "delete_doc",
883
- description: "Permanently delete a documentation entry. Works across all projects — no need to set the active project first.",
884
- inputSchema: {
885
- type: "object",
886
- properties: {
887
- path: { type: "string", description: "Path of the documentation entry to delete, e.g. 'backend/auth/authentication'" },
888
- },
889
- required: ["path"],
931
+ },
932
+ {
933
+ name: "list_doc_versions",
934
+ description: "List all saved versions of a documentation entry, with date and change type.",
935
+ inputSchema: {
936
+ type: "object",
937
+ properties: {
938
+ path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
939
+ project: { type: "string", description: "Project to look in (defaults to current project)" },
890
940
  },
941
+ required: ["path"],
891
942
  },
892
- {
893
- name: "get_doc_tree",
894
- description: "Get the full documentation tree showing folders, subfolders, and documentation names (not file names). Useful to quickly understand the documentation structure of a project.",
895
- inputSchema: {
896
- type: "object",
897
- properties: {
898
- project: { type: "string", description: "Project to display (shows all projects if omitted)" },
899
- },
943
+ },
944
+ {
945
+ name: "get_doc_version",
946
+ description: "Retrieve the full content of a specific version of a documentation entry.",
947
+ inputSchema: {
948
+ type: "object",
949
+ properties: {
950
+ path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
951
+ version: { type: "number", description: "Version number to retrieve" },
952
+ project: { type: "string", description: "Project to look in (defaults to current project)" },
900
953
  },
954
+ required: ["path", "version"],
901
955
  },
902
- {
903
- name: "list_doc_versions",
904
- description: "List all saved versions of a documentation entry, with date and change type.",
905
- inputSchema: {
906
- type: "object",
907
- properties: {
908
- path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
909
- project: { type: "string", description: "Project to look in (defaults to current project)" },
910
- },
911
- required: ["path"],
956
+ },
957
+ {
958
+ name: "restore_doc_version",
959
+ description: "Restore a documentation entry to a previous version. The current state is saved as a new version before overwriting.",
960
+ inputSchema: {
961
+ type: "object",
962
+ properties: {
963
+ path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
964
+ version: { type: "number", description: "Version number to restore" },
965
+ project: { type: "string", description: "Project to look in (defaults to current project)" },
912
966
  },
967
+ required: ["path", "version"],
913
968
  },
914
- {
915
- name: "get_doc_version",
916
- description: "Retrieve the full content of a specific version of a documentation entry.",
917
- inputSchema: {
918
- type: "object",
919
- properties: {
920
- path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
921
- version: { type: "number", description: "Version number to retrieve" },
922
- project: { type: "string", description: "Project to look in (defaults to current project)" },
923
- },
924
- required: ["path", "version"],
969
+ },
970
+ {
971
+ name: "reindex_embeddings",
972
+ description: "Generate missing embeddings for all documentation entries in a project (or all projects). Useful when importing existing documentation that was created outside the MCP. Use force: true to regenerate embeddings for all docs, including those already indexed.",
973
+ inputSchema: {
974
+ type: "object",
975
+ properties: {
976
+ project: { type: "string", description: "Project to reindex (processes all projects if omitted)" },
977
+ force: { type: "boolean", description: "If true, regenerates embeddings for all docs even if they are already indexed (default: false)" },
925
978
  },
979
+ required: [],
926
980
  },
927
- {
928
- name: "restore_doc_version",
929
- description: "Restore a documentation entry to a previous version. The current state is saved as a new version before overwriting.",
930
- inputSchema: {
931
- type: "object",
932
- properties: {
933
- path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
934
- version: { type: "number", description: "Version number to restore" },
935
- project: { type: "string", description: "Project to look in (defaults to current project)" },
981
+ },
982
+ {
983
+ name: "prepare_export_bundle",
984
+ description: "Create or update a named export bundle: a set of documentation references that the user can download as a ZIP from the GUI. The ZIP contains clean markdown content (no frontmatter). Existing bundle with the same name is replaced.",
985
+ inputSchema: {
986
+ type: "object",
987
+ properties: {
988
+ name: { type: "string", description: "Bundle display name (becomes the identifier via slugify)" },
989
+ zip_filename: { type: "string", description: "ZIP filename to download, e.g. 'my-bundle.zip'" },
990
+ docs: {
991
+ type: "array",
992
+ items: { type: "string" },
993
+ description: "Doc references to include: 'project:doc-id' for cross-project, or 'doc-id' for current project",
936
994
  },
937
- required: ["path", "version"],
995
+ description: { type: "string", description: "Short description of the bundle (optional)" },
938
996
  },
997
+ required: ["name", "zip_filename", "docs"],
939
998
  },
940
- {
941
- name: "reindex_embeddings",
942
- description: "Generate missing embeddings for all documentation entries in a project (or all projects). Useful when importing existing documentation that was created outside the MCP. Use force: true to regenerate embeddings for all docs, including those already indexed.",
943
- inputSchema: {
944
- type: "object",
945
- properties: {
946
- project: { type: "string", description: "Project to reindex (processes all projects if omitted)" },
947
- force: { type: "boolean", description: "If true, regenerates embeddings for all docs even if they are already indexed (default: false)" },
948
- },
949
- required: [],
999
+ },
1000
+ {
1001
+ name: "delete_export_bundle",
1002
+ description: "Delete an export bundle by its identifier (slug).",
1003
+ inputSchema: {
1004
+ type: "object",
1005
+ properties: {
1006
+ id: { type: "string", description: "Bundle identifier to delete (slug, e.g. 'my-bundle')" },
950
1007
  },
1008
+ required: ["id"],
951
1009
  },
952
- {
953
- name: "prepare_export_bundle",
954
- description: "Create or update a named export bundle: a set of documentation references that the user can download as a ZIP from the GUI. The ZIP contains clean markdown content (no frontmatter). Existing bundle with the same name is replaced.",
955
- inputSchema: {
956
- type: "object",
957
- properties: {
958
- name: { type: "string", description: "Bundle display name (becomes the identifier via slugify)" },
959
- zip_filename: { type: "string", description: "ZIP filename to download, e.g. 'my-bundle.zip'" },
960
- docs: {
961
- type: "array",
962
- items: { type: "string" },
963
- description: "Doc references to include: 'project:doc-id' for cross-project, or 'doc-id' for current project",
964
- },
965
- description: { type: "string", description: "Short description of the bundle (optional)" },
966
- },
967
- required: ["name", "zip_filename", "docs"],
1010
+ },
1011
+ {
1012
+ name: "upload_image",
1013
+ description: "Upload an image to a project's assets from a local file path and get back the markdown link to insert in a document. Returns the URL and a ready-to-use markdown snippet: ![alt](/api/images/{project}/{filename}).",
1014
+ inputSchema: {
1015
+ type: "object",
1016
+ properties: {
1017
+ filename: { type: "string", description: "Filename with extension, e.g. 'screenshot.png'" },
1018
+ filePath: { type: "string", description: "Absolute path to the image file on the server" },
1019
+ project: { type: "string", description: "Target project (defaults to current project)" },
1020
+ alt: { type: "string", description: "Alt text for the markdown image link (optional)" },
968
1021
  },
1022
+ required: ["filename", "filePath"],
969
1023
  },
970
- {
971
- name: "delete_export_bundle",
972
- description: "Delete an export bundle by its identifier (slug).",
973
- inputSchema: {
974
- type: "object",
975
- properties: {
976
- id: { type: "string", description: "Bundle identifier to delete (slug, e.g. 'my-bundle')" },
977
- },
978
- required: ["id"],
1024
+ },
1025
+ {
1026
+ name: "get_image",
1027
+ description: "Retrieve an image stored in a project's assets by its URI and return it as a base64-encoded image. Use this when a document references an image (e.g. ![alt](/api/images/{project}/{filename})) and you need to view its content.",
1028
+ inputSchema: {
1029
+ type: "object",
1030
+ properties: {
1031
+ uri: { type: "string", description: "Image URI as it appears in the document, e.g. '/api/images/my-project/screenshot.png'" },
979
1032
  },
1033
+ required: ["uri"],
980
1034
  },
981
- {
982
- name: "upload_image",
983
- description: "Upload an image to a project's assets from a local file path and get back the markdown link to insert in a document. Returns the URL and a ready-to-use markdown snippet: ![alt](/api/images/{project}/{filename}).",
984
- inputSchema: {
985
- type: "object",
986
- properties: {
987
- filename: { type: "string", description: "Filename with extension, e.g. 'screenshot.png'" },
988
- filePath: { type: "string", description: "Absolute path to the image file on the server" },
989
- project: { type: "string", description: "Target project (defaults to current project)" },
990
- alt: { type: "string", description: "Alt text for the markdown image link (optional)" },
991
- },
992
- required: ["filename", "filePath"],
1035
+ },
1036
+ {
1037
+ name: "sync_status",
1038
+ description: "Show the synchronization status: current branch, how many commits the local repo is ahead/behind the remote, conflicted files, and uncommitted changes. The sync target (repository, branch, token) is fixed by the server administrator via environment variables and cannot be changed from here.",
1039
+ inputSchema: { type: "object", properties: {} },
1040
+ },
1041
+ {
1042
+ name: "pull_docs",
1043
+ description: "Pull documentation updates from the GitHub remote. Defensive: only fast-forwards a clean working tree, never auto-merges a divergent history (local data is never overwritten). Reindexes embeddings for new documents after a successful update.",
1044
+ inputSchema: { type: "object", properties: {} },
1045
+ },
1046
+ {
1047
+ name: "push_docs",
1048
+ description: "Commit local documentation changes (markdown + assets + project list) and push them to the GitHub remote.",
1049
+ inputSchema: {
1050
+ type: "object",
1051
+ properties: {
1052
+ message: { type: "string", description: "Optional commit message (a default with machine name and timestamp is used otherwise)" },
993
1053
  },
994
1054
  },
995
- {
996
- name: "get_image",
997
- description: "Retrieve an image stored in a project's assets by its URI and return it as a base64-encoded image. Use this when a document references an image (e.g. ![alt](/api/images/{project}/{filename})) and you need to view its content.",
998
- inputSchema: {
999
- type: "object",
1000
- properties: {
1001
- uri: { type: "string", description: "Image URI as it appears in the document, e.g. '/api/images/my-project/screenshot.png'" },
1002
- },
1003
- required: ["uri"],
1055
+ },
1056
+ {
1057
+ name: "sync_docs",
1058
+ description: "Full synchronization: pull (defensive) then push. If the pull reveals a divergence or conflict, the push is skipped so it can be resolved first.",
1059
+ inputSchema: {
1060
+ type: "object",
1061
+ properties: {
1062
+ message: { type: "string", description: "Optional commit message for the push step" },
1004
1063
  },
1005
1064
  },
1006
- ],
1065
+ },
1066
+ {
1067
+ name: "resolve_sync_conflict",
1068
+ description: "Bulk-resolve sync merge conflicts by keeping one side for the conflicted files, then stage them. For nuanced resolution, edit the conflicted markdown files directly instead. After resolving, run push_docs.",
1069
+ inputSchema: {
1070
+ type: "object",
1071
+ properties: {
1072
+ strategy: { type: "string", enum: ["mine", "theirs"], description: "'mine' keeps the local version, 'theirs' keeps the incoming remote version" },
1073
+ paths: { type: "array", items: { type: "string" }, description: "Specific conflicted file paths to resolve (defaults to all conflicted files)" },
1074
+ },
1075
+ required: ["strategy"],
1076
+ },
1077
+ },
1078
+ ];
1079
+ return {
1080
+ tools: isReadOnly() ? tools.filter((t) => !WRITE_TOOLS.has(t.name)) : tools,
1007
1081
  };
1008
1082
  });
1009
1083
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
1010
1084
  const { name, arguments: args } = request.params;
1085
+ if (isReadOnly() && WRITE_TOOLS.has(name)) {
1086
+ return {
1087
+ content: [{ type: "text", text: `Error: tool "${name}" is disabled (READ_ONLY mode).` }],
1088
+ isError: true,
1089
+ };
1090
+ }
1011
1091
  try {
1012
1092
  switch (name) {
1013
1093
  case "create_doc":
@@ -1076,6 +1156,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1076
1156
  return await uploadImage(storageEngine, args);
1077
1157
  case "get_image":
1078
1158
  return await getImage(storageEngine, args);
1159
+ case "sync_status":
1160
+ return await syncStatus(syncEngine);
1161
+ case "pull_docs":
1162
+ return await pullDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore);
1163
+ case "push_docs":
1164
+ return await pushDocs(syncEngine, args);
1165
+ case "sync_docs":
1166
+ return await syncDocs(syncEngine, storageEngine, embeddingEngine, embeddingStore, args);
1167
+ case "resolve_sync_conflict":
1168
+ return await resolveSyncConflict(syncEngine, args);
1079
1169
  default:
1080
1170
  throw new Error(`Tool not found: ${name}`);
1081
1171
  }
@@ -1087,9 +1177,37 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1087
1177
  };
1088
1178
  }
1089
1179
  });
1180
+ async function syncPullOnStartup() {
1181
+ if (!syncEngine.isConfigured())
1182
+ return;
1183
+ if ((process.env.SYNC_PULL_ON_START ?? "").trim().toLowerCase() === "false")
1184
+ return;
1185
+ try {
1186
+ const res = await syncEngine.pull();
1187
+ if (res.status === "updated") {
1188
+ logger.info(`Startup sync: pulled ${res.changed.length} changed file(s).`);
1189
+ if (embeddingEngine.isConfigured()) {
1190
+ await reindexEmbeddings(storageEngine, embeddingEngine, embeddingStore, {});
1191
+ }
1192
+ }
1193
+ else if (res.status === "diverged") {
1194
+ logger.warn(`Startup sync: local history diverged (ahead ${res.ahead}, behind ${res.behind}). Started on local data — run sync_status / sync_docs to reconcile.`);
1195
+ }
1196
+ else if (res.status === "conflict") {
1197
+ logger.warn(`Startup sync: merge conflict in ${res.conflicted.length} file(s). Started on local data — resolve with resolve_sync_conflict.`);
1198
+ }
1199
+ else {
1200
+ logger.info("Startup sync: already up to date.");
1201
+ }
1202
+ }
1203
+ catch (err) {
1204
+ logger.error(`Startup sync failed (non-fatal): ${err?.message ?? err}`);
1205
+ }
1206
+ }
1090
1207
  async function main() {
1091
1208
  await migrateFolders(storageEngine);
1092
1209
  await migrateEmbeddings(embeddingStore, storageEngine);
1210
+ await syncPullOnStartup();
1093
1211
  const transport = new StdioServerTransport();
1094
1212
  await server.connect(transport);
1095
1213
  logger.info("MCP Server connected and running on stdio");