@4da/mcp-server 5.0.4 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/CHANGELOG.md +698 -0
  2. package/README.md +70 -39
  3. package/dist/build-staleness.d.ts +35 -0
  4. package/dist/build-staleness.js +95 -0
  5. package/dist/db.d.ts +53 -58
  6. package/dist/db.js +226 -299
  7. package/dist/doctor.js +27 -7
  8. package/dist/embeddings.js +7 -1
  9. package/dist/freshness-markers.d.ts +34 -0
  10. package/dist/freshness-markers.js +74 -0
  11. package/dist/gitignore.d.ts +37 -0
  12. package/dist/gitignore.js +130 -0
  13. package/dist/index.js +127 -44
  14. package/dist/live/bun-lockfile.d.ts +26 -0
  15. package/dist/live/bun-lockfile.js +123 -0
  16. package/dist/live/cargo-platform.d.ts +38 -0
  17. package/dist/live/cargo-platform.js +178 -0
  18. package/dist/live/changelog-classify.d.ts +41 -0
  19. package/dist/live/changelog-classify.js +207 -0
  20. package/dist/live/changelog.d.ts +64 -0
  21. package/dist/live/changelog.js +318 -0
  22. package/dist/live/crates-registry.js +3 -1
  23. package/dist/live/crates-versions.d.ts +47 -0
  24. package/dist/live/crates-versions.js +201 -0
  25. package/dist/live/dependency-set.d.ts +13 -0
  26. package/dist/live/dependency-set.js +95 -0
  27. package/dist/live/dev-scope.d.ts +32 -0
  28. package/dist/live/dev-scope.js +102 -0
  29. package/dist/live/file-signature.d.ts +18 -0
  30. package/dist/live/file-signature.js +43 -0
  31. package/dist/live/go-registry.js +3 -1
  32. package/dist/live/hn-fetcher.js +9 -3
  33. package/dist/live/index.d.ts +105 -2
  34. package/dist/live/index.js +266 -139
  35. package/dist/live/install-state.d.ts +43 -0
  36. package/dist/live/install-state.js +150 -0
  37. package/dist/live/js-lockfile-readers.d.ts +46 -0
  38. package/dist/live/js-lockfile-readers.js +181 -0
  39. package/dist/live/lockfile-parsers-pygo.d.ts +60 -0
  40. package/dist/live/lockfile-parsers-pygo.js +274 -0
  41. package/dist/live/lockfile-parsers.d.ts +69 -0
  42. package/dist/live/lockfile-parsers.js +311 -0
  43. package/dist/live/lockfile-types.d.ts +34 -0
  44. package/dist/live/lockfile-types.js +42 -0
  45. package/dist/live/maintenance.d.ts +33 -0
  46. package/dist/live/maintenance.js +37 -0
  47. package/dist/live/npm-packument.d.ts +47 -0
  48. package/dist/live/npm-packument.js +150 -0
  49. package/dist/live/npm-registry.js +67 -53
  50. package/dist/live/osv-scanner.d.ts +12 -1
  51. package/dist/live/osv-scanner.js +50 -15
  52. package/dist/live/package-archive.d.ts +49 -0
  53. package/dist/live/package-archive.js +186 -0
  54. package/dist/live/pypi-registry.js +3 -1
  55. package/dist/live/registry-health.d.ts +16 -0
  56. package/dist/live/registry-health.js +74 -0
  57. package/dist/live/release-metadata.d.ts +67 -0
  58. package/dist/live/release-metadata.js +16 -0
  59. package/dist/live/resolution.d.ts +43 -0
  60. package/dist/live/resolution.js +62 -0
  61. package/dist/live/semver-precedence.d.ts +30 -0
  62. package/dist/live/semver-precedence.js +83 -0
  63. package/dist/live/severity-scope.d.ts +57 -0
  64. package/dist/live/severity-scope.js +89 -0
  65. package/dist/live/types.d.ts +83 -0
  66. package/dist/live/upgrade-sources.d.ts +71 -0
  67. package/dist/live/upgrade-sources.js +198 -0
  68. package/dist/live/version-compare.d.ts +39 -0
  69. package/dist/live/version-compare.js +130 -0
  70. package/dist/live/version-resolver.d.ts +14 -1
  71. package/dist/live/version-resolver.js +93 -303
  72. package/dist/native-bindings.d.ts +19 -0
  73. package/dist/native-bindings.js +39 -0
  74. package/dist/project-manifests.d.ts +40 -0
  75. package/dist/project-manifests.js +193 -0
  76. package/dist/project-scanner.js +38 -48
  77. package/dist/project-tree.d.ts +41 -0
  78. package/dist/project-tree.js +110 -0
  79. package/dist/prompts.d.ts +28 -0
  80. package/dist/prompts.js +54 -0
  81. package/dist/schema-registry.d.ts +41 -29
  82. package/dist/schema-registry.js +98 -105
  83. package/dist/server-instructions.d.ts +11 -0
  84. package/dist/server-instructions.js +36 -0
  85. package/dist/tool-args.d.ts +28 -0
  86. package/dist/tool-args.js +93 -0
  87. package/dist/tool-dispatch.d.ts +1 -1
  88. package/dist/tool-dispatch.js +42 -8
  89. package/dist/tools/app-plan.d.ts +92 -0
  90. package/dist/tools/app-plan.js +155 -0
  91. package/dist/tools/briefing-stored-advisories.d.ts +20 -0
  92. package/dist/tools/briefing-stored-advisories.js +58 -0
  93. package/dist/tools/briefing-task-scope.d.ts +73 -0
  94. package/dist/tools/briefing-task-scope.js +197 -0
  95. package/dist/tools/briefing-wisdom.d.ts +31 -0
  96. package/dist/tools/briefing-wisdom.js +148 -0
  97. package/dist/tools/decision-enforcement.d.ts +0 -5
  98. package/dist/tools/decision-enforcement.js +0 -4
  99. package/dist/tools/decision-memory.js +18 -66
  100. package/dist/tools/dependency-check-signals.d.ts +74 -0
  101. package/dist/tools/dependency-check-signals.js +264 -0
  102. package/dist/tools/dependency-check.d.ts +98 -0
  103. package/dist/tools/dependency-check.js +295 -0
  104. package/dist/tools/dependency-health.js +74 -21
  105. package/dist/tools/developer-dna.d.ts +3 -0
  106. package/dist/tools/developer-dna.js +16 -15
  107. package/dist/tools/ecosystem-pulse.d.ts +6 -2
  108. package/dist/tools/ecosystem-pulse.js +53 -14
  109. package/dist/tools/get-actionable-signals.d.ts +26 -4
  110. package/dist/tools/get-actionable-signals.js +214 -251
  111. package/dist/tools/get-context.d.ts +2 -0
  112. package/dist/tools/get-context.js +9 -1
  113. package/dist/tools/get-relevant-content.d.ts +3 -1
  114. package/dist/tools/get-relevant-content.js +34 -3
  115. package/dist/tools/index.d.ts +4 -2
  116. package/dist/tools/index.js +4 -2
  117. package/dist/tools/install-drift-notes.d.ts +51 -0
  118. package/dist/tools/install-drift-notes.js +172 -0
  119. package/dist/tools/knowledge-gap-citations.d.ts +55 -0
  120. package/dist/tools/knowledge-gap-citations.js +132 -0
  121. package/dist/tools/knowledge-gap-exposure.d.ts +72 -0
  122. package/dist/tools/knowledge-gap-exposure.js +160 -0
  123. package/dist/tools/knowledge-gap-grading.d.ts +91 -0
  124. package/dist/tools/knowledge-gap-grading.js +157 -0
  125. package/dist/tools/knowledge-gap-installs.d.ts +65 -0
  126. package/dist/tools/knowledge-gap-installs.js +134 -0
  127. package/dist/tools/knowledge-gap-ranges.d.ts +51 -0
  128. package/dist/tools/knowledge-gap-ranges.js +146 -0
  129. package/dist/tools/knowledge-gap-scope.d.ts +63 -0
  130. package/dist/tools/knowledge-gap-scope.js +118 -0
  131. package/dist/tools/knowledge-gaps.d.ts +12 -3
  132. package/dist/tools/knowledge-gaps.js +172 -121
  133. package/dist/tools/package-presence.d.ts +24 -0
  134. package/dist/tools/package-presence.js +35 -0
  135. package/dist/tools/record-feedback.d.ts +0 -1
  136. package/dist/tools/record-feedback.js +7 -8
  137. package/dist/tools/signal-classifier.d.ts +25 -0
  138. package/dist/tools/signal-classifier.js +40 -0
  139. package/dist/tools/upgrade-impact-callsites.d.ts +52 -0
  140. package/dist/tools/upgrade-impact-callsites.js +226 -0
  141. package/dist/tools/upgrade-impact-literals.d.ts +45 -0
  142. package/dist/tools/upgrade-impact-literals.js +118 -0
  143. package/dist/tools/upgrade-impact-report.d.ts +80 -0
  144. package/dist/tools/upgrade-impact-report.js +178 -0
  145. package/dist/tools/upgrade-impact.d.ts +84 -0
  146. package/dist/tools/upgrade-impact.js +305 -0
  147. package/dist/tools/upgrade-planner.d.ts +41 -8
  148. package/dist/tools/upgrade-planner.js +201 -53
  149. package/dist/tools/vulnerability-scan-concise.d.ts +72 -0
  150. package/dist/tools/vulnerability-scan-concise.js +97 -0
  151. package/dist/tools/vulnerability-scan-format.d.ts +137 -0
  152. package/dist/tools/vulnerability-scan-format.js +276 -0
  153. package/dist/tools/vulnerability-scan.d.ts +338 -18
  154. package/dist/tools/vulnerability-scan.js +84 -125
  155. package/dist/tools/what-should-i-know.d.ts +86 -19
  156. package/dist/tools/what-should-i-know.js +277 -283
  157. package/dist/types.d.ts +4 -136
  158. package/package.json +35 -35
  159. package/dist/schemas/agent-memory.json +0 -49
  160. package/dist/schemas/decision-enforcement.json +0 -22
  161. package/dist/schemas/decision-memory.json +0 -54
  162. package/dist/schemas/dependency-health.json +0 -27
  163. package/dist/schemas/developer-dna.json +0 -219
  164. package/dist/schemas/ecosystem-pulse.json +0 -17
  165. package/dist/schemas/get-actionable-signals.json +0 -29
  166. package/dist/schemas/get-context.json +0 -17
  167. package/dist/schemas/get-relevant-content.json +0 -26
  168. package/dist/schemas/knowledge-gaps.json +0 -14
  169. package/dist/schemas/record-feedback.json +0 -24
  170. package/dist/schemas/upgrade-planner.json +0 -22
  171. package/dist/schemas/vulnerability-scan.json +0 -26
  172. package/dist/schemas/what-should-i-know.json +0 -19
