@imenam/mcp-documentor 1.0.92 → 1.0.93

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 (83) hide show
  1. package/README.md +343 -327
  2. package/dist/src/core/git-sync.js +1 -0
  3. package/dist/src/core/publication-store.d.ts +76 -0
  4. package/dist/src/core/publication-store.js +180 -0
  5. package/dist/src/gui-worker.js +133 -1
  6. package/dist/src/index.js +110 -3
  7. package/gui/dist/assets/DocContent-BxncLHIS.css +1 -0
  8. package/gui/dist/assets/DocContent-qNz5QMPa.js +346 -0
  9. package/gui/dist/assets/arc-DvP0UOkD.js +1 -0
  10. package/gui/dist/assets/architectureDiagram-3BPJPVTR-3wEHUA1o.js +36 -0
  11. package/gui/dist/assets/{blockDiagram-GPEHLZMM-CgILcS8Q.js → blockDiagram-GPEHLZMM-C01nZdXH.js} +1 -1
  12. package/gui/dist/assets/{c4Diagram-AAUBKEIU-ByGBdJcU.js → c4Diagram-AAUBKEIU-C995ER45.js} +1 -1
  13. package/gui/dist/assets/channel-Cjefy81D.js +1 -0
  14. package/gui/dist/assets/chunk-2J33WTMH-B7Z-cZqf.js +1 -0
  15. package/gui/dist/assets/chunk-4BX2VUAB-BRtaRRAZ.js +1 -0
  16. package/gui/dist/assets/chunk-55IACEB6-CZbPaYTs.js +1 -0
  17. package/gui/dist/assets/{chunk-727SXJPM-B0BlALdx.js → chunk-727SXJPM-BMKPsW_V.js} +1 -1
  18. package/gui/dist/assets/{chunk-AQP2D5EJ-DlKhB6LK.js → chunk-AQP2D5EJ-BQJRaqJz.js} +1 -1
  19. package/gui/dist/assets/{chunk-FMBD7UC4-DBTqXHP1.js → chunk-FMBD7UC4-BiKjOUxU.js} +1 -1
  20. package/gui/dist/assets/chunk-ND2GUHAM-BQYUzZi5.js +1 -0
  21. package/gui/dist/assets/chunk-QZHKN3VN-C0mI_9k0.js +1 -0
  22. package/gui/dist/assets/classDiagram-4FO5ZUOK-U-TA2l-f.js +1 -0
  23. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-U-TA2l-f.js +1 -0
  24. package/gui/dist/assets/cose-bilkent-S5V4N54A-9QkakK6h.js +1 -0
  25. package/gui/dist/assets/dagre-BM42HDAG-p_TitJwo.js +4 -0
  26. package/gui/dist/assets/diagram-2AECGRRQ-T3ul_-C1.js +43 -0
  27. package/gui/dist/assets/diagram-5GNKFQAL-Cv4-mYjj.js +10 -0
  28. package/gui/dist/assets/{diagram-KO2AKTUF-B-LnaXtr.js → diagram-KO2AKTUF-GEuY7REm.js} +1 -1
  29. package/gui/dist/assets/diagram-LMA3HP47-BlZVA1ZL.js +24 -0
  30. package/gui/dist/assets/diagram-OG6HWLK6-DZ9muekA.js +24 -0
  31. package/gui/dist/assets/{erDiagram-TEJ5UH35-B7jlXuub.js → erDiagram-TEJ5UH35-BQ_TWeSA.js} +1 -1
  32. package/gui/dist/assets/{flowDiagram-I6XJVG4X-BjWyZsCs.js → flowDiagram-I6XJVG4X-BVxlCJhG.js} +1 -1
  33. package/gui/dist/assets/ganttDiagram-6RSMTGT7-DlHxknKg.js +292 -0
  34. package/gui/dist/assets/gitGraphDiagram-PVQCEYII-DYHNhcQ1.js +106 -0
  35. package/gui/dist/assets/infoDiagram-5YYISTIA-usWWgsgX.js +2 -0
  36. package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-De4Ds8bG.js → ishikawaDiagram-YF4QCWOH-GXA4mXdI.js} +1 -1
  37. package/gui/dist/assets/{journeyDiagram-JHISSGLW-Cjpgxilg.js → journeyDiagram-JHISSGLW-DuiQ_voI.js} +1 -1
  38. package/gui/dist/assets/{kanban-definition-UN3LZRKU-D8Y_Af2d.js → kanban-definition-UN3LZRKU-Cf5Zx4Ai.js} +1 -1
  39. package/gui/dist/assets/linear-Dxg5B9lZ.js +1 -0
  40. package/gui/dist/assets/main-BoIDzkiC.js +10 -0
  41. package/gui/dist/assets/main-DDy8gF40.css +1 -0
  42. package/gui/dist/assets/{mindmap-definition-RKZ34NQL-Cq_fBarD.js → mindmap-definition-RKZ34NQL-BI-GTubt.js} +1 -1
  43. package/gui/dist/assets/{pieDiagram-4H26LBE5-LGkTEx-6.js → pieDiagram-4H26LBE5-jm6JjM7-.js} +1 -1
  44. package/gui/dist/assets/public-5AzxUEZY.css +1 -0
  45. package/gui/dist/assets/public-BbfNIL4J.js +1 -0
  46. package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-mgy_9RRW.js → quadrantDiagram-W4KKPZXB-D_FMBvP7.js} +1 -1
  47. package/gui/dist/assets/{requirementDiagram-4Y6WPE33-B_d8OEc3.js → requirementDiagram-4Y6WPE33-DrAnq7B5.js} +1 -1
  48. package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-BgBLM80u.js → sankeyDiagram-5OEKKPKP-BBxMYinl.js} +1 -1
  49. package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-B9GkqtWb.js → sequenceDiagram-3UESZ5HK-CzlReNxZ.js} +1 -1
  50. package/gui/dist/assets/stateDiagram-AJRCARHV-CAj_FaGh.js +1 -0
  51. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-DL8IxfA-.js +1 -0
  52. package/gui/dist/assets/{timeline-definition-PNZ67QCA-Dyx58rRr.js → timeline-definition-PNZ67QCA-CHzi4ypa.js} +1 -1
  53. package/gui/dist/assets/{vennDiagram-CIIHVFJN-IypthVr7.js → vennDiagram-CIIHVFJN-DT2hW75o.js} +1 -1
  54. package/gui/dist/assets/{wardley-L42UT6IY-DkLzWKUw.js → wardley-L42UT6IY-BBPqyjj6.js} +1 -1
  55. package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-98VafNiZ.js → wardleyDiagram-YWT4CUSO-CoB6ihhL.js} +1 -1
  56. package/gui/dist/assets/{xychartDiagram-2RQKCTM6-dZj2dDaC.js → xychartDiagram-2RQKCTM6-FgTwWkea.js} +1 -1
  57. package/gui/dist/index.html +4 -2
  58. package/gui/dist/public.html +17 -0
  59. package/package.json +1 -1
  60. package/gui/dist/assets/arc-BbedxJ3W.js +0 -1
  61. package/gui/dist/assets/architectureDiagram-3BPJPVTR-DF3IYwLZ.js +0 -36
  62. package/gui/dist/assets/channel-CIJsZK_a.js +0 -1
  63. package/gui/dist/assets/chunk-2J33WTMH-DIk0ZeJ6.js +0 -1
  64. package/gui/dist/assets/chunk-4BX2VUAB-fF78mC-G.js +0 -1
  65. package/gui/dist/assets/chunk-55IACEB6-BzoqbAWn.js +0 -1
  66. package/gui/dist/assets/chunk-ND2GUHAM-BmMICe1L.js +0 -1
  67. package/gui/dist/assets/chunk-QZHKN3VN-DyEBqCzf.js +0 -1
  68. package/gui/dist/assets/classDiagram-4FO5ZUOK-LzEGnqT7.js +0 -1
  69. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-LzEGnqT7.js +0 -1
  70. package/gui/dist/assets/cose-bilkent-S5V4N54A-DMkNiOVz.js +0 -1
  71. package/gui/dist/assets/dagre-BM42HDAG-CEGX5APR.js +0 -4
  72. package/gui/dist/assets/diagram-2AECGRRQ-DHo40rfh.js +0 -43
  73. package/gui/dist/assets/diagram-5GNKFQAL-CPbyX-Jj.js +0 -10
  74. package/gui/dist/assets/diagram-LMA3HP47-gjnm6GxN.js +0 -24
  75. package/gui/dist/assets/diagram-OG6HWLK6-B_qc7WBs.js +0 -24
  76. package/gui/dist/assets/ganttDiagram-6RSMTGT7-CGdvXL_7.js +0 -292
  77. package/gui/dist/assets/gitGraphDiagram-PVQCEYII-TxUcHrcr.js +0 -106
  78. package/gui/dist/assets/index-ARDnpfWR.css +0 -1
  79. package/gui/dist/assets/index-CrG0yj-K.js +0 -355
  80. package/gui/dist/assets/infoDiagram-5YYISTIA-BElIHd-R.js +0 -2
  81. package/gui/dist/assets/linear-BB2LhUqA.js +0 -1
  82. package/gui/dist/assets/stateDiagram-AJRCARHV-UoRM2vqX.js +0 -1
  83. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-B7m-qaHs.js +0 -1
