@imenam/mcp-documentor 1.0.85 → 1.0.86

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 (78) hide show
  1. package/README.md +323 -329
  2. package/dist/shared/read-only.js +1 -0
  3. package/dist/src/cli/setup-mcp.js +2 -5
  4. package/dist/src/core/code-source.d.ts +33 -22
  5. package/dist/src/core/code-source.js +92 -69
  6. package/dist/src/core/github-code-api.d.ts +28 -0
  7. package/dist/src/core/github-code-api.js +92 -31
  8. package/dist/src/core/local-state.d.ts +49 -0
  9. package/dist/src/core/local-state.js +78 -0
  10. package/dist/src/core/migrate-code-sources.d.ts +17 -0
  11. package/dist/src/core/migrate-code-sources.js +157 -0
  12. package/dist/src/core/path-resolver.d.ts +29 -9
  13. package/dist/src/core/path-resolver.js +18 -42
  14. package/dist/src/core/project-settings.d.ts +21 -0
  15. package/dist/src/core/project-settings.js +63 -0
  16. package/dist/src/gui-worker.js +70 -2
  17. package/dist/src/index.js +160 -7
  18. package/dist/src/tools/check-freshness.d.ts +2 -1
  19. package/dist/src/tools/check-freshness.js +1 -2
  20. package/dist/src/tools/code-sources.d.ts +102 -0
  21. package/dist/src/tools/code-sources.js +260 -0
  22. package/dist/src/version.d.ts +1 -1
  23. package/dist/src/version.js +16 -2
  24. package/gui/dist/assets/{arc-CdXRzMBT.js → arc-DBgCQU2O.js} +1 -1
  25. package/gui/dist/assets/{architectureDiagram-3BPJPVTR-CGUnwe_I.js → architectureDiagram-3BPJPVTR-BIPBAzZr.js} +1 -1
  26. package/gui/dist/assets/{blockDiagram-GPEHLZMM-BCI5_vQE.js → blockDiagram-GPEHLZMM-BE9WwuyF.js} +1 -1
  27. package/gui/dist/assets/{c4Diagram-AAUBKEIU-Bm9a6HGg.js → c4Diagram-AAUBKEIU-V1_4ktIt.js} +1 -1
  28. package/gui/dist/assets/channel-B_cci0fU.js +1 -0
  29. package/gui/dist/assets/{chunk-2J33WTMH-c7xNwExL.js → chunk-2J33WTMH-DskG-Kmf.js} +1 -1
  30. package/gui/dist/assets/{chunk-4BX2VUAB-CmcU4Q0o.js → chunk-4BX2VUAB-DaPhidEI.js} +1 -1
  31. package/gui/dist/assets/{chunk-55IACEB6-K8rRjNKN.js → chunk-55IACEB6-C9r6WDFM.js} +1 -1
  32. package/gui/dist/assets/{chunk-727SXJPM-DMPnL1bv.js → chunk-727SXJPM-BA-aC8US.js} +1 -1
  33. package/gui/dist/assets/{chunk-AQP2D5EJ-BeRIIft8.js → chunk-AQP2D5EJ-ByBp3whV.js} +1 -1
  34. package/gui/dist/assets/{chunk-FMBD7UC4-CN4It-fp.js → chunk-FMBD7UC4-CzqUByx7.js} +1 -1
  35. package/gui/dist/assets/{chunk-ND2GUHAM-Dy-ZqPua.js → chunk-ND2GUHAM-C0_B2ryQ.js} +1 -1
  36. package/gui/dist/assets/{chunk-QZHKN3VN-CaV4seJw.js → chunk-QZHKN3VN-B3eEwjDh.js} +1 -1
  37. package/gui/dist/assets/classDiagram-4FO5ZUOK-BqlcJ5BP.js +1 -0
  38. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-BqlcJ5BP.js +1 -0
  39. package/gui/dist/assets/{cose-bilkent-S5V4N54A-B1qt_j1Z.js → cose-bilkent-S5V4N54A-D_i0I2pU.js} +1 -1
  40. package/gui/dist/assets/{dagre-BM42HDAG-4x5JEBQG.js → dagre-BM42HDAG-DtEVPK_z.js} +1 -1
  41. package/gui/dist/assets/{diagram-2AECGRRQ-Q7TvjRjv.js → diagram-2AECGRRQ-BftnXa5w.js} +1 -1
  42. package/gui/dist/assets/{diagram-5GNKFQAL-Dguj26bf.js → diagram-5GNKFQAL-Dh9-kl_u.js} +1 -1
  43. package/gui/dist/assets/{diagram-KO2AKTUF-DBv6MoZF.js → diagram-KO2AKTUF-CvB0BWCa.js} +1 -1
  44. package/gui/dist/assets/{diagram-LMA3HP47-mfVXwL5q.js → diagram-LMA3HP47-CDy-J1uy.js} +1 -1
  45. package/gui/dist/assets/{diagram-OG6HWLK6-Bn3_yQWG.js → diagram-OG6HWLK6-iY9FwkIF.js} +1 -1
  46. package/gui/dist/assets/{erDiagram-TEJ5UH35-Bp4wnBw7.js → erDiagram-TEJ5UH35-BT2_IX8D.js} +1 -1
  47. package/gui/dist/assets/{flowDiagram-I6XJVG4X-CGk72E1Q.js → flowDiagram-I6XJVG4X-DaixWyMx.js} +1 -1
  48. package/gui/dist/assets/{ganttDiagram-6RSMTGT7-C1WQIgpu.js → ganttDiagram-6RSMTGT7-DSo3cQPH.js} +1 -1
  49. package/gui/dist/assets/{gitGraphDiagram-PVQCEYII-DSIs1ZqI.js → gitGraphDiagram-PVQCEYII-BfFT7Lds.js} +1 -1
  50. package/gui/dist/assets/index-CxCC1gxu.css +1 -0
  51. package/gui/dist/assets/{index-T7ZLHNgs.js → index-DPGmAc6z.js} +107 -105
  52. package/gui/dist/assets/{infoDiagram-5YYISTIA-DzHdfk5Y.js → infoDiagram-5YYISTIA-BxbxNxe2.js} +1 -1
  53. package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-DLxqTmDU.js → ishikawaDiagram-YF4QCWOH-DpopzqZr.js} +1 -1
  54. package/gui/dist/assets/{journeyDiagram-JHISSGLW-DYUPmZnf.js → journeyDiagram-JHISSGLW-DrOlFzIk.js} +1 -1
  55. package/gui/dist/assets/{kanban-definition-UN3LZRKU-f6km2MDT.js → kanban-definition-UN3LZRKU-vH81lYXy.js} +1 -1
  56. package/gui/dist/assets/{linear-DHhAaAXH.js → linear-BipFpFah.js} +1 -1
  57. package/gui/dist/assets/{mindmap-definition-RKZ34NQL-nergVxV3.js → mindmap-definition-RKZ34NQL-DsTxCLgj.js} +1 -1
  58. package/gui/dist/assets/{pieDiagram-4H26LBE5-D4IJAdGz.js → pieDiagram-4H26LBE5-Bbq2ZWQU.js} +1 -1
  59. package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-B5yYdOsV.js → quadrantDiagram-W4KKPZXB-BdTSaEo4.js} +1 -1
  60. package/gui/dist/assets/{requirementDiagram-4Y6WPE33-BrhJp07G.js → requirementDiagram-4Y6WPE33-BndI8YZy.js} +1 -1
  61. package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-CUBA4e7F.js → sankeyDiagram-5OEKKPKP-ofBkVITf.js} +1 -1
  62. package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-DP0-rUie.js → sequenceDiagram-3UESZ5HK-Cjj2UrN9.js} +1 -1
  63. package/gui/dist/assets/{stateDiagram-AJRCARHV-_BnVcpam.js → stateDiagram-AJRCARHV-CzPrXUuz.js} +1 -1
  64. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-DAq4ObN9.js +1 -0
  65. package/gui/dist/assets/{timeline-definition-PNZ67QCA-crbYjsrq.js → timeline-definition-PNZ67QCA-Cz4X-IOO.js} +1 -1
  66. package/gui/dist/assets/{vennDiagram-CIIHVFJN-EOqENtE1.js → vennDiagram-CIIHVFJN-BwIgXJcZ.js} +1 -1
  67. package/gui/dist/assets/{wardley-L42UT6IY-B1FVq-_Y.js → wardley-L42UT6IY-keQS0aNZ.js} +1 -1
  68. package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-D1DZwIH_.js → wardleyDiagram-YWT4CUSO-DhykSgbD.js} +1 -1
  69. package/gui/dist/assets/{xychartDiagram-2RQKCTM6-B6Wr8_OJ.js → xychartDiagram-2RQKCTM6-DFTqc4cj.js} +1 -1
  70. package/gui/dist/index.html +2 -2
  71. package/package.json +2 -3
  72. package/scripts/migrate-paths-to-relative.js +196 -192
  73. package/gui/dist/assets/channel-f0BgDIcS.js +0 -1
  74. package/gui/dist/assets/classDiagram-4FO5ZUOK-tGvt4n1B.js +0 -1
  75. package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-tGvt4n1B.js +0 -1
  76. package/gui/dist/assets/index-QkCDSQ2A.css +0 -1
  77. package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-B5Vwq56z.js +0 -1
  78. package/scripts/generate-version.js +0 -20