package/README.md CHANGED
@@ -2,30 +2,48 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@4da/mcp-server?color=gold)](https://www.npmjs.com/package/@4da/mcp-server)
4
4
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
5
- [![Node](https://img.shields.io/badge/Node-%3E%3D20-brightgreen)](https://nodejs.org/)
6
- [![smithery badge](https://smithery.ai/badge/@4da/mcp-server)](https://smithery.ai/server/@4da/mcp-server)
5
+ [![Node](https://img.shields.io/badge/Node-%3E%3D22-brightgreen)](https://nodejs.org/)
6
+ [![CI](https://github.com/4DA-Systems/4da-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/4DA-Systems/4da-mcp-server/actions/workflows/ci.yml)
7
7
 
8
- **Dependency intelligence for AI coding agents.** Live CVE scanning, dependency health checks, upgrade planning, ecosystem news, and persistent decision memory. Zero config, privacy-first.
8
+ **Upgrade intelligence for AI coding agents.** Before your agent bumps a dependency it learns what changes between the version you run and the one you want, which of your files that touches, and which vulnerabilities the move fixes, from your own lockfiles, on your machine. Plus vulnerability scanning at osv-scanner parity, ranked upgrade plans and decision memory. Zero config, no account.
9
9
 
10
10
  ```
11
- You: "Check my dependency health"
12
- Claude: Health: 72/100. 47 dependencies scanned, 3 vulnerable, 1 deprecated, 8 outdated.
13
-
14
- CRITICAL openssl-sys 0.9.93 CVE-2025-4231 -> 0.9.96
15
- HIGH serde 1.0.197 RUSTSEC-2026-12 -> 1.0.210
16
- MEDIUM cookie 0.17.0 deprecated -> 0.18.1
17
-
18
- Quick wins: 6 patch upgrades, 2 minor. Run upgrade_planner for full plan.
11
+ You: "Upgrade axum to 0.8"
12
+ Agent → upgrade_impact { package: "axum", to_version: "0.8.4" }
13
+
14
+ axum 0.7.9 -> 0.8.4: 5 releases (0.8.2 yanked), changelog from the 0.8.4 crate.
15
+ 12 breaking entries, 8 touch your code (Path, Query, Router, extract, serve):
16
+ breaking Remove OptionalFromRequestParts impl for `Query` (you use Query)
17
+ breaking Require `Sync` for all handlers added to `Router` (you use Router)
18
+ ...
19
+ Your code: 10 files import axum.
19
20
  ```
20
21
 
21
- One command to install. No API keys. No accounts. No code leaves your machine.
22
+ *(Real output, abridged, run on this repository, 2026-10-02.)*
23
+
24
+ One command to install. No API keys. No accounts. Your code never leaves your machine.
22
25
 
23
26
  ## Install
24
27
 
28
+ Requires Node.js 22 or later.
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.
33
+
25
34
  ```bash
26
35
  claude mcp add 4da -- npx @4da/mcp-server
27
36
  ```
28
37
 
38
+ **As a Claude Code plugin** (the MCP server plus a hook):
39
+
40
+ ```bash
41
+ claude plugin marketplace add 4DA-Systems/4da-mcp-server
42
+ claude plugin install 4da@4da
43
+ ```
44
+
45
+ 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
+
29
47
  <details>
30
48
  <summary><b>Cursor / Windsurf</b></summary>
31
49
 
@@ -83,34 +101,39 @@ npx @4da/mcp-server --setup
83
101
  ```
84
102
  </details>
85
103
 
86
- Then ask your AI: **"Check my dependency health"** or **"Scan for vulnerabilities"**
104
+ Then ask your AI: **"What changes if I upgrade X to Y?"**, **"Scan for vulnerabilities"** or **"What should I upgrade first?"**
87
105
 
88
106
  ## How It Works
89
107
 
90
- On startup, the server reads your manifest and lock files (`package.json`, `Cargo.toml`, `go.mod`, `pyproject.toml`), resolves exact dependency versions, and queries live APIs:
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
+
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.
91
111
 
92
- - **OSV.dev** for known CVEs across all ecosystems
93
- - **npm registry** for version freshness, deprecation status, and weekly downloads
94
- - **crates.io sparse index** for Rust package versions (avoids the 1 req/s API limit)
95
- - **PyPI JSON API** for Python package metadata with license normalization
96
- - **Go module proxy** for Go module versions
97
- - **Hacker News Algolia API** for ecosystem news filtered by your tech stack
112
+ - **OSV.dev** for known vulnerabilities, matched to exact installed versions
113
+ - **npm registry, crates.io, PyPI, Go module proxy** for versions, deprecations and yanks
114
+ - **The package's own registry archive** (registry.npmjs.org, static.crates.io) for the changelog `upgrade_impact` reads; never GitHub
115
+ - **npm full packument** and the **crates.io versions API** for `dependency_check` (publish times, publishers, install scripts, per-version dependencies). These requests carry the package name only; the version you have installed is never sent to a registry. Packuments are cached on disk and revalidated with `If-None-Match`; crates.io API reads are spaced one per second.
116
+ - **Hacker News Algolia API**, only when you call `ecosystem_pulse`
98
117
 
99
118
  Results are cached (24h for registry data, 1h for vulnerabilities, 30min for news) and rate-limited per source.
100
119
 
101
- **What's sent over the network:** package names + versions, generic tech keywords. The same data visible in your `package.json`. No source code, no file paths, no personal data. Set `FOURDA_OFFLINE=true` to disable all network calls.
120
+ **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.
102
121
 
103
122
  > 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.
104
123
 
105
- **Ecosystems supported:** npm, crates.io (Rust), PyPI (Python), Go.
124
+ **Ecosystems supported:** npm, crates.io (Rust), PyPI (Python), Go. `upgrade_impact`: npm and crates.io.
125
+
126
+ **Known limits, stated plainly:** a `requirements.txt` without a lockfile names only your direct pins, so their transitive dependencies are not scanned (use `uv lock`, `poetry lock` or `pip-compile`). Many packages ship no changelog in their registry archive (fastembed, vite, zod among them); `upgrade_impact` then says so and gives the release-notes URL instead of guessing. Breaking entries are flagged from the changelog's headings and wording, and every entry carries the heading it sits under (`under`). Measured on 47 upgrades never used to build the rules, against three blind raters who saw each entry's heading: 99% of entries flagged breaking were breaking (95% CI 95-100%) and about 65% of breaking entries were flagged. Earlier corpora, before the last parser fixes, measured 73-87%, so treat the flags as a pre-sort: read every entry of a major upgrade. Each answer says this in `_meta.classification`.
106
127
 
107
128
  ## What You Can Ask
108
129
 
109
130
  ```
131
+ "What changes if I upgrade axum to 0.8?" -> upgrade_impact
110
132
  "Check my dependency health" -> dependency_health
111
133
  "Scan for vulnerabilities" -> vulnerability_scan
112
134
  "Which deps should I upgrade first?" -> upgrade_planner
113
- "What should I know before I start coding?" -> what_should_i_know
135
+ "Is it safe to bump axios to 1.14.1?" -> dependency_check
136
+ "I'm about to bump fastembed 5 -> 7" -> what_should_i_know
114
137
  "What's happening in the ecosystem?" -> ecosystem_pulse
115
138
  "What's my tech stack?" -> get_context
116
139
  "Record a decision: we chose Postgres" -> decision_memory
@@ -118,26 +141,28 @@ Results are cached (24h for registry data, 1h for vulnerabilities, 30min for new
118
141
  "Remember: never use ORM for batch inserts" -> agent_memory
119
142
  ```
120
143
 
121
- ## All 14 Tools
144
+ ## All 16 Tools
122
145
 
123
146
  ### Dependency Security
124
147
 
125
148
  | Tool | What it does |
126
149
  |------|-------------|
127
- | `vulnerability_scan` | Live CVE scanning via OSV.dev. Severity, fix versions, CVSS scores. |
128
- | `dependency_health` | Health score (0-100) + version freshness, deprecation, CVE counts per dependency. |
129
- | `upgrade_planner` | Ranked upgrade recommendations. Quick wins vs. breaking changes. Risk-sorted. |
150
+ | `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. |
152
+ | `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. |
130
155
 
131
156
  ### Intelligence
132
157
 
133
158
  | Tool | What it does |
134
159
  |------|-------------|
135
- | `what_should_i_know` | Pre-task intelligence briefing: vulns, decisions, signals, ecosystem updates. |
136
- | `ecosystem_pulse` | Live ecosystem news from Hacker News, filtered by your detected tech stack. |
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. |
161
+ | `ecosystem_pulse` | Hacker News headlines that name your dependencies, then your languages (labelled as such). Fetched only when called. |
137
162
  | `get_context` | Your tech stack, resolved dependency versions, interests, detected topics. |
138
- | `get_relevant_content`* | Scored content feed — articles, advisories, releases ranked by relevance. |
139
- | `get_actionable_signals`* | Classified alerts: security advisories, breaking changes, trending repos. |
140
- | `knowledge_gaps`* | Dependencies you use daily but never read about. Surfaces missed CVEs and updates. |
163
+ | `get_relevant_content`* | Scored content feed that passed the desktop app's relevance judge. |
164
+ | `get_actionable_signals`* | Judge-accepted feed items the app classified (advisories, breaking changes), plus your live vulnerabilities. |
165
+ | `knowledge_gaps`* | Dependencies with judge-accepted advisories or releases you have not looked at. |
141
166
  | `record_feedback`* | Save or dismiss items so 4DA can record explicit interaction history. |
142
167
 
143
168
  ### Decisions & Memory
@@ -156,16 +181,22 @@ Results are cached (24h for registry data, 1h for vulnerabilities, 30min for new
156
181
 
157
182
  *\* Requires the [4DA desktop app](https://4da.ai) for full data.*
158
183
 
184
+ ### Prompt: `deps`
185
+
186
+ A user-invoked workflow (shown as a slash command by hosts that surface MCP prompts). It tells the agent to run `upgrade_planner`, check every proposed bump with `dependency_check`, apply only `proceed` items in small batches with the project's own tests after each batch, stop and report every `review` / `avoid` / `wait` / `unknown` item with its evidence, and re-run `vulnerability_scan` at the end. Optional argument `scope` (e.g. `security only`).
187
+
159
188
  ## Standalone vs. Full Mode
160
189
 
161
- The MCP server works without the desktop app. On first run it creates a local database and scans your project:
190
+ The MCP server works without the desktop app. It keeps a small local database in your user data folder (`%LOCALAPPDATA%\4da-mcp`, `~/Library/Application Support/4da-mcp` or `~/.local/share/4da-mcp`, never inside your repository) and scans your project on every start:
162
191
 
163
192
  | Capability | Standalone | With 4DA Desktop |
164
193
  |------------|-----------|-------------------|
194
+ | Upgrade impact (changelog, breaking changes, your call sites) | Yes | Yes |
165
195
  | Vulnerability scanning (OSV.dev) | Yes | Yes |
166
196
  | Dependency health (4 registries) | Yes | Yes |
167
197
  | Upgrade planner | Yes | Yes |
168
- | Ecosystem news (Hacker News) | Yes | Yes |
198
+ | Pre-install dependency check | Yes | Yes |
199
+ | Ecosystem news (Hacker News, on request) | Yes | Yes |
169
200
  | Pre-task intelligence briefing | Yes | Yes |
170
201
  | Tech stack detection + resolved versions | Yes | Yes |
171
202
  | Decision memory + alignment checking | Yes | Yes |
@@ -226,10 +257,10 @@ npx @4da/mcp-server --version # Print version
226
257
  ## FAQ
227
258
 
228
259
  **Does this send my code anywhere?**
229
- No. The server sends package names and versions to public APIs ([OSV.dev](https://osv.dev), npm registry, crates.io, PyPI, Go proxy) and generic tech keywords to [HN Algolia](https://hn.algolia.com/api). The same public data visible in your `package.json`. No source code, no file paths, no personal data. Set `FOURDA_OFFLINE=true` to disable all network calls. (The sole exception is opt-in OpenAI embeddings — see the network note above.)
260
+ No. The server sends package names and versions to public APIs ([OSV.dev](https://osv.dev), npm registry, crates.io, PyPI, Go proxy), downloads the target version's archive from the package's own registry for `upgrade_impact`, and, only when you call `ecosystem_pulse`, sends a few dependency names as search terms to [HN Algolia](https://hn.algolia.com/api). No source code, no file paths, no personal data: the call-site scan runs locally. Set `FOURDA_OFFLINE=true` to disable all network calls. (The sole exception is opt-in OpenAI embeddings — see the network note above.)
230
261
 
231
262
  **Do I need the 4DA desktop app?**
232
- No. 9 tools work standalone: vulnerability scanning, dependency health, upgrade planning, ecosystem news, pre-task briefings, project context, decision memory, alignment checking, and agent memory. The desktop app adds a scored content feed from 20+ sources, graded against your actual stack.
263
+ No. 11 tools work standalone: upgrade impact, pre-install dependency checks, vulnerability scanning, dependency health, upgrade planning, ecosystem news, pre-task briefings, project context, decision memory, alignment checking, and agent memory. The desktop app adds a scored content feed from 20+ sources, judged against your actual stack.
233
264
 
234
265
  **Which AI tools does this work with?**
235
266
  Any tool that supports [MCP](https://modelcontextprotocol.io): Claude Code, Claude Desktop, Cursor, Windsurf, VS Code (Copilot), and any custom MCP client.
@@ -237,11 +268,11 @@ Any tool that supports [MCP](https://modelcontextprotocol.io): Claude Code, Clau
237
268
  ## Build from Source
238
269
 
239
270
  ```bash
240
- git clone https://github.com/4DA-Systems/4DA.git
241
- cd 4DA/mcp-4da-server
271
+ git clone https://github.com/4DA-Systems/4da-mcp-server.git
272
+ cd 4da-mcp-server
242
273
  pnpm install
243
274
  pnpm build
244
- pnpm test # 71 contract tests
275
+ pnpm test # offline
245
276
  ```
246
277
 
247
278
  ## License
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Build staleness — is the running `dist/` older than `src/`?
3
+ *
4
+ * `.mcp.json` runs `node ./mcp-4da-server/dist/index.js`, and nothing rebuilds
5
+ * `dist/` when a merge lands. 2026-08-30: the live server was a dist built two
6
+ * days before its src — three already-fixed defects (platform filtering,
7
+ * knowledge-gap grading, feed hygiene) presented as live bugs and were nearly
8
+ * re-fixed. The same merged-but-not-running disease as a stale binary, with
9
+ * zero symptoms.
10
+ *
11
+ * This check only means something in a repo checkout: the published npm
12
+ * package ships no `src/`, so it returns null there (unknown-and-irrelevant,
13
+ * never a false alarm). In a checkout it compares the newest source mtime
14
+ * against the newest dist mtime — a `git pull` touches exactly the changed
15
+ * files, which is exactly the signal needed.
16
+ */
17
+ export interface BuildStaleness {
18
+ stale: boolean;
19
+ /** Newest mtime under src/ (ms). */
20
+ src_newest_ms: number;
21
+ /** Newest mtime under dist/ (ms). */
22
+ dist_newest_ms: number;
23
+ /** Human-readable remedy, set when stale. */
24
+ note: string | null;
25
+ }
26
+ /** Reset the memo. Tests only. */
27
+ export declare function _resetBuildStalenessCache(): void;
28
+ /**
29
+ * Compare `src/` against `dist/` for the package this module runs from.
30
+ *
31
+ * Returns null when there is no `src/` beside the running `dist/` (the
32
+ * published package) or no dist output at all — unknown, never "fresh".
33
+ * Pass explicit directories in tests.
34
+ */
35
+ export declare function checkBuildStaleness(distDir?: string, srcDir?: string): BuildStaleness | null;
@@ -0,0 +1,95 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Build staleness — is the running `dist/` older than `src/`?
4
+ *
5
+ * `.mcp.json` runs `node ./mcp-4da-server/dist/index.js`, and nothing rebuilds
6
+ * `dist/` when a merge lands. 2026-08-30: the live server was a dist built two
7
+ * days before its src — three already-fixed defects (platform filtering,
8
+ * knowledge-gap grading, feed hygiene) presented as live bugs and were nearly
9
+ * re-fixed. The same merged-but-not-running disease as a stale binary, with
10
+ * zero symptoms.
11
+ *
12
+ * This check only means something in a repo checkout: the published npm
13
+ * package ships no `src/`, so it returns null there (unknown-and-irrelevant,
14
+ * never a false alarm). In a checkout it compares the newest source mtime
15
+ * against the newest dist mtime — a `git pull` touches exactly the changed
16
+ * files, which is exactly the signal needed.
17
+ */
18
+ import * as fs from "node:fs";
19
+ import * as path from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+ /** Editable-source extensions that produce build output. */
22
+ const SRC_EXTENSIONS = new Set([".ts", ".json"]);
23
+ /** Skip clock-skew false alarms: src must be newer by more than this. */
24
+ const SKEW_TOLERANCE_MS = 2_000;
25
+ function newestMtimeMs(dir, filter) {
26
+ let newest = 0;
27
+ let entries;
28
+ try {
29
+ entries = fs.readdirSync(dir, { withFileTypes: true });
30
+ }
31
+ catch {
32
+ return newest;
33
+ }
34
+ for (const entry of entries) {
35
+ const full = path.join(dir, entry.name);
36
+ if (entry.isDirectory()) {
37
+ if (entry.name === "node_modules" || entry.name.startsWith("."))
38
+ continue;
39
+ const sub = newestMtimeMs(full, filter);
40
+ if (sub > newest)
41
+ newest = sub;
42
+ }
43
+ else if (filter(entry.name)) {
44
+ try {
45
+ const m = fs.statSync(full).mtimeMs;
46
+ if (m > newest)
47
+ newest = m;
48
+ }
49
+ catch {
50
+ // A file deleted mid-scan is not a staleness signal.
51
+ }
52
+ }
53
+ }
54
+ return newest;
55
+ }
56
+ let memo;
57
+ /** Reset the memo. Tests only. */
58
+ export function _resetBuildStalenessCache() {
59
+ memo = undefined;
60
+ }
61
+ /**
62
+ * Compare `src/` against `dist/` for the package this module runs from.
63
+ *
64
+ * Returns null when there is no `src/` beside the running `dist/` (the
65
+ * published package) or no dist output at all — unknown, never "fresh".
66
+ * Pass explicit directories in tests.
67
+ */
68
+ export function checkBuildStaleness(distDir, srcDir) {
69
+ if (distDir === undefined && memo !== undefined)
70
+ return memo;
71
+ const here = distDir ?? path.dirname(fileURLToPath(import.meta.url));
72
+ const src = srcDir ?? path.join(here, "..", "src");
73
+ const result = compute(here, src);
74
+ if (distDir === undefined)
75
+ memo = result;
76
+ return result;
77
+ }
78
+ function compute(distDir, srcDir) {
79
+ if (!fs.existsSync(srcDir))
80
+ return null;
81
+ const srcNewest = newestMtimeMs(srcDir, (n) => SRC_EXTENSIONS.has(path.extname(n)));
82
+ const distNewest = newestMtimeMs(distDir, (n) => n.endsWith(".js"));
83
+ if (srcNewest === 0 || distNewest === 0)
84
+ return null;
85
+ const stale = srcNewest > distNewest + SKEW_TOLERANCE_MS;
86
+ const behindMinutes = Math.round((srcNewest - distNewest) / 60_000);
87
+ return {
88
+ stale,
89
+ src_newest_ms: srcNewest,
90
+ dist_newest_ms: distNewest,
91
+ note: stale
92
+ ? `This server's dist/ build is ~${behindMinutes} min older than src/ — its behavior does not include the latest merged fixes. Rebuild with: pnpm --dir mcp-4da-server run build`
93
+ : null,
94
+ };
95
+ }
package/dist/db.d.ts CHANGED
@@ -5,8 +5,31 @@
5
5
  * Only the record_feedback function performs writes.
6
6
  */
7
7
  import type BetterSqlite3 from "better-sqlite3";
8
- import type { SourceItem, RelevantItem, UserContext, RelevanceExplanation, FeedbackAction, FeedbackResult } from "./types.js";
8
+ /** Null when better-sqlite3 can open a database here; otherwise why not, with the fix. */
9
+ export declare function nativeBindingProblem(): string | null;
10
+ import type { RelevantItem, UserContext, FeedbackAction, FeedbackResult } from "./types.js";
9
11
  import type { ProjectScanResult } from "./project-scanner.js";
12
+ /**
13
+ * Where the deployed desktop app keeps its database, mirroring
14
+ * `src-tauri/src/state.rs::get_db_path` / `get_platform_data_dir`:
15
+ * FOURDA_DATA_DIR overrides; Windows `%APPDATA%\com.4da.app\data`, macOS
16
+ * `~/Library/Application Support/com.4da.app/data`, Linux
17
+ * `$XDG_DATA_HOME/4da/data` (default `~/.local/share/4da/data`).
18
+ *
19
+ * Measured 2026-10-03 (pre-publish verification): this looked in
20
+ * `~/.local/share/com.4da.app/data` on Linux, a directory the app never
21
+ * writes, so a Linux desktop user's server never found the app's database and
22
+ * never offered the desktop tools. The old path is still checked last, in case
23
+ * a database was ever placed there by hand.
24
+ */
25
+ export declare function desktopAppDbPaths(platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv, home?: string): string[];
26
+ /**
27
+ * Where a standalone install keeps its database: a per-user data directory.
28
+ * It used to be `<cwd>/data/4da.db` — inside the user's own repository, where
29
+ * it could be committed, and one per directory the server happened to start
30
+ * in, so agent memory and decisions did not follow the user between projects.
31
+ */
32
+ export declare function standaloneDbPath(): string;
10
33
  export interface DatabaseValidationResult {
11
34
  valid: boolean;
12
35
  error?: string;
@@ -39,6 +62,19 @@ export interface DataFreshness {
39
62
  is_stale: boolean;
40
63
  /** Human-readable summary with the remedy when stale. */
41
64
  note: string;
65
+ /** Set when `data/.engine-blocked` reports a schema-refused scheduled refresh. */
66
+ engine_blocked_at?: string;
67
+ /** The refusal error recorded by the blocked engine. */
68
+ engine_blocked_error?: string;
69
+ /**
70
+ * Set when `data/.db-recovered` reports that the headless refresh engine
71
+ * restored the database from a backup or quarantined it: when it happened.
72
+ */
73
+ db_recovered_at?: string;
74
+ /** `restored_from_backup` | `quarantined_no_backup` | `recovery_failed` (a newer engine may add kinds). */
75
+ db_recovery_kind?: string;
76
+ /** The preserved file's path, or the failure reason. */
77
+ db_recovery_detail?: string;
42
78
  }
43
79
  /**
44
80
  * Collapse near-duplicate stories (same news via reddit + rss + mastodon…) by
@@ -123,7 +159,15 @@ export declare class FourDADatabase {
123
159
  * Get relevant content items scored by the Rust pipeline (preferred) or
124
160
  * TypeScript fallback scoring when Rust scores are unavailable.
125
161
  */
126
- getRelevantContent(minScore?: number, sourceType?: string, limit?: number, sinceHours?: number, requireCurrentVersion?: boolean): RelevantItem[];
162
+ getRelevantContent(minScore?: number, sourceType?: string, limit?: number, sinceHours?: number, requireCurrentVersion?: boolean,
163
+ /**
164
+ * Restrict to items the scoring pipeline stamped with this signal_type.
165
+ * Lets a type-specific read (a security-only pass) fill its limit with
166
+ * matches instead of competing for the top-N slots across every type.
167
+ * Only meaningful over pipeline-stamped rows: without Rust scores there
168
+ * are no stored types, so the read returns nothing rather than pretend.
169
+ */
170
+ signalType?: string): RelevantItem[];
127
171
  /**
128
172
  * Read the live freshness state of the feed database. Answered from ground truth, not from any
129
173
  * claim that a refresh ran — so DB-backed tools can tell the caller whether the curated feed is
@@ -149,10 +193,6 @@ export declare class FourDADatabase {
149
193
  * Fallback: TypeScript keyword scoring for standalone mode (no Rust pipeline).
150
194
  */
151
195
  private getRelevantContentFallback;
152
- /**
153
- * Get a single source item by ID and type
154
- */
155
- getSourceItem(itemId: number, sourceType: string): SourceItem | null;
156
196
  /**
157
197
  * Look up persisted necessity scores for an item (written by Rust analysis pipeline).
158
198
  * Returns defaults (0.0/null) if no necessity data exists yet.
@@ -163,61 +203,16 @@ export declare class FourDADatabase {
163
203
  */
164
204
  getUserContext(includeAce?: boolean, includeLearned?: boolean): UserContext;
165
205
  /**
166
- * Explain why an item was considered relevant
206
+ * Record user feedback on an item
167
207
  */
168
- explainRelevance(itemId: number, sourceType: string): RelevanceExplanation | null;
208
+ recordFeedback(itemId: number, sourceType: string | undefined, action: FeedbackAction): FeedbackResult;
169
209
  /**
170
- * Record user feedback on an item
210
+ * Ensure the `feedback` table exists. The desktop app always has it; a
211
+ * standalone database created before record_feedback wrote labels does not
212
+ * (createMinimalSchema runs only for brand-new files). Same DDL as desktop,
213
+ * so against the desktop database this is a no-op.
171
214
  */
172
- recordFeedback(itemId: number, sourceType: string, action: FeedbackAction): FeedbackResult;
173
- /**
174
- * Ensure the agent_feedback table exists.
175
- * Called on first use so existing databases (created before this feature)
176
- * get the table added without requiring a migration.
177
- */
178
- private ensureAgentFeedbackTable;
179
- /**
180
- * Record agent feedback on recommended content items
181
- */
182
- recordAgentFeedback(itemIds: string[], outcome: string, context?: string, sessionTask?: string): {
183
- success: boolean;
184
- message: string;
185
- recorded_count: number;
186
- };
187
- /**
188
- * Get agent feedback statistics for the given period
189
- */
190
- getAgentFeedbackStats(days?: number): {
191
- period_days: number;
192
- outcomes: {
193
- used: number;
194
- rejected: number;
195
- partially_used: number;
196
- total: number;
197
- };
198
- by_source: Array<{
199
- source_type: string;
200
- used: number;
201
- rejected: number;
202
- partially_used: number;
203
- total: number;
204
- usefulness_rate: number;
205
- }>;
206
- top_used_items: Array<{
207
- item_id: string;
208
- title: string | null;
209
- source_type: string | null;
210
- used_count: number;
211
- rejected_count: number;
212
- }>;
213
- recent_feedback: Array<{
214
- item_id: string;
215
- outcome: string;
216
- context: string | null;
217
- session_task: string | null;
218
- recorded_at: string;
219
- }>;
220
- };
215
+ private ensureFeedbackTable;
221
216
  /**
222
217
  * Compute relevance score for an item based on user context
223
218
  */