@4da/mcp-server 6.0.1 → 6.1.0

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 (93) hide show
  1. package/CHANGELOG.md +316 -0
  2. package/README.md +19 -13
  3. package/dist/db.d.ts +88 -7
  4. package/dist/db.js +248 -63
  5. package/dist/doctor.d.ts +1 -1
  6. package/dist/doctor.js +15 -9
  7. package/dist/embeddings.js +19 -7
  8. package/dist/gitignore.js +9 -3
  9. package/dist/index.js +54 -7
  10. package/dist/live/cache.d.ts +31 -2
  11. package/dist/live/cache.js +113 -1
  12. package/dist/live/changelog-classify.js +4 -1
  13. package/dist/live/changelog.d.ts +15 -0
  14. package/dist/live/changelog.js +67 -11
  15. package/dist/live/dev-scope.d.ts +2 -2
  16. package/dist/live/fix-paths.d.ts +118 -0
  17. package/dist/live/fix-paths.js +498 -0
  18. package/dist/live/go-registry.d.ts +5 -0
  19. package/dist/live/go-registry.js +1 -1
  20. package/dist/live/index.d.ts +30 -3
  21. package/dist/live/index.js +59 -6
  22. package/dist/live/lockfile-parents.d.ts +61 -0
  23. package/dist/live/lockfile-parents.js +360 -0
  24. package/dist/live/lockfile-parsers-pygo.d.ts +31 -0
  25. package/dist/live/lockfile-parsers-pygo.js +178 -28
  26. package/dist/live/lockfile-parsers.d.ts +4 -1
  27. package/dist/live/lockfile-parsers.js +57 -24
  28. package/dist/live/lockfile-types.d.ts +12 -3
  29. package/dist/live/lockfile-types.js +2 -1
  30. package/dist/live/osv-ranges.d.ts +79 -0
  31. package/dist/live/osv-ranges.js +133 -0
  32. package/dist/live/osv-scanner.d.ts +1 -0
  33. package/dist/live/osv-scanner.js +85 -78
  34. package/dist/live/range-satisfies.d.ts +21 -0
  35. package/dist/live/range-satisfies.js +190 -0
  36. package/dist/live/rate-limiter.d.ts +6 -0
  37. package/dist/live/rate-limiter.js +5 -0
  38. package/dist/live/registry-health.js +2 -0
  39. package/dist/live/resolution.d.ts +11 -1
  40. package/dist/live/resolution.js +16 -6
  41. package/dist/live/semver-utils.d.ts +8 -0
  42. package/dist/live/semver-utils.js +10 -2
  43. package/dist/live/types.d.ts +17 -1
  44. package/dist/live/upgrade-sources.d.ts +2 -0
  45. package/dist/live/upgrade-sources.js +7 -2
  46. package/dist/memory-store.d.ts +19 -0
  47. package/dist/memory-store.js +8 -0
  48. package/dist/native-bindings.d.ts +7 -2
  49. package/dist/native-bindings.js +4 -0
  50. package/dist/project-tree.d.ts +50 -8
  51. package/dist/project-tree.js +121 -29
  52. package/dist/schema-registry.js +2 -2
  53. package/dist/setup.d.ts +76 -3
  54. package/dist/setup.js +313 -130
  55. package/dist/sqlite-driver.d.ts +134 -0
  56. package/dist/sqlite-driver.js +359 -0
  57. package/dist/tool-dispatch.d.ts +10 -1
  58. package/dist/tool-dispatch.js +37 -2
  59. package/dist/tools/agent-memory.js +10 -2
  60. package/dist/tools/app-plan.d.ts +11 -1
  61. package/dist/tools/app-plan.js +32 -6
  62. package/dist/tools/briefing-task-scope.d.ts +21 -2
  63. package/dist/tools/briefing-task-scope.js +97 -4
  64. package/dist/tools/decision-memory.js +9 -3
  65. package/dist/tools/dependency-check-signals.d.ts +7 -0
  66. package/dist/tools/dependency-check-signals.js +17 -2
  67. package/dist/tools/dependency-check.d.ts +1 -1
  68. package/dist/tools/dependency-check.js +1 -1
  69. package/dist/tools/dependency-health.d.ts +1 -1
  70. package/dist/tools/dependency-health.js +5 -0
  71. package/dist/tools/knowledge-gap-ranges.d.ts +1 -1
  72. package/dist/tools/knowledge-gap-ranges.js +1 -1
  73. package/dist/tools/knowledge-gap-scope.d.ts +2 -2
  74. package/dist/tools/project-scope.d.ts +45 -0
  75. package/dist/tools/project-scope.js +119 -0
  76. package/dist/tools/record-feedback.js +1 -1
  77. package/dist/tools/upgrade-impact-literals.js +1 -1
  78. package/dist/tools/upgrade-impact-report.d.ts +10 -0
  79. package/dist/tools/upgrade-impact-report.js +32 -2
  80. package/dist/tools/upgrade-impact.js +1 -1
  81. package/dist/tools/upgrade-planner-fixpaths.d.ts +40 -0
  82. package/dist/tools/upgrade-planner-fixpaths.js +0 -0
  83. package/dist/tools/upgrade-planner.d.ts +31 -3
  84. package/dist/tools/upgrade-planner.js +74 -19
  85. package/dist/tools/vulnerability-scan-concise.d.ts +13 -1
  86. package/dist/tools/vulnerability-scan-concise.js +15 -3
  87. package/dist/tools/vulnerability-scan-format.d.ts +58 -1
  88. package/dist/tools/vulnerability-scan-format.js +107 -11
  89. package/dist/tools/vulnerability-scan.d.ts +104 -13
  90. package/dist/tools/vulnerability-scan.js +27 -3
  91. package/dist/tools/what-should-i-know.d.ts +22 -2
  92. package/dist/tools/what-should-i-know.js +121 -11
  93. package/package.json +10 -31