package/README.md CHANGED
@@ -1,329 +1,323 @@
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 configurable code root (`MCP_DOCUMENTOR_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 (`MCP_DOCUMENTOR_CODE_REPO`) 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
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 | Base directory that tracked-file paths are stored relative to, keeping docs portable across machines (see [Portable tracked-file paths](#portable-tracked-file-paths)) |
97
- | `MCP_DOCUMENTOR_CODE_ROOTS` | No | JSON map of per-project code-root overrides, e.g. `{"my-proj":"/srv/my-proj"}` |
98
- | `MCP_DOCUMENTOR_CODE_REPO` | No | Track a **GitHub branch** instead of the local working copy. Either one spec applied to every project — `owner/repo`, a GitHub URL, optional `#branch` suffix — or a JSON map of per-project specs, e.g. `{"web":"you/web#main","legacy":"local"}` (the value `local` opts a project back out to its working copy). Setting it is what enables the mode (see [Tracking a GitHub branch](#tracking-a-github-branch-instead-of-the-working-copy)) |
99
- | `MCP_DOCUMENTOR_CODE_BRANCH` | No | Branch used for repositories carrying no `#branch` suffix (default: the repository's own default branch) |
100
- | `MCP_DOCUMENTOR_CODE_TOKEN` | No | GitHub token used to read the code; falls back to `MCP_DOCUMENTOR_SYNC_TOKEN` |
101
- | `READ_ONLY` | No | When set to `1`/`true`/`yes`, disables all create/update/delete tools and GUI write actions (read-only mode) |
102
- | `MCP_DOCUMENTOR_SYNC_REMOTE` | No | HTTPS URL of the private GitHub repo to sync with; enables GitHub sync when set |
103
- | `MCP_DOCUMENTOR_SYNC_BRANCH` | No | Branch of the documentation sync repo to push/pull docs on (default: `main`) |
104
- | `MCP_DOCUMENTOR_SYNC_TOKEN` | No | Fine-grained GitHub PAT used for HTTPS auth (see GitHub sync) |
105
- | `SYNC_PULL_ON_START` | No | Set to `false` to disable the automatic pull at server startup |
106
-
107
- Optional `.env` at the package root (directory containing `package.json` discovered from the installed binary) is loaded when present.
108
-
109
- ## Available Tools
110
-
111
- Every tool that targets a specific documentation entry takes a **mandatory `project`**. It is never
112
- guessed and there is no "active project" to fall back to: resolving a doc path across projects is
113
- what made operations silently hit the wrong project when the same path existed in several. Tools
114
- that work on a whole project (`list_docs`, `get_doc_tree`, `check_freshness` without a `path`,
115
- `reindex_embeddings`) keep `project` optional, since omitting it means "all projects".
116
-
117
- | Tool | Description |
118
- |---|---|
119
- | `create_doc` | Create a new documentation entry linked to source files (`project` required) |
120
- | `get_doc` | Retrieve a documentation entry with full content (`project` required) |
121
- | `get_doc_metadata` | Get metadata and freshness status without loading content (`project` required) |
122
- | `list_docs` | List docs with staleness indicators (optionally filter by project) |
123
- | `get_doc_tree` | Return folder/doc structure for a project (or all projects) |
124
- | `update_doc` | Fully replace content and/or metadata (`project` required; prefer `patch_doc` for small edits) |
125
- | `patch_doc` | Search-and-replace a fragment of a doc’s content (`project` required) |
126
- | `move_doc` | Change a doc’s path (folder/name) within its project (`project` required) |
127
- | `delete_doc` | Permanently delete a documentation entry (`project` required) |
128
- | `search_docs` | Semantic search over docs (natural language; requires OpenAI) |
129
- | `check_freshness` | Staleness for one doc (`project` required) or a full project scan |
130
- | `refresh_file_snapshot` | Mark tracked files as up-to-date without editing the doc (`project` required) |
131
- | `git_diff_doc` | Git diff for tracked files vs snapshot baseline (`project` required) |
132
- | `check_links` | Validate `related_docs`, `doc://` links, and `#heading` anchors (`project` required) |
133
- | `list_projects` | List all documentation projects |
134
- | `upload_image` | Upload an image from a local file path to a project's assets (`project` required); returns the markdown link to insert |
135
- | `sync_status` | Show branch, ahead/behind counts, conflicted files, and uncommitted changes |
136
- | `pull_docs` | Pull updates from the remote (defensive fast-forward; reindexes new docs) |
137
- | `push_docs` | Commit local doc changes and push them to the remote |
138
- | `sync_docs` | Full sync: pull then push (push skipped if the pull reveals a conflict) |
139
- | `resolve_sync_conflict` | Bulk-resolve merge conflicts by keeping `mine` or `theirs`, then stage |
140
-
141
- ## Images in documents
142
-
143
- Images can be embedded in any documentation entry using standard markdown syntax:
144
-
145
- ```markdown
146
- ![alt text](/api/images/my-project/screenshot.png) <!-- local upload -->
147
- ![alt text](https://example.com/diagram.png) <!-- external URL -->
148
- ```
149
-
150
- **Via the MCP tool** — use `upload_image` with the absolute path of the file on the server:
151
-
152
- ```json
153
- {
154
- "filename": "screenshot.png",
155
- "filePath": "/absolute/path/to/screenshot.png",
156
- "alt": "Architecture diagram"
157
- }
158
- ```
159
-
160
- The tool copies the file into `.docs-data/projects/{project}/assets/` and returns the ready-to-use markdown snippet.
161
-
162
- **Via the GUI editor** — click the **🖼 Image** button in the editor toolbar to open the image panel:
163
- - **Upload tab** — pick a local file; it is uploaded automatically and the markdown link is inserted at the cursor position
164
- - **URL tab** — paste any external image URL; the markdown link is inserted at the cursor position
165
- - **Project images gallery** — click any previously uploaded image to re-insert it
166
-
167
- ## Syncing across machines with GitHub
168
-
169
- 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.
170
-
171
- ### What is and isn't synced
172
-
173
- | Synced (versioned) | Local only (gitignored) |
174
- |---|---|
175
- | `projects/**` (markdown + assets) | `embeddings.db` (regenerated after pull) |
176
- | `config.json` (project list) | `history.db` (per-machine version history) |
177
-
178
- ### Setup
179
-
180
- 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.
181
-
182
- 1. Create an **empty private repo** on GitHub (e.g. `my-docs`).
183
- 2. Create a **fine-grained PAT** scoped to that single repo (Repository permissions → **Contents: Read and write**).
184
- 3. Add the sync environment variables to your MCP client config, per machine:
185
-
186
- ```jsonc
187
- {
188
- "mcpServers": {
189
- "mcp-documentor": {
190
- "command": "npx",
191
- "args": ["-y", "@imenam/mcp-documentor"],
192
- "env": {
193
- "MCP_DOCUMENTOR_SYNC_REMOTE": "https://github.com/<you>/my-docs.git",
194
- "MCP_DOCUMENTOR_SYNC_BRANCH": "main", // branch of the docs sync repo, optional, defaults to "main"
195
- "MCP_DOCUMENTOR_SYNC_TOKEN": "github_pat_..."
196
- }
197
- }
198
- }
199
- }
200
- ```
201
-
202
- The token is injected into the `origin` URL inside `.git/config` only (which git never commits) — it is never pushed.
203
-
204
- ### Daily workflow
205
-
206
- - `sync_docs` — pull then push in one step (recommended).
207
- - `pull_docs` / `push_docs` — granular control.
208
- - `sync_status` — inspect ahead/behind, conflicts, and uncommitted changes.
209
-
210
- 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.
211
-
212
- ### Conflicts
213
-
214
- Because docs are plain markdown, merge conflicts are readable. When `pull_docs` reports a conflict:
215
-
216
- - edit the conflicted `.md` files directly (remove the `<<<<<<<` / `>>>>>>>` markers), **or**
217
- - run `resolve_sync_conflict` with `strategy: "mine"` or `"theirs"` to take one side in bulk,
218
-
219
- 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.
220
-
221
- > Read-only machines (`READ_ONLY=1`) can still `pull_docs` and `sync_status`, but `push_docs`, `sync_docs`, and `resolve_sync_conflict` are disabled.
222
-
223
- ## Portable tracked-file paths
224
-
225
- 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.
226
-
227
- 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.
228
-
229
- ### Configuration
230
-
231
- Set the code root per machine (it is machine-local, so it lives in the environment, never in the synced data):
232
-
233
- ```jsonc
234
- {
235
- "env": {
236
- "MCP_DOCUMENTOR_CODE_ROOT": "/home/you/code/repos",
237
- // optional per-project overrides when repos don't share a common parent:
238
- "MCP_DOCUMENTOR_CODE_ROOTS": "{\"my-proj\":\"/srv/my-proj\"}"
239
- }
240
- }
241
- ```
242
-
243
- Resolution precedence: `MCP_DOCUMENTOR_CODE_ROOTS[project]` → `MCP_DOCUMENTOR_CODE_ROOT` → none.
244
-
245
- | Stored path | On read |
246
- |---|---|
247
- | Relative + code root configured | resolved against the root |
248
- | Relative + **no** root on this machine | reported as `unverifiable` (source absent) instead of a false `stale` |
249
- | Absolute (legacy, or file outside the root) | used as-is |
250
-
251
- The feature is **opt-in**: with no code root configured, behaviour is unchanged (paths stored and resolved as absolute).
252
-
253
- ### Migrating existing docs
254
-
255
- Docs created before this feature store absolute paths. Convert them in one shot with the bundled `mcp-documentor-migrate-paths`:
256
-
257
- ```bash
258
- MCP_DOCUMENTOR_CODE_ROOT="/home/you/code/repos" \
259
- MCP_DATA_DIR="/path/to/your/data" \
260
- npx -p @imenam/mcp-documentor mcp-documentor-migrate-paths --dry-run
261
- ```
262
-
263
- 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.
264
-
265
- 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 `MCP_DOCUMENTOR_CODE_ROOT` — no migration needed.
266
-
267
- ## Tracking a GitHub branch instead of the working copy
268
-
269
- 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.
270
-
271
- Pointing a project at a repository makes the documentation track **one branch of published code** instead:
272
-
273
- ```jsonc
274
- {
275
- "env": {
276
- "MCP_DOCUMENTOR_CODE_REPO": "you/my-repo#main",
277
- // optional: reuses MCP_DOCUMENTOR_SYNC_TOKEN when omitted
278
- "MCP_DOCUMENTOR_CODE_TOKEN": "github_pat_..."
279
- }
280
- }
281
- ```
282
-
283
- A repository names exactly one codebase, so an instance serving **several projects** gives `MCP_DOCUMENTOR_CODE_REPO` a JSON map instead of a single spec — a leading `{` is what tells the two apart:
284
-
285
- ```jsonc
286
- "MCP_DOCUMENTOR_CODE_REPO": "{\"web\":\"you/web#main\",\"api\":\"you/api#main\",\"legacy\":\"local\"}"
287
- ```
288
-
289
- Projects absent from the map, and projects whose value is `local`, keep reading their local working copy. **With the variable unset, nothing changes** — the local behaviour is untouched.
290
-
291
- The token needs **Contents: Read** on the code repository (a different repo from the docs sync one, so either widen the existing PAT's scope or issue a second one).
292
-
293
- ### What changes once a project tracks a branch
294
-
295
- | | Local working copy (default) | GitHub branch |
296
- |---|---|---|
297
- | Sees uncommitted edits | yes | no — only what is pushed |
298
- | Affected by `git checkout` | yes | no |
299
- | Needs the codebase on the server | yes | no |
300
- | `git_diff_doc` baseline | last commit before the snapshot date | the exact commit the snapshot was taken at |
301
-
302
- 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`.
303
-
304
- ### Paths and migration
305
-
306
- 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:
307
-
308
- - 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.
309
- - 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).
310
-
311
- Docs snapshotted before the switch carry no source commit: `check_freshness` works immediately, while `git_diff_doc` will ask for one `refresh_file_snapshot` to anchor them on the branch.
312
-
313
- ### API usage
314
-
315
- 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.
316
-
317
- 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.
318
-
319
- Everything else is content-addressed and therefore cached for the life of the process: a commit's tree by its sha, a file's bytes by its blob sha. 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.
320
-
321
- ## Data Storage
322
-
323
- 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.
324
-
325
- 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`). A legacy `currentProject` left in `config.json` is dropped on first read. GitHub sync settings live in environment variables, not on disk.
326
-
327
- ## License
328
-
329
- 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
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 (`project` required) |
123
+ | `move_doc` | Change a doc’s path (folder/name) within its project (`project` required) |
124
+ | `delete_doc` | Permanently delete a documentation entry (`project` required) |
125
+ | `search_docs` | Semantic search over docs (natural language; requires OpenAI) |
126
+ | `check_freshness` | Staleness for one doc (`project` required) or a full project scan |
127
+ | `refresh_file_snapshot` | Mark tracked files as up-to-date without editing the doc (`project` required) |
128
+ | `git_diff_doc` | Git diff for tracked files vs snapshot baseline (`project` required) |
129
+ | `check_links` | Validate `related_docs`, `doc://` links, and `#heading` anchors (`project` required) |
130
+ | `list_projects` | List all documentation projects |
131
+ | `upload_image` | Upload an image from a local file path to a project's assets (`project` required); returns the markdown link to insert |
132
+ | `sync_status` | Show branch, ahead/behind counts, conflicted files, and uncommitted changes |
133
+ | `pull_docs` | Pull updates from the remote (defensive fast-forward; reindexes new docs) |
134
+ | `push_docs` | Commit local doc changes and push them to the remote |
135
+ | `sync_docs` | Full sync: pull then push (push skipped if the pull reveals a conflict) |
136
+ | `resolve_sync_conflict` | Bulk-resolve merge conflicts by keeping `mine` or `theirs`, then stage |
137
+ | `get_code_source` | Show each project's code source (local working copy or GitHub branch) and its code root on this machine |
138
+ | `set_code_source` | Make a project read its local working copy or one branch of a GitHub repository (checked before saving) |
139
+ | `set_code_root` | Set the code root of a project, or the machine default, on this machine |
140
+
141
+ ## Images in documents
142
+
143
+ Images can be embedded in any documentation entry using standard markdown syntax:
144
+
145
+ ```markdown
146
+ ![alt text](/api/images/my-project/screenshot.png) <!-- local upload -->
147
+ ![alt text](https://example.com/diagram.png) <!-- external URL -->
148
+ ```
149
+
150
+ **Via the MCP tool** — use `upload_image` with the absolute path of the file on the server:
151
+
152
+ ```json
153
+ {
154
+ "filename": "screenshot.png",
155
+ "filePath": "/absolute/path/to/screenshot.png",
156
+ "alt": "Architecture diagram"
157
+ }
158
+ ```
159
+
160
+ The tool copies the file into `.docs-data/projects/{project}/assets/` and returns the ready-to-use markdown snippet.
161
+
162
+ **Via the GUI editor** — click the **🖼 Image** button in the editor toolbar to open the image panel:
163
+ - **Upload tab** — pick a local file; it is uploaded automatically and the markdown link is inserted at the cursor position
164
+ - **URL tab** — paste any external image URL; the markdown link is inserted at the cursor position
165
+ - **Project images gallery** — click any previously uploaded image to re-insert it
166
+
167
+ ## Syncing across machines with GitHub
168
+
169
+ 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.
170
+
171
+ ### What is and isn't synced
172
+
173
+ | Synced (versioned) | Local only (gitignored) |
174
+ |---|---|
175
+ | `projects/**` (markdown + assets) | `embeddings.db` (regenerated after pull) |
176
+ | `config.json` (project list) | `history.db` (per-machine version history) |
177
+
178
+ ### Setup
179
+
180
+ 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.
181
+
182
+ 1. Create an **empty private repo** on GitHub (e.g. `my-docs`).
183
+ 2. Create a **fine-grained PAT** scoped to that single repo (Repository permissions → **Contents: Read and write**).
184
+ 3. Add the sync environment variables to your MCP client config, per machine:
185
+
186
+ ```jsonc
187
+ {
188
+ "mcpServers": {
189
+ "mcp-documentor": {
190
+ "command": "npx",
191
+ "args": ["-y", "@imenam/mcp-documentor"],
192
+ "env": {
193
+ "MCP_DOCUMENTOR_SYNC_REMOTE": "https://github.com/<you>/my-docs.git",
194
+ "MCP_DOCUMENTOR_SYNC_BRANCH": "main", // branch of the docs sync repo, optional, defaults to "main"
195
+ "MCP_DOCUMENTOR_SYNC_TOKEN": "github_pat_..."
196
+ }
197
+ }
198
+ }
199
+ }
200
+ ```
201
+
202
+ The token is injected into the `origin` URL inside `.git/config` only (which git never commits) — it is never pushed.
203
+
204
+ ### Daily workflow
205
+
206
+ - `sync_docs` — pull then push in one step (recommended).
207
+ - `pull_docs` / `push_docs` — granular control.
208
+ - `sync_status` — inspect ahead/behind, conflicts, and uncommitted changes.
209
+
210
+ 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.
211
+
212
+ ### Conflicts
213
+
214
+ Because docs are plain markdown, merge conflicts are readable. When `pull_docs` reports a conflict:
215
+
216
+ - edit the conflicted `.md` files directly (remove the `<<<<<<<` / `>>>>>>>` markers), **or**
217
+ - run `resolve_sync_conflict` with `strategy: "mine"` or `"theirs"` to take one side in bulk,
218
+
219
+ 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.
220
+
221
+ > Read-only machines (`READ_ONLY=1`) can still `pull_docs` and `sync_status`, but `push_docs`, `sync_docs`, and `resolve_sync_conflict` are disabled.
222
+
223
+ ## Portable tracked-file paths
224
+
225
+ 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.
226
+
227
+ 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.
228
+
229
+ ### Configuration
230
+
231
+ 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**:
232
+
233
+ - per project — in the project settings (project selector → gear → *Code root on this machine*), or with `set_code_root` and a `project`;
234
+ - default — in Settings (*Default code root*), or with `set_code_root` without a `project`.
235
+
236
+ Both apply at once, without restart. Resolution precedence: the project's own root → the machine default → none.
237
+
238
+ `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.
239
+
240
+ | Stored path | On read |
241
+ |---|---|
242
+ | Relative + code root configured | resolved against the root |
243
+ | Relative + **no** root on this machine | reported as `unverifiable` (source absent) instead of a false `stale` |
244
+ | Absolute (legacy, or file outside the root) | used as-is |
245
+
246
+ The feature is **opt-in**: with no code root configured, behaviour is unchanged (paths stored and resolved as absolute).
247
+
248
+ ### Migrating existing docs
249
+
250
+ Docs created before this feature store absolute paths. Convert them in one shot with the bundled `mcp-documentor-migrate-paths`:
251
+
252
+ ```bash
253
+ MCP_DATA_DIR="/path/to/your/data" \
254
+ npx -p @imenam/mcp-documentor mcp-documentor-migrate-paths --dry-run
255
+ ```
256
+
257
+ It uses the code roots stored for that data directory, so set them first.
258
+
259
+ 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.
260
+
261
+ 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.
262
+
263
+ ## Tracking a GitHub branch instead of the working copy
264
+
265
+ 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.
266
+
267
+ 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).
268
+
269
+ - **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.
270
+ - **Branch** — picked from the repository's branches; the commit it points at is shown.
271
+ - **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).
272
+ - Before saving, the project's docs are checked against the chosen source, so you see how many would turn outdated.
273
+
274
+ 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`.
275
+
276
+ 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.
277
+
278
+ #### Migrating from the environment variables
279
+
280
+ 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:
281
+
282
+ - 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;
283
+ - every project named by `MCP_DOCUMENTOR_CODE_ROOTS` gets that code root on this machine, unless it already has one.
284
+
285
+ 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).
286
+
287
+ ### What changes once a project tracks a branch
288
+
289
+ | | Local working copy (default) | GitHub branch |
290
+ |---|---|---|
291
+ | Sees uncommitted edits | yes | no — only what is pushed |
292
+ | Affected by `git checkout` | yes | no |
293
+ | Needs the codebase on the server | yes | no |
294
+ | `git_diff_doc` baseline | last commit before the snapshot date | the exact commit the snapshot was taken at |
295
+
296
+ 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`.
297
+
298
+ ### Paths and migration
299
+
300
+ 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:
301
+
302
+ - 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.
303
+ - 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).
304
+
305
+ Docs snapshotted before the switch carry no source commit: `check_freshness` works immediately, while `git_diff_doc` will ask for one `refresh_file_snapshot` to anchor them on the branch.
306
+
307
+ ### API usage
308
+
309
+ 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.
310
+
311
+ 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.
312
+
313
+ Everything else is content-addressed and therefore cached for the life of the process: a commit's tree by its sha, a file's bytes by its blob sha. 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.
314
+
315
+ ## Data Storage
316
+
317
+ 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.
318
+
319
+ 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`). A legacy `currentProject` left in `config.json` is dropped on first read. GitHub sync settings live in environment variables, not on disk.
320
+
321
+ ## License
322
+
323
+ ISC