package/README.md CHANGED
@@ -1,327 +1,343 @@
1
- # @imenam/mcp-documentor
2
-
3
- An MCP server to create, manage, and search project documentation — with staleness tracking and optional semantic search.
4
-
5
- Connect it to Cursor, Claude Desktop, or any MCP-compatible agent to let your AI document, browse and verify your codebase docs without leaving the chat.
6
-
7
- ## Features
8
-
9
- - **Create & update docs** — write markdown documentation entries linked to source files; prefer partial edits via `patch_doc`
10
- - **Staleness tracking** — automatically detects when tracked files have changed since the last doc update
11
- - **Portable code references** — tracked-file paths are stored relative to a code root set per machine and per project (GUI or `set_code_root`), so synced docs resolve their source files on any machine regardless of where the repos live
12
- - **Track a branch, not a checkout** — point a project at a GitHub branch from its settings in the GUI and freshness is measured against *published* code: switching branches or editing without committing no longer makes docs look stale, and a server with no copy of the codebase (an MCP gateway) can still verify them
13
- - **Semantic search** — find relevant docs by meaning (requires an OpenAI API key)
14
- - **Multi-project support** — organize docs into separate projects; every MCP tool names the project it targets, and the GUI's project selector is a per-browser view preference
15
- - **GUI** — built-in web interface, either behind the proxy (`PROXY_URL`) or served directly on a local port (`APP_PORT`); with neither, the GUI worker is not started. Navigation runs from an icon rail on the left: the current project's badge (click to switch projects or create one), then Documentation, Search (`Ctrl/⌘ + K`, current or all projects), Outdated, Recent, Export bundles and Logs, with Sync and Settings at the bottom. Each destination fills the panel next to the rail; the panel's hide button, or a second click on the active icon, folds it away. Drag the panel's right edge to resize it (double-click resets the width); the browser remembers the width. The details panel on the right of a document (`Ctrl/⌘ + \`) is open by default from 1280 px wide; showing or hiding it by hand is remembered across reloads until the window crosses that width. Badges show the outdated docs, pending commits and the day's failed GitHub calls. Each open tab polls `GET /api/changes` every 3 seconds and every request is answered immediately: no connection stays open, so many tabs can share the proxy's origin, and changes made by the agent show up within 3 seconds. After a GUI worker restart, or when it has fallen more than 200 changes behind, the tab reloads the server state (`GET /api/initial-state`) and the list of the project it shows
16
- - **Proxy-aware** — integrates with the [proxy-setup](https://github.com/Imena/proxy-setup) for GUI exposure
17
- - **Links & structure** — `check_links`, `get_doc_tree`, `move_doc`, and `git_diff_doc` for maintenance workflows
18
- - **Image support** — upload images to a project's assets via `upload_image` (local file path) or directly from the GUI editor; images are stored per-project and rendered inline in the viewer and in PDF exports
19
- - **GitHub sync** — share your documentation across machines by backing the data directory with a private GitHub repo; the repo/token are fixed by the administrator via environment variables, and the agent can only `pull_docs` / `push_docs` / `sync_docs`
20
-
21
- ## Installation
22
-
23
- ```bash
24
- npx -y @imenam/mcp-documentor
25
- ```
26
-
27
- Or install globally:
28
-
29
- ```bash
30
- npm install -g @imenam/mcp-documentor
31
- ```
32
-
33
- Global install exposes:
34
-
35
- - `mcp-documentor` — MCP server
36
- - `mcp-documentor-migrate` — CLI helper for migrating inline doc links (see `scripts/migrate-links.js`)
37
- - `mcp-documentor-migrate-paths` — one-shot migration of tracked-file paths from absolute to code-root-relative (see [Portable tracked-file paths](#portable-tracked-file-paths))
38
-
39
- ## MCP Configuration
40
-
41
- ### Quick setup
42
-
43
- From the repo you want to document, generate a ready-to-fill `.mcp.json` in the current directory:
44
-
45
- ```bash
46
- npx @imenam/mcp-documentor --claude-setup-mcp
47
- ```
48
-
49
- It creates the `mcp-documentor` entry with the usual environment variables as empty placeholders —
50
- an empty variable is simply inactive, so the generated config runs as-is and you only fill in what
51
- you need. An existing `.mcp.json` is merged, other servers are preserved, and an existing
52
- `mcp-documentor` entry is only replaced when `--force` is passed.
53
-
54
- Two flags let you pick how the GUI is exposed straight from the command line, instead of editing
55
- the JSON afterwards:
56
-
57
- ```bash
58
- # GUI behind the central proxy
59
- npx @imenam/mcp-documentor --claude-setup-mcp --proxy_url=http://localhost:3000
60
-
61
- # GUI served directly, no proxy: http://localhost:4500
62
- npx @imenam/mcp-documentor --claude-setup-mcp --app_port=4500
63
- ```
64
-
65
- The two flags are mutually exclusive — passing both fails.
66
-
67
- ### Manual setup
68
-
69
- Add to your MCP client config (e.g. Cursor `mcp.json`):
70
-
71
- ```json
72
- {
73
- "mcpServers": {
74
- "mcp-documentor": {
75
- "command": "npx",
76
- "args": ["-y", "@imenam/mcp-documentor"],
77
- "env": {
78
- "OPENAI_API_KEY": "sk-..."
79
- }
80
- }
81
- }
82
- }
83
- ```
84
-
85
- ### Environment Variables
86
-
87
- | Variable | Required | Description |
88
- |---|---|---|
89
- | `OPENAI_API_KEY` | No | Enables semantic search via OpenAI embeddings (`search_docs`) |
90
- | `MCP_DOCUMENTOR_OPENAI_MODEL` | No | Embedding model (default: `text-embedding-3-small`) |
91
- | `PROXY_URL` | No | URL of the proxy server: it allocates the port and mounts the GUI under `APP_PATH` |
92
- | `APP_PORT` | No | Local port used **when `PROXY_URL` is unset**: the GUI is served directly at `http://localhost:<port>`, at the root. Lets you use the interface without the central hub. `PROXY_URL` wins if both are set; with neither, the GUI is disabled and the MCP tools still work |
93
- | `APP_GROUP` | No | Groups the app under a collapsible section (folded by default) in the proxy dashboard. Apps sharing the same value appear together; if unset, the app lands in the default "ungrouped" section |
94
- | `MCP_DATA_DIR` | No | Overrides the default data directory (see Data storage) |
95
- | `MCP_LOG_DIR` | No | Overrides the log directory of both the server and the GUI worker (default: `<package>/.docs-data/logs`). Must be set in the MCP client config — the package `.env` is loaded after the logger starts |
96
- | `MCP_DOCUMENTOR_CODE_ROOT` | No | Seeds the machine's default code root the first time a data directory starts (`--claude-setup-mcp` fills it with the current folder). Afterwards code roots are set in the GUI or with `set_code_root` (see [Portable tracked-file paths](#portable-tracked-file-paths)) |
97
- | `MCP_DOCUMENTOR_CODE_TOKEN` | No | GitHub token used to read the code of projects tracking a GitHub branch. The only place it is set: neither the GUI nor the agent can change it |
98
- | `READ_ONLY` | No | When set to `1`/`true`/`yes`, disables all create/update/delete tools and GUI write actions (read-only mode) |
99
- | `MCP_DOCUMENTOR_SYNC_REMOTE` | No | HTTPS URL of the private GitHub repo to sync with; enables GitHub sync when set |
100
- | `MCP_DOCUMENTOR_SYNC_BRANCH` | No | Branch of the documentation sync repo to push/pull docs on (default: `main`) |
101
- | `MCP_DOCUMENTOR_SYNC_TOKEN` | No | Fine-grained GitHub PAT used for HTTPS auth (see GitHub sync) |
102
- | `SYNC_PULL_ON_START` | No | Set to `false` to disable the automatic pull at server startup |
103
-
104
- Optional `.env` at the package root (directory containing `package.json` discovered from the installed binary) is loaded when present.
105
-
106
- ## Available Tools
107
-
108
- Every tool that targets a specific documentation entry takes a **mandatory `project`**. It is never
109
- guessed and there is no "active project" to fall back to: resolving a doc path across projects is
110
- what made operations silently hit the wrong project when the same path existed in several. Tools
111
- that work on a whole project (`list_docs`, `get_doc_tree`, `check_freshness` without a `path`,
112
- `reindex_embeddings`) keep `project` optional, since omitting it means "all projects".
113
-
114
- | Tool | Description |
115
- |---|---|
116
- | `create_doc` | Create a new documentation entry linked to source files (`project` required) |
117
- | `get_doc` | Retrieve a documentation entry with full content (`project` required) |
118
- | `get_doc_metadata` | Get metadata and freshness status without loading content (`project` required) |
119
- | `list_docs` | List docs with staleness indicators (optionally filter by project) |
120
- | `get_doc_tree` | Return folder/doc structure for a project (or all projects) |
121
- | `update_doc` | Fully replace content and/or metadata (`project` required; prefer `patch_doc` for small edits) |
122
- | `patch_doc` | Search-and-replace a fragment of a doc’s content; the text must occur once unless `replace_all` is set (`project` required) |
123
- | `move_doc` | Change a doc’s path (`new_path`) and/or project (`new_project`); links are rewritten across all projects, bare within their own project (`project` required) |
124
- | `delete_doc` | Permanently delete a documentation entry (`project` required) |
125
- | `create_project` | Create an empty project, optionally with its code source (`code_source`, as `set_code_source`) |
126
- | `delete_project` | Delete a project; refused while it holds any doc, image or other file besides its settings |
127
- | `search_docs` | Semantic search over docs (natural language; requires OpenAI) |
128
- | `check_freshness` | Staleness for one doc (`project` required) or a full project scan |
129
- | `refresh_file_snapshot` | Mark tracked files as up-to-date without editing the doc (`project` required) |
130
- | `git_diff_doc` | Diff of changed tracked files between their snapshot content and the current one (`project` required). Files whose tracked sections can no longer be located (anchors lost) are included and diffed whole, with a warning |
131
- | `check_links` | Validate `related_docs`, `doc://` links, and `#heading` anchors (`project` required) |
132
- | `list_projects` | List all documentation projects |
133
- | `upload_image` | Upload an image from a local file path to a project's assets (`project` required); returns the markdown link to insert |
134
- | `sync_status` | Show branch, ahead/behind counts, conflicted files, and uncommitted changes |
135
- | `pull_docs` | Pull updates from the remote (defensive fast-forward; reindexes new docs) |
136
- | `push_docs` | Commit local doc changes and push them to the remote |
137
- | `sync_docs` | Full sync: pull then push (push skipped if the pull reveals a conflict) |
138
- | `resolve_sync_conflict` | Bulk-resolve merge conflicts by keeping `mine` or `theirs`, then stage |
139
- | `get_code_source` | Show each project's code source (local working copy or GitHub branch) and its code root on this machine |
140
- | `set_code_source` | Make a project read its local working copy or one branch of a GitHub repository (checked before saving) |
141
- | `set_code_root` | Set the code root of a project, or the machine default, on this machine |
142
-
143
- ## Images in documents
144
-
145
- Images can be embedded in any documentation entry using standard markdown syntax:
146
-
147
- ```markdown
148
- ![alt text](/api/images/my-project/screenshot.png) <!-- local upload -->
149
- ![alt text](https://example.com/diagram.png) <!-- external URL -->
150
- ```
151
-
152
- **Via the MCP tool** — use `upload_image` with the absolute path of the file on the server:
153
-
154
- ```json
155
- {
156
- "filename": "screenshot.png",
157
- "filePath": "/absolute/path/to/screenshot.png",
158
- "alt": "Architecture diagram"
159
- }
160
- ```
161
-
162
- The tool copies the file into `.docs-data/projects/{project}/assets/` and returns the ready-to-use markdown snippet.
163
-
164
- **Via the GUI editor** — click the **🖼 Image** button in the editor toolbar to open the image panel:
165
- - **Upload tab** — pick a local file; it is uploaded automatically and the markdown link is inserted at the cursor position
166
- - **URL tab** — paste any external image URL; the markdown link is inserted at the cursor position
167
- - **Project images gallery** — click any previously uploaded image to re-insert it
168
-
169
- ## Syncing across machines with GitHub
170
-
171
- You can share documentation between several machines by backing the data directory with a **private** GitHub repository. The approach is git-based: only the human-readable markdown, assets, and the shared project list are versioned. Derived state (embeddings) is regenerated locally, and version history stays local to each machine.
172
-
173
- ### What is and isn't synced
174
-
175
- | Synced (versioned) | Local only (gitignored) |
176
- |---|---|
177
- | `projects/**` (markdown + assets) | `embeddings.db` (regenerated after pull) |
178
- | `config.json` (project list) | `history.db` (per-machine version history) |
179
-
180
- ### Setup
181
-
182
- The sync target is **administrator-controlled**: the repository, branch and token are read from environment variables and **cannot be set or changed by the agent**. The agent can only pull/push against whatever repo you configured.
183
-
184
- 1. Create an **empty private repo** on GitHub (e.g. `my-docs`).
185
- 2. Create a **fine-grained PAT** scoped to that single repo (Repository permissions → **Contents: Read and write**).
186
- 3. Add the sync environment variables to your MCP client config, per machine:
187
-
188
- ```jsonc
189
- {
190
- "mcpServers": {
191
- "mcp-documentor": {
192
- "command": "npx",
193
- "args": ["-y", "@imenam/mcp-documentor"],
194
- "env": {
195
- "MCP_DOCUMENTOR_SYNC_REMOTE": "https://github.com/<you>/my-docs.git",
196
- "MCP_DOCUMENTOR_SYNC_BRANCH": "main", // branch of the docs sync repo, optional, defaults to "main"
197
- "MCP_DOCUMENTOR_SYNC_TOKEN": "github_pat_..."
198
- }
199
- }
200
- }
201
- }
202
- ```
203
-
204
- The token is injected into the `origin` URL inside `.git/config` only (which git never commits) — it is never pushed.
205
-
206
- ### Daily workflow
207
-
208
- - `sync_docs` — pull then push in one step (recommended).
209
- - `pull_docs` / `push_docs` — granular control.
210
- - `sync_status` — inspect ahead/behind, conflicts, and uncommitted changes.
211
-
212
- By default the server also performs a **defensive pull at startup**: it only fast-forwards a clean working tree and never overwrites local changes (disable with `SYNC_PULL_ON_START=false`). On the first run, a machine with no local data simply clones the remote.
213
-
214
- ### Conflicts
215
-
216
- Because docs are plain markdown, merge conflicts are readable. When `pull_docs` reports a conflict:
217
-
218
- - edit the conflicted `.md` files directly (remove the `<<<<<<<` / `>>>>>>>` markers), **or**
219
- - run `resolve_sync_conflict` with `strategy: "mine"` or `"theirs"` to take one side in bulk,
220
-
221
- then run `push_docs` to publish the resolution. If you have **uncommitted** local changes while the remote has moved, the pull reports `diverged` without touching anything — push your changes first, then pull again.
222
-
223
- > Read-only machines (`READ_ONLY=1`) can still `pull_docs` and `sync_status`, but `push_docs`, `sync_docs`, and `resolve_sync_conflict` are disabled.
224
-
225
- ## Portable tracked-file paths
226
-
227
- When docs are synced across machines, absolute tracked-file paths break: the codebase rarely lives at the same absolute location everywhere. To stay portable, tracked-file paths are stored **relative to a configurable code root** and resolved back to absolute paths on demand.
228
-
229
- Agents keep passing **absolute** paths to `create_doc` / `update_doc` (and in `[copy-from-codebase|…]` tags) as usual — the conversion to a stored relative path happens automatically server-side. No change in how the agent works.
230
-
231
- ### Configuration
232
-
233
- The code root is machine-local: it is stored in `local-state.json` in the data directory, never synced. Each machine has a **default root**, and each project can have **its own root**:
234
-
235
- - per project — in the project settings (project selector → gear → *Code root on this machine*), or with `set_code_root` and a `project`;
236
- - default — in Settings (*Default code root*), or with `set_code_root` without a `project`.
237
-
238
- Both apply at once, without restart. Resolution precedence: the project's own root → the machine default → none.
239
-
240
- `MCP_DOCUMENTOR_CODE_ROOT` seeds the default root the first time a data directory starts. Once a default root is stored, the variable is not read again.
241
-
242
- | Stored path | On read |
243
- |---|---|
244
- | Relative + code root configured | resolved against the root |
245
- | Relative + **no** root on this machine | reported as `unverifiable` (source absent) instead of a false `stale` |
246
- | Absolute (legacy, or file outside the root) | used as-is |
247
-
248
- The feature is **opt-in**: with no code root configured, behaviour is unchanged (paths stored and resolved as absolute).
249
-
250
- ### Migrating existing docs
251
-
252
- Docs created before this feature store absolute paths. Convert them in one shot with the bundled `mcp-documentor-migrate-paths`:
253
-
254
- ```bash
255
- MCP_DATA_DIR="/path/to/your/data" \
256
- npx -p @imenam/mcp-documentor mcp-documentor-migrate-paths --dry-run
257
- ```
258
-
259
- It uses the code roots stored for that data directory, so set them first.
260
-
261
- Review the report (`converted` / `kept absolute (outside root)` / `projects skipped`), then re-run without `--dry-run`. Finally publish with `sync_docs` (never commit the data dir by hand). The data dir is resolved like the server: `--data-dir` flag → `MCP_DATA_DIR` → package-relative fallback.
262
-
263
- This is a **one-time** operation, and must run on the machine whose OS/filesystem **created** the stored absolute paths (path semantics differ between Windows and POSIX). Once migrated and synced, every other machine just `pull_docs` and sets its own code roots — no migration needed.
264
-
265
- ## Tracking a GitHub branch instead of the working copy
266
-
267
- By default, freshness is measured against the **working copy on the machine running the server**. That has two consequences: switching git branches locally flips docs in and out of `stale` even though the deployed code never moved, and a server that has no copy of the codebase — typically an MCP gateway on another host — cannot verify anything at all.
268
-
269
- Pointing a project at a repository makes the documentation track **one branch of published code** instead. It is set per project in the GUI: open the project selector, hover a project and click its gear (or **Fix** on a project in error).
270
-
271
- - **Repository** — a search field: type part of a name (or `owner/name`) and matching repositories are suggested, private ones included when `MCP_DOCUMENTOR_CODE_TOKEN` can see them. Pasting `owner/repo` or a GitHub URL works too.
272
- - **Branch** — picked from the repository's branches; the commit it points at is shown.
273
- - **GitHub access** — shown read-only: the token is read from `MCP_DOCUMENTOR_CODE_TOKEN` only, and neither the GUI nor the agent can set it. It needs **Contents: Read** on the code repositories. Without it only public repositories can be read, within GitHub's unauthenticated rate limit (60 requests per hour).
274
- - Before saving, the project's docs are checked against the chosen source, so you see how many would turn outdated.
275
-
276
- The choice is applied at once, without restart. It is stored in `projects/<project>/project.json` next to the docs, so it is synced with them and every machine reads the same source. A project with no setting reads its local working copy. Agents can do the same with `get_code_source` and `set_code_source`.
277
-
278
- The project selector shows each project's source, and flags a project whose repository or branch cannot be found. Its docs are then reported `unverifiable`, never silently read from somewhere else.
279
-
280
- #### Migrating from the environment variables
281
-
282
- Earlier versions read `MCP_DOCUMENTOR_CODE_REPO`, `MCP_DOCUMENTOR_CODE_BRANCH` and `MCP_DOCUMENTOR_CODE_ROOTS`. On the first start of this version, each machine imports them once:
283
-
284
- - every project named by `MCP_DOCUMENTOR_CODE_REPO` (single spec or JSON map, `#branch` suffix or `MCP_DOCUMENTOR_CODE_BRANCH`) gets that source in its settings — unless it already has one, for instance pulled from another machine;
285
- - every project named by `MCP_DOCUMENTOR_CODE_ROOTS` gets that code root on this machine, unless it already has one.
286
-
287
- The GUI then shows a report of what each project became and which entries were not imported (unknown project, invalid spec). It can be reopened from Settings. From then on these variables are no longer read: remove them from the MCP configuration, the server only logs that they are ignored. `MCP_DOCUMENTOR_CODE_TOKEN` is not migrated — it stays the only place the token is set — and `MCP_DOCUMENTOR_CODE_ROOT` only seeds the default root (see above).
288
-
289
- ### What changes once a project tracks a branch
290
-
291
- | | Local working copy (default) | GitHub branch |
292
- |---|---|---|
293
- | Sees uncommitted edits | yes | no — only what is pushed |
294
- | Affected by `git checkout` | yes | no |
295
- | Needs the codebase on the server | yes | no |
296
- | `git_diff_doc` baseline | the commit the snapshot was taken at, else the last one before its date | the blob the snapshot was taken from, else its commit, else the last one before its date |
297
-
298
- Every report names the source it used (`Code source: GitHub you/my-repo@main`), so a verdict is never ambiguous. If the source cannot be reached — network, auth, rate limit — the status is `unverifiable` with the reason attached, **never** `stale`.
299
-
300
- ### Paths and migration
301
-
302
- Stored tracked-file paths are already relative to the code root, and a code root is normally the repository root — so **existing docs keep working as-is**, with no migration and no mass re-snapshot. Two details make the switch seamless:
303
-
304
- - Hashes are computed on line-ending-normalised content, so a snapshot taken from a CRLF checkout still matches the LF blob in the repository. Hashes stored by earlier versions are still recognised.
305
- - Absolute paths sent by an agent are reduced by the code root when one is configured, and otherwise matched against the repository tree by their longest unambiguous suffix (a bare filename is only accepted when the repository holds exactly one file by that name).
306
-
307
- Docs snapshotted before the switch carry no source commit: `check_freshness` works immediately, and `git_diff_doc` falls back on the last commit before the snapshot date. Whatever its origin, a baseline is checked against the hash stored at the snapshot; when it does not match — typically after a rebase that rewrote commit dates — the diff is reported impossible, and one `refresh_file_snapshot` anchors the file on its blob, which survives rebases.
308
-
309
- ### API usage
310
-
311
- Freshness is **never computed in the background** — only when something asks for it: a `check_freshness` / `list_docs` / `get_doc` call, or the GUI loading a project's list. So the cost is bounded per request, not per unit of time.
312
-
313
- Within one such request, the branch head is resolved **once** and every tracked file is compared against that same commit — no mixed verdict if someone pushes mid-scan. The next request resolves it again, so a push is visible immediately: there is no staleness window to configure.
314
-
315
- Resolving the branch head is a conditional request: while the branch has not moved, GitHub answers `304 Not Modified`, which does not count against the rate limit of an authenticated token.
316
-
317
- Everything else is content-addressed: a commit's tree is cached by its sha for the life of the process, and a file's bytes by their blob sha on disk, in `<data dir>/blob-cache/` (machine-local, excluded from sync) — each version of a file is downloaded once per machine, across restarts. Each tracked file also records the blob sha it was snapshotted at, so an unchanged file is proven untouched without being downloaded at all. A full scan of an unchanged repository costs about two API calls — not one per file.
318
-
319
- ## Data Storage
320
-
321
- By default, data lives under **`.docs-data`** next to the resolved package root (the folder that contains `package.json` for the running install). Set **`MCP_DATA_DIR`** to use another directory. Contents include projects, SQLite/embeddings, and logs under `.docs-data/logs`. This tree is **not** part of the npm package; it is created at runtime.
322
-
323
- There is no server-side "active project": every MCP tool call names its project, and the GUI's selector only changes what that browser displays (kept in the URL and in `localStorage`). The open and closed folders of each project's navigation tree are also kept in `localStorage`, so a reload restores them. A legacy `currentProject` left in `config.json` is dropped on first read. GitHub sync settings live in environment variables, not on disk.
324
-
325
- ## License
326
-
327
- ISC
1
+ # @imenam/mcp-documentor
2
+
3
+ An MCP server to create, manage, and search project documentation — with staleness tracking and optional semantic search.
4
+
5
+ Connect it to Cursor, Claude Desktop, or any MCP-compatible agent to let your AI document, browse and verify your codebase docs without leaving the chat.
6
+
7
+ ## Features
8
+
9
+ - **Create & update docs** — write markdown documentation entries linked to source files; prefer partial edits via `patch_doc`
10
+ - **Staleness tracking** — automatically detects when tracked files have changed since the last doc update
11
+ - **Portable code references** — tracked-file paths are stored relative to a code root set per machine and per project (GUI or `set_code_root`), so synced docs resolve their source files on any machine regardless of where the repos live
12
+ - **Track a branch, not a checkout** — point a project at a GitHub branch from its settings in the GUI and freshness is measured against *published* code: switching branches or editing without committing no longer makes docs look stale, and a server with no copy of the codebase (an MCP gateway) can still verify them
13
+ - **Semantic search** — find relevant docs by meaning (requires an OpenAI API key)
14
+ - **Multi-project support** — organize docs into separate projects; every MCP tool names the project it targets, and the GUI's project selector is a per-browser view preference
15
+ - **GUI** — built-in web interface, either behind the proxy (`PROXY_URL`) or served directly on a local port (`APP_PORT`); with neither, the GUI worker is not started. Navigation runs from an icon rail on the left: the current project's badge (click to switch projects or create one), then Documentation, Search (`Ctrl/⌘ + K`, current or all projects), Outdated, Recent, Export bundles, Publications and Logs, with Sync and Settings at the bottom. Each destination fills the panel next to the rail; the panel's hide button, or a second click on the active icon, folds it away. Drag the panel's right edge to resize it (double-click resets the width); the browser remembers the width. On wide screens the whole interface is capped at 1600 px and centred. The details panel on the right of a document (`Ctrl/⌘ + \`) is open by default from 1280 px wide; showing or hiding it by hand is remembered across reloads until the window crosses that width. Badges show the outdated docs, pending commits and the day's failed GitHub calls. Each open tab polls `GET /api/changes` every 3 seconds and every request is answered immediately: no connection stays open, so many tabs can share the proxy's origin, and changes made by the agent show up within 3 seconds. After a GUI worker restart, or when it has fallen more than 200 changes behind, the tab reloads the server state (`GET /api/initial-state`) and the list of the project it shows
16
+ - **Proxy-aware** — integrates with the [proxy-setup](https://github.com/Imena/proxy-setup) for GUI exposure
17
+ - **Links & structure** — `check_links`, `get_doc_tree`, `move_doc`, and `git_diff_doc` for maintenance workflows
18
+ - **Image support** — upload images to a project's assets via `upload_image` (local file path) or directly from the GUI editor; images are stored per-project and rendered inline in the viewer and in PDF exports
19
+ - **GitHub sync** — share your documentation across machines by backing the data directory with a private GitHub repo; the repo/token are fixed by the administrator via environment variables, and the agent can only `pull_docs` / `push_docs` / `sync_docs`
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npx -y @imenam/mcp-documentor
25
+ ```
26
+
27
+ Or install globally:
28
+
29
+ ```bash
30
+ npm install -g @imenam/mcp-documentor
31
+ ```
32
+
33
+ Global install exposes:
34
+
35
+ - `mcp-documentor` — MCP server
36
+ - `mcp-documentor-migrate` — CLI helper for migrating inline doc links (see `scripts/migrate-links.js`)
37
+ - `mcp-documentor-migrate-paths` — one-shot migration of tracked-file paths from absolute to code-root-relative (see [Portable tracked-file paths](#portable-tracked-file-paths))
38
+
39
+ ## MCP Configuration
40
+
41
+ ### Quick setup
42
+
43
+ From the repo you want to document, generate a ready-to-fill `.mcp.json` in the current directory:
44
+
45
+ ```bash
46
+ npx @imenam/mcp-documentor --claude-setup-mcp
47
+ ```
48
+
49
+ It creates the `mcp-documentor` entry with the usual environment variables as empty placeholders —
50
+ an empty variable is simply inactive, so the generated config runs as-is and you only fill in what
51
+ you need. An existing `.mcp.json` is merged, other servers are preserved, and an existing
52
+ `mcp-documentor` entry is only replaced when `--force` is passed.
53
+
54
+ Two flags let you pick how the GUI is exposed straight from the command line, instead of editing
55
+ the JSON afterwards:
56
+
57
+ ```bash
58
+ # GUI behind the central proxy
59
+ npx @imenam/mcp-documentor --claude-setup-mcp --proxy_url=http://localhost:3000
60
+
61
+ # GUI served directly, no proxy: http://localhost:4500
62
+ npx @imenam/mcp-documentor --claude-setup-mcp --app_port=4500
63
+ ```
64
+
65
+ The two flags are mutually exclusive — passing both fails.
66
+
67
+ ### Manual setup
68
+
69
+ Add to your MCP client config (e.g. Cursor `mcp.json`):
70
+
71
+ ```json
72
+ {
73
+ "mcpServers": {
74
+ "mcp-documentor": {
75
+ "command": "npx",
76
+ "args": ["-y", "@imenam/mcp-documentor"],
77
+ "env": {
78
+ "OPENAI_API_KEY": "sk-..."
79
+ }
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ ### Environment Variables
86
+
87
+ | Variable | Required | Description |
88
+ |---|---|---|
89
+ | `OPENAI_API_KEY` | No | Enables semantic search via OpenAI embeddings (`search_docs`) |
90
+ | `MCP_DOCUMENTOR_OPENAI_MODEL` | No | Embedding model (default: `text-embedding-3-small`) |
91
+ | `PROXY_URL` | No | URL of the proxy server: it allocates the port and mounts the GUI under `APP_PATH` |
92
+ | `APP_PORT` | No | Local port used **when `PROXY_URL` is unset**: the GUI is served directly at `http://localhost:<port>`, at the root. Lets you use the interface without the central hub. `PROXY_URL` wins if both are set; with neither, the GUI is disabled and the MCP tools still work |
93
+ | `APP_GROUP` | No | Groups the app under a collapsible section (folded by default) in the proxy dashboard. Apps sharing the same value appear together; if unset, the app lands in the default "ungrouped" section |
94
+ | `MCP_DATA_DIR` | No | Overrides the default data directory (see Data storage) |
95
+ | `MCP_LOG_DIR` | No | Overrides the log directory of both the server and the GUI worker (default: `<package>/.docs-data/logs`). Must be set in the MCP client config — the package `.env` is loaded after the logger starts |
96
+ | `MCP_DOCUMENTOR_CODE_ROOT` | No | Seeds the machine's default code root the first time a data directory starts (`--claude-setup-mcp` fills it with the current folder). Afterwards code roots are set in the GUI or with `set_code_root` (see [Portable tracked-file paths](#portable-tracked-file-paths)) |
97
+ | `MCP_DOCUMENTOR_CODE_TOKEN` | No | GitHub token used to read the code of projects tracking a GitHub branch. The only place it is set: neither the GUI nor the agent can change it |
98
+ | `READ_ONLY` | No | When set to `1`/`true`/`yes`, disables all create/update/delete tools and GUI write actions (read-only mode) |
99
+ | `MCP_DOCUMENTOR_SYNC_REMOTE` | No | HTTPS URL of the private GitHub repo to sync with; enables GitHub sync when set |
100
+ | `MCP_DOCUMENTOR_SYNC_BRANCH` | No | Branch of the documentation sync repo to push/pull docs on (default: `main`) |
101
+ | `MCP_DOCUMENTOR_SYNC_TOKEN` | No | Fine-grained GitHub PAT used for HTTPS auth (see GitHub sync) |
102
+ | `SYNC_PULL_ON_START` | No | Set to `false` to disable the automatic pull at server startup |
103
+
104
+ Optional `.env` at the package root (directory containing `package.json` discovered from the installed binary) is loaded when present.
105
+
106
+ ## Available Tools
107
+
108
+ Every tool that targets a specific documentation entry takes a **mandatory `project`**. It is never
109
+ guessed and there is no "active project" to fall back to: resolving a doc path across projects is
110
+ what made operations silently hit the wrong project when the same path existed in several. Tools
111
+ that work on a whole project (`list_docs`, `get_doc_tree`, `check_freshness` without a `path`,
112
+ `reindex_embeddings`) keep `project` optional, since omitting it means "all projects".
113
+
114
+ | Tool | Description |
115
+ |---|---|
116
+ | `create_doc` | Create a new documentation entry linked to source files (`project` required) |
117
+ | `get_doc` | Retrieve a documentation entry with full content (`project` required) |
118
+ | `get_doc_metadata` | Get metadata and freshness status without loading content (`project` required) |
119
+ | `list_docs` | List docs with staleness indicators (optionally filter by project) |
120
+ | `get_doc_tree` | Return folder/doc structure for a project (or all projects) |
121
+ | `update_doc` | Fully replace content and/or metadata (`project` required; prefer `patch_doc` for small edits) |
122
+ | `patch_doc` | Search-and-replace a fragment of a doc’s content; the text must occur once unless `replace_all` is set (`project` required) |
123
+ | `move_doc` | Change a doc’s path (`new_path`) and/or project (`new_project`); links are rewritten across all projects, bare within their own project (`project` required) |
124
+ | `delete_doc` | Permanently delete a documentation entry (`project` required) |
125
+ | `create_project` | Create an empty project, optionally with its code source (`code_source`, as `set_code_source`) |
126
+ | `delete_project` | Delete a project; refused while it holds any doc, image or other file besides its settings |
127
+ | `search_docs` | Semantic search over docs (natural language; requires OpenAI) |
128
+ | `check_freshness` | Staleness for one doc (`project` required) or a full project scan |
129
+ | `refresh_file_snapshot` | Mark tracked files as up-to-date without editing the doc (`project` required) |
130
+ | `git_diff_doc` | Diff of changed tracked files between their snapshot content and the current one (`project` required). Files whose tracked sections can no longer be located (anchors lost) are included and diffed whole, with a warning |
131
+ | `check_links` | Validate `related_docs`, `doc://` links, and `#heading` anchors (`project` required) |
132
+ | `list_projects` | List all documentation projects |
133
+ | `upload_image` | Upload an image from a local file path to a project's assets (`project` required); returns the markdown link to insert |
134
+ | `sync_status` | Show branch, ahead/behind counts, conflicted files, and uncommitted changes |
135
+ | `pull_docs` | Pull updates from the remote (defensive fast-forward; reindexes new docs) |
136
+ | `push_docs` | Commit local doc changes and push them to the remote |
137
+ | `sync_docs` | Full sync: pull then push (push skipped if the pull reveals a conflict) |
138
+ | `resolve_sync_conflict` | Bulk-resolve merge conflicts by keeping `mine` or `theirs`, then stage |
139
+ | `get_code_source` | Show each project's code source (local working copy or GitHub branch) and its code root on this machine |
140
+ | `set_code_source` | Make a project read its local working copy or one branch of a GitHub repository (checked before saving) |
141
+ | `set_code_root` | Set the code root of a project, or the machine default, on this machine |
142
+
143
+ ## Images in documents
144
+
145
+ Images can be embedded in any documentation entry using standard markdown syntax:
146
+
147
+ ```markdown
148
+ ![alt text](/api/images/my-project/screenshot.png) <!-- local upload -->
149
+ ![alt text](https://example.com/diagram.png) <!-- external URL -->
150
+ ```
151
+
152
+ **Via the MCP tool** — use `upload_image` with the absolute path of the file on the server:
153
+
154
+ ```json
155
+ {
156
+ "filename": "screenshot.png",
157
+ "filePath": "/absolute/path/to/screenshot.png",
158
+ "alt": "Architecture diagram"
159
+ }
160
+ ```
161
+
162
+ The tool copies the file into `.docs-data/projects/{project}/assets/` and returns the ready-to-use markdown snippet.
163
+
164
+ **Via the GUI editor** — click the **🖼 Image** button in the editor toolbar to open the image panel:
165
+ - **Upload tab** — pick a local file; it is uploaded automatically and the markdown link is inserted at the cursor position
166
+ - **URL tab** — paste any external image URL; the markdown link is inserted at the cursor position
167
+ - **Project images gallery** — click any previously uploaded image to re-insert it
168
+
169
+ ## Publishing documents
170
+
171
+ A document can be shared, read-only, with anyone who holds its link — no account, no session. Publishing is a user action in the GUI only: no MCP tool exposes it, so an agent can neither publish nor unpublish.
172
+
173
+ - **Publish** — the **Publish** button of the document toolbar freezes the current version, with an optional version note, and creates the link. The button then reads **Published**; its popover gives the link (copy, open), the publication date, the frozen version and the note. An amber dot on the button means the document changed since it was published.
174
+ - **Update publication** — freezes the current version under the **same** link, with a new optional note. Offered only when the document changed.
175
+ - **Unpublish** — revokes the link after a confirmation: it answers 404 at once. Publishing again creates a new link.
176
+ - **Publications page** — the globe of the rail lists every published document of the current project: version note, publication date, state (*Up to date*, *Changed · v12 → v14*, or *Document removed*), with Update, copy, open and Unpublish on each row. *Changed* keeps only the publications that lag behind their document.
177
+
178
+ What the reader sees is the viewer's rendering (code highlighting, Mermaid, tables, images) without any action. An *On this page* table of contents of the document's headings sits on the right and follows the scroll; it is hidden below 1024 px wide. Web links stay active and open in a new tab; links to other documents become plain text; in-page anchors work. Images uploaded to the project are copied into the snapshot when it is frozen, so the page never changes until the publication is updated.
179
+
180
+ **The link** is a *capability URL*: its last segment is a 32-byte random token, and holding it is the only authorization. The page is served under `public/<token>/` next to the GUI, or under `/_pub/<route of the GUI>/<token>/` behind the mcp-gateway-manager hub, which relays that prefix without a session. Every public response carries `X-Robots-Tag: noindex, nofollow, noarchive` and `Referrer-Policy: no-referrer` (the token never leaks to the sites the reader opens), and the page has a strict Content-Security-Policy. The page embeds the frozen document: it makes no API call.
181
+
182
+ Publications live in `<data dir>/publications/` — `index.json` plus one snapshot directory per publication. The directory is machine-local and excluded from sync: a link is served by the machine that created it. A document moved with `move_doc` keeps its publication; a deleted one keeps serving its frozen snapshot until it is unpublished. `READ_ONLY` disables publishing, updating and unpublishing.
183
+
184
+ ## Syncing across machines with GitHub
185
+
186
+ You can share documentation between several machines by backing the data directory with a **private** GitHub repository. The approach is git-based: only the human-readable markdown, assets, and the shared project list are versioned. Derived state (embeddings) is regenerated locally, and version history stays local to each machine.
187
+
188
+ ### What is and isn't synced
189
+
190
+ | Synced (versioned) | Local only (gitignored) |
191
+ |---|---|
192
+ | `projects/**` (markdown + assets) | `embeddings.db` (regenerated after pull) |
193
+ | `config.json` (project list) | `history.db` (per-machine version history) |
194
+ | | `publications/` (public links served by this machine) |
195
+
196
+ ### Setup
197
+
198
+ The sync target is **administrator-controlled**: the repository, branch and token are read from environment variables and **cannot be set or changed by the agent**. The agent can only pull/push against whatever repo you configured.
199
+
200
+ 1. Create an **empty private repo** on GitHub (e.g. `my-docs`).
201
+ 2. Create a **fine-grained PAT** scoped to that single repo (Repository permissions → **Contents: Read and write**).
202
+ 3. Add the sync environment variables to your MCP client config, per machine:
203
+
204
+ ```jsonc
205
+ {
206
+ "mcpServers": {
207
+ "mcp-documentor": {
208
+ "command": "npx",
209
+ "args": ["-y", "@imenam/mcp-documentor"],
210
+ "env": {
211
+ "MCP_DOCUMENTOR_SYNC_REMOTE": "https://github.com/<you>/my-docs.git",
212
+ "MCP_DOCUMENTOR_SYNC_BRANCH": "main", // branch of the docs sync repo, optional, defaults to "main"
213
+ "MCP_DOCUMENTOR_SYNC_TOKEN": "github_pat_..."
214
+ }
215
+ }
216
+ }
217
+ }
218
+ ```
219
+
220
+ The token is injected into the `origin` URL inside `.git/config` only (which git never commits) — it is never pushed.
221
+
222
+ ### Daily workflow
223
+
224
+ - `sync_docs` — pull then push in one step (recommended).
225
+ - `pull_docs` / `push_docs` — granular control.
226
+ - `sync_status` — inspect ahead/behind, conflicts, and uncommitted changes.
227
+
228
+ By default the server also performs a **defensive pull at startup**: it only fast-forwards a clean working tree and never overwrites local changes (disable with `SYNC_PULL_ON_START=false`). On the first run, a machine with no local data simply clones the remote.
229
+
230
+ ### Conflicts
231
+
232
+ Because docs are plain markdown, merge conflicts are readable. When `pull_docs` reports a conflict:
233
+
234
+ - edit the conflicted `.md` files directly (remove the `<<<<<<<` / `>>>>>>>` markers), **or**
235
+ - run `resolve_sync_conflict` with `strategy: "mine"` or `"theirs"` to take one side in bulk,
236
+
237
+ then run `push_docs` to publish the resolution. If you have **uncommitted** local changes while the remote has moved, the pull reports `diverged` without touching anything — push your changes first, then pull again.
238
+
239
+ > Read-only machines (`READ_ONLY=1`) can still `pull_docs` and `sync_status`, but `push_docs`, `sync_docs`, and `resolve_sync_conflict` are disabled.
240
+
241
+ ## Portable tracked-file paths
242
+
243
+ When docs are synced across machines, absolute tracked-file paths break: the codebase rarely lives at the same absolute location everywhere. To stay portable, tracked-file paths are stored **relative to a configurable code root** and resolved back to absolute paths on demand.
244
+
245
+ Agents keep passing **absolute** paths to `create_doc` / `update_doc` (and in `[copy-from-codebase|…]` tags) as usual — the conversion to a stored relative path happens automatically server-side. No change in how the agent works.
246
+
247
+ ### Configuration
248
+
249
+ The code root is machine-local: it is stored in `local-state.json` in the data directory, never synced. Each machine has a **default root**, and each project can have **its own root**:
250
+
251
+ - per project — in the project settings (project selector → gear → *Code root on this machine*), or with `set_code_root` and a `project`;
252
+ - default — in Settings (*Default code root*), or with `set_code_root` without a `project`.
253
+
254
+ Both apply at once, without restart. Resolution precedence: the project's own root → the machine default → none.
255
+
256
+ `MCP_DOCUMENTOR_CODE_ROOT` seeds the default root the first time a data directory starts. Once a default root is stored, the variable is not read again.
257
+
258
+ | Stored path | On read |
259
+ |---|---|
260
+ | Relative + code root configured | resolved against the root |
261
+ | Relative + **no** root on this machine | reported as `unverifiable` (source absent) instead of a false `stale` |
262
+ | Absolute (legacy, or file outside the root) | used as-is |
263
+
264
+ The feature is **opt-in**: with no code root configured, behaviour is unchanged (paths stored and resolved as absolute).
265
+
266
+ ### Migrating existing docs
267
+
268
+ Docs created before this feature store absolute paths. Convert them in one shot with the bundled `mcp-documentor-migrate-paths`:
269
+
270
+ ```bash
271
+ MCP_DATA_DIR="/path/to/your/data" \
272
+ npx -p @imenam/mcp-documentor mcp-documentor-migrate-paths --dry-run
273
+ ```
274
+
275
+ It uses the code roots stored for that data directory, so set them first.
276
+
277
+ Review the report (`converted` / `kept absolute (outside root)` / `projects skipped`), then re-run without `--dry-run`. Finally publish with `sync_docs` (never commit the data dir by hand). The data dir is resolved like the server: `--data-dir` flag → `MCP_DATA_DIR` → package-relative fallback.
278
+
279
+ This is a **one-time** operation, and must run on the machine whose OS/filesystem **created** the stored absolute paths (path semantics differ between Windows and POSIX). Once migrated and synced, every other machine just `pull_docs` and sets its own code roots — no migration needed.
280
+
281
+ ## Tracking a GitHub branch instead of the working copy
282
+
283
+ By default, freshness is measured against the **working copy on the machine running the server**. That has two consequences: switching git branches locally flips docs in and out of `stale` even though the deployed code never moved, and a server that has no copy of the codebase — typically an MCP gateway on another host — cannot verify anything at all.
284
+
285
+ Pointing a project at a repository makes the documentation track **one branch of published code** instead. It is set per project in the GUI: open the project selector, hover a project and click its gear (or **Fix** on a project in error).
286
+
287
+ - **Repository** — a search field: type part of a name (or `owner/name`) and matching repositories are suggested, private ones included when `MCP_DOCUMENTOR_CODE_TOKEN` can see them. Pasting `owner/repo` or a GitHub URL works too.
288
+ - **Branch** — picked from the repository's branches; the commit it points at is shown.
289
+ - **GitHub access** — shown read-only: the token is read from `MCP_DOCUMENTOR_CODE_TOKEN` only, and neither the GUI nor the agent can set it. It needs **Contents: Read** on the code repositories. Without it only public repositories can be read, within GitHub's unauthenticated rate limit (60 requests per hour).
290
+ - Before saving, the project's docs are checked against the chosen source, so you see how many would turn outdated.
291
+
292
+ The choice is applied at once, without restart. It is stored in `projects/<project>/project.json` next to the docs, so it is synced with them and every machine reads the same source. A project with no setting reads its local working copy. Agents can do the same with `get_code_source` and `set_code_source`.
293
+
294
+ The project selector shows each project's source, and flags a project whose repository or branch cannot be found. Its docs are then reported `unverifiable`, never silently read from somewhere else.
295
+
296
+ #### Migrating from the environment variables
297
+
298
+ Earlier versions read `MCP_DOCUMENTOR_CODE_REPO`, `MCP_DOCUMENTOR_CODE_BRANCH` and `MCP_DOCUMENTOR_CODE_ROOTS`. On the first start of this version, each machine imports them once:
299
+
300
+ - every project named by `MCP_DOCUMENTOR_CODE_REPO` (single spec or JSON map, `#branch` suffix or `MCP_DOCUMENTOR_CODE_BRANCH`) gets that source in its settings — unless it already has one, for instance pulled from another machine;
301
+ - every project named by `MCP_DOCUMENTOR_CODE_ROOTS` gets that code root on this machine, unless it already has one.
302
+
303
+ The GUI then shows a report of what each project became and which entries were not imported (unknown project, invalid spec). It can be reopened from Settings. From then on these variables are no longer read: remove them from the MCP configuration, the server only logs that they are ignored. `MCP_DOCUMENTOR_CODE_TOKEN` is not migrated — it stays the only place the token is set — and `MCP_DOCUMENTOR_CODE_ROOT` only seeds the default root (see above).
304
+
305
+ ### What changes once a project tracks a branch
306
+
307
+ | | Local working copy (default) | GitHub branch |
308
+ |---|---|---|
309
+ | Sees uncommitted edits | yes | no — only what is pushed |
310
+ | Affected by `git checkout` | yes | no |
311
+ | Needs the codebase on the server | yes | no |
312
+ | `git_diff_doc` baseline | the commit the snapshot was taken at, else the last one before its date | the blob the snapshot was taken from, else its commit, else the last one before its date |
313
+
314
+ Every report names the source it used (`Code source: GitHub you/my-repo@main`), so a verdict is never ambiguous. If the source cannot be reached — network, auth, rate limit — the status is `unverifiable` with the reason attached, **never** `stale`.
315
+
316
+ ### Paths and migration
317
+
318
+ Stored tracked-file paths are already relative to the code root, and a code root is normally the repository root — so **existing docs keep working as-is**, with no migration and no mass re-snapshot. Two details make the switch seamless:
319
+
320
+ - Hashes are computed on line-ending-normalised content, so a snapshot taken from a CRLF checkout still matches the LF blob in the repository. Hashes stored by earlier versions are still recognised.
321
+ - Absolute paths sent by an agent are reduced by the code root when one is configured, and otherwise matched against the repository tree by their longest unambiguous suffix (a bare filename is only accepted when the repository holds exactly one file by that name).
322
+
323
+ Docs snapshotted before the switch carry no source commit: `check_freshness` works immediately, and `git_diff_doc` falls back on the last commit before the snapshot date. Whatever its origin, a baseline is checked against the hash stored at the snapshot; when it does not match — typically after a rebase that rewrote commit dates — the diff is reported impossible, and one `refresh_file_snapshot` anchors the file on its blob, which survives rebases.
324
+
325
+ ### API usage
326
+
327
+ Freshness is **never computed in the background** — only when something asks for it: a `check_freshness` / `list_docs` / `get_doc` call, or the GUI loading a project's list. So the cost is bounded per request, not per unit of time.
328
+
329
+ Within one such request, the branch head is resolved **once** and every tracked file is compared against that same commit — no mixed verdict if someone pushes mid-scan. The next request resolves it again, so a push is visible immediately: there is no staleness window to configure.
330
+
331
+ Resolving the branch head is a conditional request: while the branch has not moved, GitHub answers `304 Not Modified`, which does not count against the rate limit of an authenticated token.
332
+
333
+ Everything else is content-addressed: a commit's tree is cached by its sha for the life of the process, and a file's bytes by their blob sha on disk, in `<data dir>/blob-cache/` (machine-local, excluded from sync) — each version of a file is downloaded once per machine, across restarts. Each tracked file also records the blob sha it was snapshotted at, so an unchanged file is proven untouched without being downloaded at all. A full scan of an unchanged repository costs about two API calls — not one per file.
334
+
335
+ ## Data Storage
336
+
337
+ By default, data lives under **`.docs-data`** next to the resolved package root (the folder that contains `package.json` for the running install). Set **`MCP_DATA_DIR`** to use another directory. Contents include projects, SQLite/embeddings, and logs under `.docs-data/logs`. This tree is **not** part of the npm package; it is created at runtime.
338
+
339
+ There is no server-side "active project": every MCP tool call names its project, and the GUI's selector only changes what that browser displays (kept in the URL and in `localStorage`). The open and closed folders of each project's navigation tree are also kept in `localStorage`, so a reload restores them. A legacy `currentProject` left in `config.json` is dropped on first read. GitHub sync settings live in environment variables, not on disk.
340
+
341
+ ## License
342
+
343
+ ISC