package/CHANGELOG.md CHANGED
@@ -1,5 +1,321 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.1.0 — 2026-10-10
4
+
5
+ Fixes from an agent eval over stdio JSON-RPC against real projects (2026-10-07)
6
+ and an engine-generalization test on 19 public repositories against
7
+ osv-scanner, pip-audit and a fix-path oracle (2026-10-10): precision 1.00 over
8
+ 19,115 findings and recall 19,115 of 19,116 on the lockfiles the server reads
9
+ (was 0.74 overall, with 44 langchain lockfiles dropped silently); the median
10
+ `vulnerability_scan` over the corpus went from 27 s to 8.6 s.
11
+
12
+ ### Install: runs under npm 12 with no install scripts
13
+
14
+ npm 12 blocks dependency install scripts unless allowed, so a default
15
+ `npx @4da/mcp-server` or `npm install` left better-sqlite3's native module
16
+ unbuilt and every tool failed (6.0.x documented an `allow-scripts` workaround).
17
+
18
+ - The server uses Node's built-in `node:sqlite` when the runtime has it
19
+ (Node 22.13.0 and later; npm 12 itself requires 22.22.2+). No install
20
+ script, no native download. better-sqlite3 moved to `optionalDependencies`
21
+ and is used only where `node:sqlite` is missing (Node 22.0-22.12), if its
22
+ native module opens a database.
23
+ - One driver interface (`src/sqlite-driver.ts`) covers what the server uses;
24
+ node:sqlite is adapted to match better-sqlite3: plain-object rows, BLOBs as
25
+ Buffers, error codes (`SQLITE_BUSY`, `SQLITE_NOTADB`, ...), the 5 s busy
26
+ timeout, savepoint-nested transactions, `readonly` / `fileMustExist`.
27
+ Read-only connections also set `PRAGMA query_only`.
28
+ - With no usable driver at all the server no longer exits at import: it
29
+ starts, `vulnerability_scan`, `dependency_health`, `dependency_check` and
30
+ `upgrade_impact` run without a database (in-memory cache), and the other
31
+ tools return the fix (upgrade Node, or allow better-sqlite3's build).
32
+ - `--doctor` reports the driver in use. `FOURDA_SQLITE_DRIVER=node|better|none`
33
+ forces one (tests, diagnostics). node:sqlite's one-time ExperimentalWarning
34
+ is filtered; other warnings pass.
35
+ - Tests: the suite passes on each driver (CI runs it on both, and on Node
36
+ 22.12, which has no node:sqlite); driver parity tests; the built server over
37
+ stdio with better-sqlite3's native module missing (fails on 6.0.2: every
38
+ tool answered "Could not locate the bindings file").
39
+
40
+ ### The server never changes the desktop app's schema
41
+
42
+ The server opened the 4DA app's live `4da.db` read-write, created a
43
+ `live_cache` table in it (2,774 rows on one machine) that the app's
44
+ migrations do not own, and added `embedding` columns to app tables when an
45
+ embedding provider was set.
46
+
47
+ - Reads go through a `{ readonly: true, fileMustExist: true }` connection.
48
+ Nothing is created or altered in the app's database: no tables, columns
49
+ or indexes.
50
+ - The response cache moved to `cache.db` in the server's own folder
51
+ (`%LOCALAPPDATA%\4da-mcp`, `~/Library/Application Support/4da-mcp`,
52
+ `~/.local/share/4da-mcp`; `FOURDA_MCP_HOME` moves it).
53
+ - Decisions, agent memory and feedback still go into the app's own
54
+ `developer_decisions`, `agent_memory`, `interactions` and `feedback`
55
+ tables, so the app's decision UI and calibration see them. They go through a
56
+ separate write connection (busy timeout 3 s, one short transaction per
57
+ record). If the app's table or a column a record needs is missing (schema
58
+ drift), the record goes to the server's `standalone.db` and the response
59
+ says so in `_meta`; the server never creates it.
60
+ - Semantic-recall embeddings are cached in the app's tables only when they
61
+ already have `embedding` / `embedding_model` columns; otherwise they are
62
+ computed per call.
63
+ - Tests: the app's `sqlite_master` is identical after every tool ran, and
64
+ only those four tables gain rows; caching alone leaves the file
65
+ byte-for-byte unchanged.
66
+ - The `live_cache` table already inside existing app databases is left
67
+ alone; the app owns that file and can drop it.
68
+
69
+ ### `upgrade_impact`
70
+
71
+ - Reads changelog headings with inline HTML before the version (stripe:
72
+ `## <a id="23-0-0"></a>23.0.0 - 2026-09-30`). stripe 22.3.0 -> 23.0.0 went
73
+ from "no changelog" to 8 releases with 11 entries flagged breaking.
74
+ - Bullets nested under an entry are that entry's `details`, not entries of
75
+ their own; a breaking detail makes the entry breaking. sqlx 0.8.6 -> 0.9.0
76
+ went from "83 entries flagged breaking" to 24 (its Breaking section's 23
77
+ bullets plus the MSRV statement).
78
+ - "`Cargo.lock` Removed from Tracking" is no longer read as a Removed
79
+ category.
80
+ - The parsed-changelog cache key carries the parser version
81
+ (`upgrade-impact:changelog:p2:...`). Parsed changelogs are cached for 7
82
+ days, so before this a parser fix in a release stayed invisible for a week
83
+ on every machine that had already read that package.
84
+
85
+ ### Breaking version ranges
86
+
87
+ - `dependency_health` and `upgrade_planner` label a 0.x minor bump (and a
88
+ 0.0.x patch bump) "major", as `upgrade_impact` and `dependency_check`
89
+ already did: under caret rules 0.12 -> 0.13 is breaking.
90
+ - `dependency_check` returns `review` for a breaking range (a major, a 0.x
91
+ minor) with "breaking version range, run upgrade_impact". It returned
92
+ `proceed` for vitest 3 -> 5, reqwest 0.12 -> 0.13 and stripe 22 -> 23.
93
+ - `what_should_i_know` counts a 0.x minor bump as a major one in
94
+ `majors_crossed` and the delegation verdict. It compared plain major
95
+ numbers, so "upgrade reqwest from 0.12 to 0.13" crossed 0 majors and came
96
+ back `safe_to_delegate`; it is now `review_needed`. Without a stated
97
+ "from", the furthest installed copy sets the distance.
98
+
99
+ ### Project scope
100
+
101
+ - `upgrade_planner` and `what_should_i_know` take `project_path`. Default:
102
+ the project the server was started in; `"*"` for every project. Called
103
+ from 4DA, the app's plan led with navcal, verax and 4da-ledger steps; it
104
+ is now narrowed to the project's steps, lines and sites, and says how
105
+ many steps only touch other projects.
106
+
107
+ ### `what_should_i_know`
108
+
109
+ - Expands the package families a task names ("all tauri plugins", "tauri
110
+ and its plugins", "@tauri-apps packages", "tauri-plugin-*") to the
111
+ project's direct dependencies in them. It matched only `tauri`.
112
+ - For a task asking for the latest release, each package gets `latest`
113
+ (newest stable, installed vs latest major line, `on_latest_major`) and the
114
+ summary says which are already on the latest major.
115
+
116
+ ### `vulnerability_scan`
117
+
118
+ - One recommendation per installed version, each advisory counted once.
119
+ "rsa 0.9.10 — 2 known vulnerabilities" was one advisory on two versions.
120
+
121
+ ### `vulnerability_scan` latency
122
+
123
+ A cold scan (empty cache) through the server's own modules, same machine,
124
+ same network, two runs each (2026-10-10):
125
+
126
+ | Project | Dependencies | Before | After |
127
+ |---|---|---|---|
128
+ | superset | 4,516 | 71 s, 143 s | 9.4 s, 9.2 s |
129
+ | proshop-mern | 1,545 | 34 s, 34 s | 6.5 s, 6.5 s |
130
+ | nushell | 531 | 20 s, 17 s | 4.5 s, 4.4 s |
131
+
132
+ - **Cache writes in one transaction.** A scan caches one row per dependency
133
+ and one per advisory, and each was its own autocommit: on superset the
134
+ network part of a cold scan ended at 12 s and the remaining ~40 s were
135
+ 4,516 WAL commits. They are now one transaction per batch, and the cache
136
+ file runs `synchronous = NORMAL` (a cache: in WAL mode a crash can lose
137
+ only the last writes, never corrupt the file).
138
+ - **Advisory details in a pool of 24, not rounds of 8.** OSV has no batch
139
+ endpoint for advisory records; rounds of 8 each waited for their slowest
140
+ request. A throttled (429), failed or timed-out detail request is retried
141
+ once instead of leaving the advisory without severity or summary.
142
+ - **querybatch chunks (1,000 dependencies each) are sent 4 at a time**
143
+ instead of one after another; every chunk must answer or the scan fails, so
144
+ an unanswered chunk can never read as clean.
145
+ - **No duplicate work beside the warm-up.** The startup warm-up scan and the
146
+ first tool call's scan ran side by side and both fetched every advisory
147
+ before either could cache it. An identical scan already running is now
148
+ awaited (a forced refresh still runs its own), and a detail request in
149
+ flight is shared by every scanner in the process.
150
+
151
+ ### What the standalone scan reads
152
+
153
+ From the same 19-repository corpus, checked against osv-scanner and
154
+ pip-audit.
155
+
156
+ - **The project-tree walk is wider, and never silent about its bounds.**
157
+ langchain keeps 108 lockfiles up to three levels down; the walk (depth 2,
158
+ 64 projects) read 64 and dropped 44 (4,852 advisory findings) without a
159
+ word in any answer. Bounds are now 4 levels and 256 projects (langchain:
160
+ about 2 s for the walk and every project scan). Past a bound the walk keeps
161
+ counting without scanning (up to 20,000 directories), and
162
+ `vulnerability_scan` returns a `coverage` block (concise and detailed:
163
+ `complete: false`, a note, `lockfiles_not_scanned`; detailed also lists
164
+ each skipped lockfile and why). `dependency_health` and `upgrade_planner`
165
+ say "Partial scan: ..." in their summaries.
166
+ - **Python reads every source in a directory, merged:** poetry.lock, uv.lock,
167
+ pdm.lock (new), Pipfile.lock and every requirements file:
168
+ `*requirements*.txt` (requirements-dev.txt, _test_minimum_requirements.txt)
169
+ and the `.txt` files of a `requirements/` folder (superset keeps its 100
170
+ runtime pins in requirements/base.txt, which nothing read), plus a
171
+ `.txt` that `-r`-includes a requirements file and pins packages itself
172
+ (CTFd's development.txt). It read one source per directory, so langchain's
173
+ libs/community poetry.lock hid the _test_minimum_requirements.txt beside
174
+ it. `-r` includes are followed; a `-c` constraints file only gives a
175
+ version to a name a requirements file lists (it installs nothing by
176
+ itself). Against pip-audit on the newly read files: superset 149/152 rows
177
+ agree (the 3 others are advisories OSV lists and pip-audit's data does not
178
+ yet) and 262/262 of pip-audit's advisories are found; CTFd 6/6; langchain
179
+ libs 38/38 rows, 64/65 advisories (the miss is a langchainjs CVE pip-audit
180
+ files under the Python package).
181
+ - A `dir/*/` .gitignore rule hid the files beside the ignored
182
+ subdirectories: `docs/api_reference/*/` made langchain's
183
+ docs/api_reference/requirements.txt look ignored. A directory-only rule now
184
+ ignores a file only through a directory above it.
185
+ - **Dev scope from a requirements file's name, and only from it:**
186
+ requirements-dev.txt, requirements/testing.txt, docs-requirements.txt and
187
+ the like mark their packages dev. A package any other file lists keeps an
188
+ unknown scope (never "dev"), so a runtime package is never graded down by
189
+ a dev file that includes the runtime one.
190
+ - **No lockfile: declared ranges are labelled, never scanned as installs.**
191
+ A package.json or Cargo.toml with no lockfile used to have its range
192
+ floors scanned as installed versions (nushell samples/wasm: 2 false
193
+ findings). Ranged dependencies now carry `declaredRange` ("^1.2.0") in
194
+ `dependency_health` and `upgrade_planner` and are not sent to OSV; exact
195
+ pins still are. `vulnerability_scan` lists each such manifest under
196
+ `coverage.unresolved_manifests` with the command that writes its lockfile.
197
+ - A Cargo.toml read without Cargo.lock took `[package]`'s own `version =
198
+ "0.1.0"` and `edition = "2021"` for crates named "version" and "edition";
199
+ only dependency tables are read now.
200
+
201
+ ### Fix paths (`upgrade_planner`)
202
+
203
+ From a fix-path oracle on 19 public repositories (2026-10-10): each
204
+ recommended step was applied to a copy, the lockfile re-resolved and the
205
+ project re-scanned with osv-scanner.
206
+
207
+ - **A target is checked against every advisory of the package, not only the
208
+ ones the installed version has.** openssl 0.10.38 was sent to 0.10.79,
209
+ which GHSA-phqj-4mhp-q6mq affects (0.10.50 up to 0.10.80): the upgrade
210
+ traded old advisories for a new one. The target is now the smallest
211
+ published version at or above every per-advisory fix that no advisory
212
+ affects (0.10.80). The package's advisories come from one OSV `/v1/query`
213
+ per vulnerable package; versions from its registry (npm, crates.io sparse
214
+ index, PyPI, Go proxy), else from the advisories' own fix events. An
215
+ advisory no release fixes (braces GHSA-vfj7-8cjw-p6xm, last affected
216
+ 3.0.3, the newest release) does not disqualify a target and is named in
217
+ the step. OSV `last_affected` events are read as closed ranges.
218
+ - **"Waiting on upstream" only when a parent really blocks the fix.** Every
219
+ transitive finding used to be `waiting_on_upstream`; for minimist 1.2.5
220
+ (npm), braces 3.0.2 (pnpm) and mio 0.8.0 (Cargo) that was wrong: each
221
+ parent's declared requirement already admitted the fix, and a lockfile
222
+ refresh fixed all three. The planner now reads the parents of the
223
+ installed copy (package-lock v1-v3, yarn v1 and berry and bun.lock record
224
+ the ranges; pnpm and Cargo.lock do not, so the parent's requirement comes
225
+ from registry.npmjs.org or the crates.io sparse index) and returns
226
+ `action: "lockfile_refresh"` with `refreshCommands` (`npm update
227
+ minimist`, `pnpm update braces`, `yarn up -R`, `cargo update -p
228
+ mio@0.8.0 --precise 0.8.11`) when every requirement admits a clean
229
+ version. A step still waiting on upstream names the requirement that
230
+ blocks it (`left-pad 1.0.0 requires "1.2.5"`). Summary and
231
+ `lockfileRefreshes` count them.
232
+ - A Cargo refresh names the locked crates `--precise` cannot move by itself.
233
+ `cargo update -p openssl@0.10.38 --precise 0.10.80` fails on nushell:
234
+ 0.10.80 pulls syn 2, which needs quote ^1.0.25, and quote 1.0.15 is held
235
+ by rstest. The step is now `cargo update -p bytemuck@1.8.0 -p
236
+ proc-macro2@1.0.36 -p quote@1.0.15 && cargo update -p openssl@0.10.38
237
+ --precise 0.10.80`, found by walking the target's dependency requirements
238
+ against the lockfile.
239
+ - Checked by applying every `lockfile_refresh` step of four plans to a copy
240
+ and re-scanning with osv-scanner: 104 of 104 steps cleared their
241
+ advisories (proshop-mern npm 45, taxonomy pnpm 21, chatgpt-tauri pnpm and
242
+ Cargo 26, nushell Cargo 12).
243
+ - **`vulnerability_scan` and `what_should_i_know` run the same check** on
244
+ the targets they recommend. "Upgrade openssl 0.10.38 → 0.10.79" is now
245
+ "→ 0.10.80 (... 0.10.79, the highest per-advisory fix, is itself affected
246
+ by GHSA-phqj-4mhp-q6mq)". Concise rows carry `fix_checked`; detailed
247
+ output adds `fix_targets` per vulnerable package version, while each
248
+ advisory row keeps its own `fixed_version`. The check is cached and
249
+ bounded (5 s per call, most severe first); what it could not reach in time
250
+ says `installed_advisories_only`. On nushell, cold: 33 of 39 package
251
+ versions checked in the first call.
252
+ - The test suite is offline by construction: `fetch` throws unless a test
253
+ stubs it (`FOURDA_TEST_NETWORK=1` lifts that), so no assertion can depend
254
+ on live OSV or registry data.
255
+ - Each vulnerable step says how its target was checked: `fixPathChecked`
256
+ is `all_advisories`, or `installed_advisories_only` when OSV or the
257
+ registry did not answer (offline, or past the 25 s budget), and the
258
+ summary counts those.
259
+
260
+ ### Build
261
+
262
+ - pnpm settings (overrides, onlyBuiltDependencies) moved from package.json
263
+ to pnpm-workspace.yaml: pnpm 11 ignores package.json's `pnpm` field, so
264
+ the CI audit ran without the security overrides. New override
265
+ source-map-js >= 1.2.2 (GHSA-68fv-2mgg-jv7q, high). Fixes the nightly CI
266
+ (#9).
267
+
268
+ ## 6.0.2 — 2026-10-04
269
+
270
+ ### `--setup` writes the files editors actually read
271
+
272
+ Three of the four editors `--setup` reported as configured were configured in
273
+ files those editors never read, so nothing happened:
274
+
275
+ | Editor | Before | Now |
276
+ |---|---|---|
277
+ | VS Code | `~/.vscode/mcp.json` | the user profile's `mcp.json`: `%APPDATA%\Code\User\` (Windows), `~/Library/Application Support/Code/User/` (macOS), `$XDG_CONFIG_HOME/Code/User/` (Linux, default `~/.config`), key `servers`, `"type": "stdio"` |
278
+ | Windsurf | `~/.windsurf/mcp.json` | `~/.codeium/windsurf/mcp_config.json`, key `mcpServers` |
279
+ | Claude Code | `mcpServers` in the project's `.claude/settings.json` (or, outside a project, rewrote `~/.claude.json`) | prints `claude mcp add --scope user 4da -- npx @4da/mcp-server`; `~/.claude.json` is never edited |
280
+
281
+ Added: VS Code Insiders, Claude Desktop (`claude_desktop_config.json`) and
282
+ Devin Desktop / Devin CLI (`~/.config/devin/mcp_config.json`,
283
+ `%APPDATA%\devin\` on Windows), Windsurf's successor. Cursor
284
+ (`~/.cursor/mcp.json`) was already right.
285
+
286
+ - **A config file setup cannot parse is left untouched.** It used to be read
287
+ as `{}`, so a VS Code `mcp.json` with a comment or a trailing comma (both
288
+ legal there) was replaced by our entry alone, deleting every other server.
289
+ Files are now parsed as JSONC and edited in place with `jsonc-parser`
290
+ (Microsoft's, the parser VS Code uses; no dependencies of its own), which
291
+ keeps comments, formatting, other servers and other keys. A file that does
292
+ not parse is reported with the entry to add by hand.
293
+ - An existing `4da` entry is updated, not duplicated, and keeps its other
294
+ fields (`env`); an identical one is left alone, so a second run writes
295
+ nothing.
296
+ - Every modified file is backed up first (`<file>.bak`; an earlier backup is
297
+ never overwritten) and replaced atomically (temp file, then rename).
298
+ - `--setup --dry-run` wrote anyway when run through `npx @4da/mcp-server`
299
+ (only the `4da-mcp-setup` entry honoured the flag). It now writes nothing on
300
+ either, and prints each file and the exact entry it would add.
301
+
302
+ ### Clearer answers when the database cannot help
303
+
304
+ - A desktop-only tool (`get_relevant_content`, `get_actionable_signals`,
305
+ `knowledge_gaps`, `record_feedback`, `developer_dna`) called without the
306
+ desktop app's database answered `[]`, which reads as "nothing relevant". It
307
+ now carries a `desktop_app_note` saying it needs the app.
308
+ - A database file that is damaged or not SQLite answered every call with
309
+ SQLite's bare "file is not a database". The message now names the file and
310
+ what to do, and never advises deleting it (`--doctor`'s integrity check used
311
+ to say "Try deleting data/4da.db"). The handle a failed open left behind is
312
+ closed, so on Windows the file can be moved aside while the server runs.
313
+ - The database lookup no longer checks `data/4da.db` two directories above the
314
+ server's own files, a leftover from when the server was a folder of the app
315
+ repository; installed, it pointed into `node_modules/@4da/`.
316
+
317
+ - Dependencies: `@modelcontextprotocol/server` ^2.2.0, `hono` ^4.13.12; dev: `@types/node` ^26.6.3, `vitest` ^5.0.3 (#2).
318
+
3
319
  ## 6.0.1 — 2026-10-04
4
320
 
5
321
  ### The server has its own repository
package/README.md CHANGED
@@ -27,9 +27,7 @@ One command to install. No API keys. No accounts. Your code never leaves your ma
27
27
 
28
28
  Requires Node.js 22 or later.
29
29
 
30
- > **npm 12+:** npm 12 blocks dependency install scripts unless you allow them, which leaves the SQLite module (`better-sqlite3`) unbuilt. Allow it once, then clear the npx cache:
31
- > `npm config set allow-scripts=better-sqlite3 --location=user` and `npx clear-npx-cache`.
32
- > `npx @4da/mcp-server --doctor` checks this by opening a database. npm 10 and 11, which every current Node release ships, are not affected.
30
+ The server uses Node's built-in `node:sqlite` (Node 22.13 and later), so it needs no install script and works with npm 12's default of blocking them. On Node 22.0-22.12 it uses the optional `better-sqlite3`, whose native module npm 10 and 11 build on install. `npx @4da/mcp-server --doctor` shows which one is in use.
33
31
 
34
32
  ```bash
35
33
  claude mcp add 4da -- npx @4da/mcp-server
@@ -45,9 +43,9 @@ claude plugin install 4da@4da
45
43
  The hook: when your agent edits a dependency's version in `package.json`, `Cargo.toml`, `pyproject.toml`, `requirements.txt` or `go.mod`, it is told which packages moved and given the exact `dependency_check` and `upgrade_impact` calls to make before it installs and builds. The hook is plain Node, contacts nothing, and stays silent for every other edit.
46
44
 
47
45
  <details>
48
- <summary><b>Cursor / Windsurf</b></summary>
46
+ <summary><b>Cursor / Windsurf / Devin Desktop</b></summary>
49
47
 
50
- Add to `~/.cursor/mcp.json` or `~/.windsurf/mcp.json`:
48
+ Add to `~/.cursor/mcp.json` (Cursor), `~/.codeium/windsurf/mcp_config.json` (Windsurf), or `~/.config/devin/mcp_config.json` (Devin Desktop and Devin CLI; `%APPDATA%devinmcp_config.json` on Windows):
51
49
  ```json
52
50
  {
53
51
  "mcpServers": {
@@ -63,7 +61,7 @@ Add to `~/.cursor/mcp.json` or `~/.windsurf/mcp.json`:
63
61
  <details>
64
62
  <summary><b>VS Code (Copilot)</b></summary>
65
63
 
66
- Add to `~/.vscode/mcp.json`:
64
+ Run **MCP: Open User Configuration** and add the entry, or edit the file directly: `%APPDATA%CodeUsermcp.json` (Windows), `~/Library/Application Support/Code/User/mcp.json` (macOS), `~/.config/Code/User/mcp.json` (Linux). VS Code uses `servers`, not `mcpServers`:
67
65
  ```json
68
66
  {
69
67
  "servers": {
@@ -97,17 +95,20 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
97
95
  <summary><b>Auto-setup</b> (detects all installed editors)</summary>
98
96
 
99
97
  ```bash
98
+ npx @4da/mcp-server --setup --dry-run # show each file and entry, write nothing
100
99
  npx @4da/mcp-server --setup
101
100
  ```
101
+
102
+ Writes Cursor, VS Code (and Insiders), Windsurf, Devin Desktop and Claude Desktop configs it finds, merging into what is there: other servers, settings and comments are kept, a backup (`.bak`) is written first, and a file it cannot parse is left untouched with the entry to add by hand. For Claude Code it prints the `claude mcp add` command instead of editing `~/.claude.json`.
102
103
  </details>
103
104
 
104
105
  Then ask your AI: **"What changes if I upgrade X to Y?"**, **"Scan for vulnerabilities"** or **"What should I upgrade first?"**
105
106
 
106
107
  ## How It Works
107
108
 
108
- The server reads every lockfile of the project it is started in, including independently-locked projects below the root (a repo with `src-tauri/Cargo.lock` beside a root `pnpm-lock.yaml` is scanned whole) and skipping what `.gitignore` excludes: `package-lock.json` / `npm-shrinkwrap.json`, `pnpm-lock.yaml` (v5–v9), `yarn.lock` (v1 and berry), `bun.lock`, `Cargo.lock` (Cargo workspaces included), `poetry.lock`, `uv.lock`, `Pipfile.lock`, `requirements.txt` pins, and `go.mod` / `go.sum` (Go's build list). Every installed copy is scanned, not one version per name, with the lockfile's own dev flags. It re-reads them whenever a lockfile changes, and for npm it also checks what `node_modules` actually holds.
109
+ The server reads every lockfile of the project it is started in, including independently-locked projects below the root (a repo with `src-tauri/Cargo.lock` beside a root `pnpm-lock.yaml` is scanned whole) and skipping what `.gitignore` excludes: `package-lock.json` / `npm-shrinkwrap.json`, `pnpm-lock.yaml` (v5–v9), `yarn.lock` (v1 and berry), `bun.lock`, `Cargo.lock` (Cargo workspaces included), `poetry.lock`, `uv.lock`, `pdm.lock`, `Pipfile.lock`, requirements pins (`*requirements*.txt`, `requirements/*.txt`, with `-r` and `-c`), and `go.mod` / `go.sum` (Go's build list). Every Python source in a folder is read, not only the first. Every installed copy is scanned, not one version per name, with the lockfile's own dev flags. It re-reads them whenever a lockfile changes, and for npm it also checks what `node_modules` actually holds. The walk goes 4 levels down and up to 256 projects; whatever it leaves out, and every manifest with no lockfile (its declared ranges are not installs and are not scanned), is listed in the answer's `coverage` block, never dropped silently.
109
110
 
110
- Measured against osv-scanner on 12 projects (npm, pnpm, Cargo, Poetry, requirements.txt, Go): precision 1.00, recall 0.995; re-run before release from the installed package on those and 11 more never used to build the readers (yarn, pnpm, bun, uv, Pipfile, Go included), with every difference settled by OSV or Go's own build list.
111
+ Measured against osv-scanner on 19 public repositories (npm, pnpm v5-v9, yarn v1 and berry, bun, Cargo, Poetry, uv, Pipfile, requirements, Go; 2026-10-10), every difference settled by OSV's own API: precision 1.00 over 19,115 findings, recall 19,115 of 19,116 on the lockfiles it reads (Maven/Gradle are not read). Newly read requirement files were also checked against pip-audit.
111
112
 
112
113
  - **OSV.dev** for known vulnerabilities, matched to exact installed versions
113
114
  - **npm registry, crates.io, PyPI, Go module proxy** for versions, deprecations and yanks
@@ -119,6 +120,10 @@ Results are cached (24h for registry data, 1h for vulnerabilities, 30min for new
119
120
 
120
121
  **What's sent over the network:** package names and versions (the same data visible in your lockfile), and, only for `ecosystem_pulse`, the names of a few of your dependencies as search terms. No source code, no file paths, no personal data. The call-site scan of `upgrade_impact` runs locally. Set `FOURDA_OFFLINE=true` to disable all network calls.
121
122
 
123
+ **What's written to disk, and where:** only files the server owns, in its folder in your user data directory (`%LOCALAPPDATA%\4da-mcp`, `~/Library/Application Support/4da-mcp` or `~/.local/share/4da-mcp`; `FOURDA_MCP_HOME` moves it): `cache.db` (registry, OSV and changelog responses; safe to delete) and `standalone.db` (without the desktop app: your project scan and the decisions, agent memory and feedback you record). Nothing is written inside your repository.
124
+
125
+ When the 4DA desktop app is installed, the server reads the app's database (`4da.db`) through a **read-only** connection and never creates or alters anything in it: no tables, no columns, no cache. The only writes are rows in the four app-owned tables the tools exist to fill, so the app sees them: decisions (`decision_memory` -> `developer_decisions`), agent memory (`agent_memory`), and feedback (`record_feedback` -> `interactions` and `feedback`, which the app's calibration reads). Those go through a separate write connection that waits briefly for the app's lock. If your app version lacks the table or a column a record needs, the record goes to `standalone.db` instead and the response says so in `_meta`. (Before 6.1 the server also created a `live_cache` table inside the app's database; it now keeps that cache in `cache.db`.)
126
+
122
127
  > The one exception: if you *explicitly* configure an OpenAI embedding provider (`FOURDA_EMBED_PROVIDER=openai`) for semantic recall, the decision/memory text you store is sent to OpenAI to be embedded. The default — no embedding provider, or a local Ollama one — keeps everything on your machine, and `FOURDA_OFFLINE=true` overrides it regardless.
123
128
 
124
129
  **Ecosystems supported:** npm, crates.io (Rust), PyPI (Python), Go. `upgrade_impact`: npm and crates.io.
@@ -148,22 +153,22 @@ Results are cached (24h for registry data, 1h for vulnerabilities, 30min for new
148
153
  | Tool | What it does |
149
154
  |------|-------------|
150
155
  | `upgrade_impact` | What changes between the installed and a target version of one dependency: releases in between, changelog entries classified breaking / deprecation / security, the breaking ones that touch your code (symbols you import, and route or pattern syntax in your string literals, e.g. axum 0.8's `/:id` -> `/{id}`), the files that import it, advisories fixed. |
151
- | `vulnerability_scan` | Every installed copy in every lockfile matched against OSV.dev. Scope-adjusted severity, the fix version on your release line, where each version is pinned. Concise by default (one row per vulnerable package version, the 40 most severe, about 4k tokens on a 290-advisory project); `package` for one dependency; `response_format: "detailed"` for every advisory. |
156
+ | `vulnerability_scan` | Every installed copy in every lockfile matched against OSV.dev. Scope-adjusted severity, an upgrade target checked against every advisory of the package (the highest per-advisory fix can itself be affected by another), where each version is pinned, and a `coverage` block whenever the scan is partial. Concise by default (one row per vulnerable package version, the 40 most severe, about 4k tokens on a 290-advisory project); `package` for one dependency; `response_format: "detailed"` for every advisory. |
152
157
  | `dependency_health` | Version freshness, deprecation (of the version you run) and vulnerability counts per dependency. |
153
- | `upgrade_planner` | The smallest version that fixes each vulnerability, majors flagged, transitive fixes waiting on upstream. `package` for a one-package plan. |
154
- | `dependency_check` | Call before adding a dependency or applying a bump. Verdict per item (`proceed` / `wait` / `review` / `avoid` / `unknown`) with evidence: advisories on the target, release age (holds releases under 3 days unless they fix an advisory you have), publish-trust drop, new install scripts, brand-new transitive dependencies, yanked or deprecated. npm and crates.io. |
158
+ | `upgrade_planner` | The smallest version no advisory affects for each vulnerable package, majors flagged (a 0.x minor counts as a major). Transitive fixes: `lockfile_refresh` with the exact command (`npm update`, `pnpm update`, `yarn up -R`, `cargo update -p ... --precise`) when every parent's declared requirement already admits a clean version, else `waiting_on_upstream` naming the requirement that blocks it. For one project: `project_path` (default: the project the server was started in; `"*"` for every project the desktop app tracks). `package` for a one-package plan. |
159
+ | `dependency_check` | Call before adding a dependency or applying a bump. Verdict per item (`proceed` / `wait` / `review` / `avoid` / `unknown`) with evidence: advisories on the target, release age (holds releases under 3 days unless they fix an advisory you have), publish-trust drop, new install scripts, brand-new transitive dependencies, yanked or deprecated, and a breaking version range (a major, or a 0.x minor) is `review` with a pointer to `upgrade_impact`. npm and crates.io. |
155
160
 
156
161
  ### Intelligence
157
162
 
158
163
  | Tool | What it does |
159
164
  |------|-------------|
160
- | `what_should_i_know` | Pre-task briefing built from the task: the dependencies it names, their versions and confirmed vulnerabilities, majors crossed, your recorded decisions, and a delegation verdict only confirmed evidence can raise. |
165
+ | `what_should_i_know` | Pre-task briefing built from the task: the dependencies it names (and the families it names: "all tauri plugins" adds the project's tauri-*, tauri-plugin-* and @tauri-apps/* packages; for "latest", whether each is already on the newest major), their versions and confirmed vulnerabilities, majors crossed, your recorded decisions, and a delegation verdict only confirmed evidence can raise. |
161
166
  | `ecosystem_pulse` | Hacker News headlines that name your dependencies, then your languages (labelled as such). Fetched only when called. |
162
167
  | `get_context` | Your tech stack, resolved dependency versions, interests, detected topics. |
163
168
  | `get_relevant_content`* | Scored content feed that passed the desktop app's relevance judge. |
164
169
  | `get_actionable_signals`* | Judge-accepted feed items the app classified (advisories, breaking changes), plus your live vulnerabilities. |
165
170
  | `knowledge_gaps`* | Dependencies with judge-accepted advisories or releases you have not looked at. |
166
- | `record_feedback`* | Save or dismiss items so 4DA can record explicit interaction history. |
171
+ | `record_feedback`* | Save or dismiss items; recorded in the app's own feedback tables, which its calibration reads. |
167
172
 
168
173
  ### Decisions & Memory
169
174
 
@@ -250,6 +255,7 @@ npx @4da/mcp-server --version # Print version
250
255
  |----------|-------------|---------|
251
256
  | `FOURDA_DB_PATH` | Path to 4DA's SQLite database | Auto-detected |
252
257
  | `FOURDA_OFFLINE` | Disable all network calls | `false` |
258
+ | `FOURDA_MCP_HOME` | Folder for the files the server writes (`cache.db`, `standalone.db`) | Your user data folder + `4da-mcp` |
253
259
  | `MCP_AUTH_SECRET` | Shared secret for verifying Bearer tokens on `--http` (HMAC-SHA256). Falls back to `JWT_SECRET`. Unset means no token is accepted. | Unset |
254
260
  | `MCP_AUTH_REQUIRED` | Require auth on a **loopback** `--http` bind. Always required on a non-loopback bind. | `false` |
255
261
  | `MCP_ALLOWED_HOSTS` | Extra comma-separated hostnames accepted in `Host`/`Origin` (needed when binding to `0.0.0.0`). | localhost only |
package/dist/db.d.ts CHANGED
@@ -1,11 +1,29 @@
1
1
  /**
2
2
  * Database module for 4DA MCP Server
3
3
  *
4
- * Read-only access to the 4DA SQLite database.
5
- * Only the record_feedback function performs writes.
4
+ * The desktop app's database (`4da.db`) is opened READ-ONLY
5
+ * (`{ readonly: true, fileMustExist: true }`) for everything the server reads.
6
+ * The app owns that file and its migrations: the server never creates or
7
+ * alters anything in it. It writes rows into exactly the app-owned tables its
8
+ * tools exist to write (APP_WRITABLE_TABLES: decisions, agent memory, feedback
9
+ * and interactions, which the app's calibration and decision UI read), through
10
+ * a separate write connection opened only for those statements (writerFor).
11
+ *
12
+ * Everything else the server writes goes to files it owns, under
13
+ * serverDataDir():
14
+ * - `cache.db`: registry, OSV and changelog responses (LiveCache).
15
+ * - `standalone.db`: the standalone database when no app is installed, and
16
+ * the fallback for a record the app's schema cannot hold (schema drift).
17
+ *
18
+ * Until 6.0.2 the server created a `live_cache` table inside the app's
19
+ * database (2,774 rows on one machine, 2026-10-07) and added `embedding`
20
+ * columns to two app tables when an embedding provider was set.
21
+ */
22
+ import { type SqliteDatabase } from "./sqlite-driver.js";
23
+ /**
24
+ * Null when a SQLite driver (node:sqlite, or better-sqlite3 with a working
25
+ * native module) can open a database here; otherwise why not, with the fix.
6
26
  */
7
- import type BetterSqlite3 from "better-sqlite3";
8
- /** Null when better-sqlite3 can open a database here; otherwise why not, with the fix. */
9
27
  export declare function nativeBindingProblem(): string | null;
10
28
  import type { RelevantItem, UserContext, FeedbackAction, FeedbackResult } from "./types.js";
11
29
  import type { ProjectScanResult } from "./project-scanner.js";
@@ -30,6 +48,28 @@ export declare function desktopAppDbPaths(platform?: NodeJS.Platform, env?: Node
30
48
  * in, so agent memory and decisions did not follow the user between projects.
31
49
  */
32
50
  export declare function standaloneDbPath(): string;
51
+ /**
52
+ * The directory holding every file this server writes: a per-user data
53
+ * directory (`%LOCALAPPDATA%\4da-mcp`, `~/Library/Application Support/4da-mcp`,
54
+ * `$XDG_DATA_HOME/4da-mcp`). FOURDA_MCP_HOME overrides it (tests use a temp dir).
55
+ */
56
+ export declare function serverDataDir(): string;
57
+ /** The server's response cache (registry, OSV, changelogs). Disposable: deleting it costs refetches only. */
58
+ export declare function serverCacheDbPath(): string;
59
+ /**
60
+ * Whether an error says the database file itself is unreadable: not SQLite at
61
+ * all, or damaged. SQLite reports these as SQLITE_NOTADB ("file is not
62
+ * a database") and SQLITE_CORRUPT ("database disk image is malformed").
63
+ */
64
+ export declare function isUnreadableDbError(error: unknown): boolean;
65
+ /**
66
+ * What to tell the user about an unreadable database. Every tool call used to
67
+ * answer with the bare SQLite text ("file is not a database"), with no way
68
+ * out. Nothing is deleted or moved automatically: the desktop app's database
69
+ * can be the user's whole corpus, and the standalone one holds their recorded
70
+ * decisions and agent memory.
71
+ */
72
+ export declare function unreadableDbMessage(absolutePath: string, error: unknown): string;
33
73
  export interface DatabaseValidationResult {
34
74
  valid: boolean;
35
75
  error?: string;
@@ -91,6 +131,20 @@ export declare function dedupeByTitle<T extends {
91
131
  * the `is_direct` standalone-schema gap slipped through precisely because the
92
132
  * two modes were only ever tested separately.
93
133
  */
134
+ /**
135
+ * The app-owned tables this server is designed to write when the desktop app
136
+ * is installed: decisions (decision_memory), agent memory (agent_memory) and
137
+ * feedback (record_feedback -> the app's calibration). Nothing else in the
138
+ * app's database is ever written, and nothing in it is created or altered.
139
+ */
140
+ export declare const APP_WRITABLE_TABLES: readonly ["developer_decisions", "agent_memory", "interactions", "feedback"];
141
+ export type AppWritableTable = (typeof APP_WRITABLE_TABLES)[number];
142
+ export interface WriteTarget {
143
+ raw: SqliteDatabase;
144
+ /** database = the open (standalone) database; app = the app's table; server_store = fallback on schema drift. */
145
+ target: "database" | "app" | "server_store";
146
+ note?: string;
147
+ }
94
148
  export declare const DEPENDENCY_GROUP_QUERY = "SELECT DISTINCT package_name, language, project_path, is_dev, is_direct FROM project_dependencies";
95
149
  /**
96
150
  * 4DA Database accessor
@@ -98,12 +152,38 @@ export declare const DEPENDENCY_GROUP_QUERY = "SELECT DISTINCT package_name, lan
98
152
  export declare class FourDADatabase {
99
153
  private db;
100
154
  private _isStandalone;
155
+ /** Absolute path of the open database file. */
156
+ readonly dbPath: string;
101
157
  constructor(dbPath?: string);
102
158
  /**
103
159
  * Whether this database was freshly created in standalone mode
104
160
  * (no pre-existing 4DA desktop app database found).
105
161
  */
106
162
  get isStandalone(): boolean;
163
+ /** True for the desktop app's database, which this connection only reads. */
164
+ get isReadOnly(): boolean;
165
+ private memoryStore;
166
+ private appWriter;
167
+ /**
168
+ * The connection a record into one of the app-owned tables this server is
169
+ * designed to write (APP_WRITABLE_TABLES) goes through, and where it lands.
170
+ *
171
+ * - Standalone database: the database itself.
172
+ * - The desktop app's database: a SEPARATE writable connection, opened on
173
+ * first use for these statements only (the main connection stays
174
+ * read-only), with a busy timeout so a write waits briefly for the app
175
+ * instead of failing. Only when the app's table has every column the
176
+ * statement names; nothing is ever created or altered in the app's file.
177
+ * - Schema drift (table or column missing in the app): the record goes to
178
+ * this server's own store (standaloneDbPath()) and `note` says so.
179
+ */
180
+ writerFor(table: AppWritableTable, columns: readonly string[]): WriteTarget;
181
+ /**
182
+ * The server's own store: its standalone database (standaloneDbPath()).
183
+ * Used for records the app's schema cannot hold (see writerFor). A
184
+ * standalone database is its own store.
185
+ */
186
+ getMemoryStore(): FourDADatabase;
107
187
  /**
108
188
  * Validate that a database file exists and is a readable SQLite database.
109
189
  *
@@ -112,7 +192,7 @@ export declare class FourDADatabase {
112
192
  * By default this runs a CHEAP probe (open + list tables, ~0.1s) which surfaces
113
193
  * a missing, locked, or corrupt-header file. It deliberately does NOT run a full
114
194
  * `PRAGMA integrity_check`: that is an O(database-size) scan (~11s on a 1.7 GB DB,
115
- * and growing) and, because better-sqlite3 is synchronous, it blocks the event
195
+ * and growing) and, because the SQLite drivers are synchronous, it blocks the event
116
196
  * loop — running it on the startup path stalls the MCP stdio handshake until it
117
197
  * finishes, so the host's connect timeout can fire and the server looks like it
118
198
  * "failed to start". Pass `{ deep: true }` for the full integrity_check on the
@@ -126,9 +206,9 @@ export declare class FourDADatabase {
126
206
  deep?: boolean;
127
207
  }): DatabaseValidationResult;
128
208
  /**
129
- * Get the raw better-sqlite3 database instance for custom queries
209
+ * Get the raw database connection (sqlite-driver.ts) for custom queries
130
210
  */
131
- getRawDb(): BetterSqlite3.Database;
211
+ getRawDb(): SqliteDatabase;
132
212
  /**
133
213
  * Close the database connection
134
214
  */
@@ -176,6 +256,7 @@ export declare class FourDADatabase {
176
256
  getFreshness(): DataFreshness;
177
257
  /** `MAX(sources.last_fetch)`, tolerant of a missing `sources` table (returns null). */
178
258
  private safeMaxLastFetch;
259
+ hasTable(table: string): boolean;
179
260
  hasColumn(table: string, column: string): boolean;
180
261
  /**
181
262
  * Idempotently add a column to a table if it does not already exist.