@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.
- package/CHANGELOG.md +698 -0
- package/README.md +70 -39
- package/dist/build-staleness.d.ts +35 -0
- package/dist/build-staleness.js +95 -0
- package/dist/db.d.ts +53 -58
- package/dist/db.js +226 -299
- package/dist/doctor.js +27 -7
- package/dist/embeddings.js +7 -1
- package/dist/freshness-markers.d.ts +34 -0
- package/dist/freshness-markers.js +74 -0
- package/dist/gitignore.d.ts +37 -0
- package/dist/gitignore.js +130 -0
- package/dist/index.js +127 -44
- package/dist/live/bun-lockfile.d.ts +26 -0
- package/dist/live/bun-lockfile.js +123 -0
- package/dist/live/cargo-platform.d.ts +38 -0
- package/dist/live/cargo-platform.js +178 -0
- package/dist/live/changelog-classify.d.ts +41 -0
- package/dist/live/changelog-classify.js +207 -0
- package/dist/live/changelog.d.ts +64 -0
- package/dist/live/changelog.js +318 -0
- package/dist/live/crates-registry.js +3 -1
- package/dist/live/crates-versions.d.ts +47 -0
- package/dist/live/crates-versions.js +201 -0
- package/dist/live/dependency-set.d.ts +13 -0
- package/dist/live/dependency-set.js +95 -0
- package/dist/live/dev-scope.d.ts +32 -0
- package/dist/live/dev-scope.js +102 -0
- package/dist/live/file-signature.d.ts +18 -0
- package/dist/live/file-signature.js +43 -0
- package/dist/live/go-registry.js +3 -1
- package/dist/live/hn-fetcher.js +9 -3
- package/dist/live/index.d.ts +105 -2
- package/dist/live/index.js +266 -139
- package/dist/live/install-state.d.ts +43 -0
- package/dist/live/install-state.js +150 -0
- package/dist/live/js-lockfile-readers.d.ts +46 -0
- package/dist/live/js-lockfile-readers.js +181 -0
- package/dist/live/lockfile-parsers-pygo.d.ts +60 -0
- package/dist/live/lockfile-parsers-pygo.js +274 -0
- package/dist/live/lockfile-parsers.d.ts +69 -0
- package/dist/live/lockfile-parsers.js +311 -0
- package/dist/live/lockfile-types.d.ts +34 -0
- package/dist/live/lockfile-types.js +42 -0
- package/dist/live/maintenance.d.ts +33 -0
- package/dist/live/maintenance.js +37 -0
- package/dist/live/npm-packument.d.ts +47 -0
- package/dist/live/npm-packument.js +150 -0
- package/dist/live/npm-registry.js +67 -53
- package/dist/live/osv-scanner.d.ts +12 -1
- package/dist/live/osv-scanner.js +50 -15
- package/dist/live/package-archive.d.ts +49 -0
- package/dist/live/package-archive.js +186 -0
- package/dist/live/pypi-registry.js +3 -1
- package/dist/live/registry-health.d.ts +16 -0
- package/dist/live/registry-health.js +74 -0
- package/dist/live/release-metadata.d.ts +67 -0
- package/dist/live/release-metadata.js +16 -0
- package/dist/live/resolution.d.ts +43 -0
- package/dist/live/resolution.js +62 -0
- package/dist/live/semver-precedence.d.ts +30 -0
- package/dist/live/semver-precedence.js +83 -0
- package/dist/live/severity-scope.d.ts +57 -0
- package/dist/live/severity-scope.js +89 -0
- package/dist/live/types.d.ts +83 -0
- package/dist/live/upgrade-sources.d.ts +71 -0
- package/dist/live/upgrade-sources.js +198 -0
- package/dist/live/version-compare.d.ts +39 -0
- package/dist/live/version-compare.js +130 -0
- package/dist/live/version-resolver.d.ts +14 -1
- package/dist/live/version-resolver.js +93 -303
- package/dist/native-bindings.d.ts +19 -0
- package/dist/native-bindings.js +39 -0
- package/dist/project-manifests.d.ts +40 -0
- package/dist/project-manifests.js +193 -0
- package/dist/project-scanner.js +38 -48
- package/dist/project-tree.d.ts +41 -0
- package/dist/project-tree.js +110 -0
- package/dist/prompts.d.ts +28 -0
- package/dist/prompts.js +54 -0
- package/dist/schema-registry.d.ts +41 -29
- package/dist/schema-registry.js +98 -105
- package/dist/server-instructions.d.ts +11 -0
- package/dist/server-instructions.js +36 -0
- package/dist/tool-args.d.ts +28 -0
- package/dist/tool-args.js +93 -0
- package/dist/tool-dispatch.d.ts +1 -1
- package/dist/tool-dispatch.js +42 -8
- package/dist/tools/app-plan.d.ts +92 -0
- package/dist/tools/app-plan.js +155 -0
- package/dist/tools/briefing-stored-advisories.d.ts +20 -0
- package/dist/tools/briefing-stored-advisories.js +58 -0
- package/dist/tools/briefing-task-scope.d.ts +73 -0
- package/dist/tools/briefing-task-scope.js +197 -0
- package/dist/tools/briefing-wisdom.d.ts +31 -0
- package/dist/tools/briefing-wisdom.js +148 -0
- package/dist/tools/decision-enforcement.d.ts +0 -5
- package/dist/tools/decision-enforcement.js +0 -4
- package/dist/tools/decision-memory.js +18 -66
- package/dist/tools/dependency-check-signals.d.ts +74 -0
- package/dist/tools/dependency-check-signals.js +264 -0
- package/dist/tools/dependency-check.d.ts +98 -0
- package/dist/tools/dependency-check.js +295 -0
- package/dist/tools/dependency-health.js +74 -21
- package/dist/tools/developer-dna.d.ts +3 -0
- package/dist/tools/developer-dna.js +16 -15
- package/dist/tools/ecosystem-pulse.d.ts +6 -2
- package/dist/tools/ecosystem-pulse.js +53 -14
- package/dist/tools/get-actionable-signals.d.ts +26 -4
- package/dist/tools/get-actionable-signals.js +214 -251
- package/dist/tools/get-context.d.ts +2 -0
- package/dist/tools/get-context.js +9 -1
- package/dist/tools/get-relevant-content.d.ts +3 -1
- package/dist/tools/get-relevant-content.js +34 -3
- package/dist/tools/index.d.ts +4 -2
- package/dist/tools/index.js +4 -2
- package/dist/tools/install-drift-notes.d.ts +51 -0
- package/dist/tools/install-drift-notes.js +172 -0
- package/dist/tools/knowledge-gap-citations.d.ts +55 -0
- package/dist/tools/knowledge-gap-citations.js +132 -0
- package/dist/tools/knowledge-gap-exposure.d.ts +72 -0
- package/dist/tools/knowledge-gap-exposure.js +160 -0
- package/dist/tools/knowledge-gap-grading.d.ts +91 -0
- package/dist/tools/knowledge-gap-grading.js +157 -0
- package/dist/tools/knowledge-gap-installs.d.ts +65 -0
- package/dist/tools/knowledge-gap-installs.js +134 -0
- package/dist/tools/knowledge-gap-ranges.d.ts +51 -0
- package/dist/tools/knowledge-gap-ranges.js +146 -0
- package/dist/tools/knowledge-gap-scope.d.ts +63 -0
- package/dist/tools/knowledge-gap-scope.js +118 -0
- package/dist/tools/knowledge-gaps.d.ts +12 -3
- package/dist/tools/knowledge-gaps.js +172 -121
- package/dist/tools/package-presence.d.ts +24 -0
- package/dist/tools/package-presence.js +35 -0
- package/dist/tools/record-feedback.d.ts +0 -1
- package/dist/tools/record-feedback.js +7 -8
- package/dist/tools/signal-classifier.d.ts +25 -0
- package/dist/tools/signal-classifier.js +40 -0
- package/dist/tools/upgrade-impact-callsites.d.ts +52 -0
- package/dist/tools/upgrade-impact-callsites.js +226 -0
- package/dist/tools/upgrade-impact-literals.d.ts +45 -0
- package/dist/tools/upgrade-impact-literals.js +118 -0
- package/dist/tools/upgrade-impact-report.d.ts +80 -0
- package/dist/tools/upgrade-impact-report.js +178 -0
- package/dist/tools/upgrade-impact.d.ts +84 -0
- package/dist/tools/upgrade-impact.js +305 -0
- package/dist/tools/upgrade-planner.d.ts +41 -8
- package/dist/tools/upgrade-planner.js +201 -53
- package/dist/tools/vulnerability-scan-concise.d.ts +72 -0
- package/dist/tools/vulnerability-scan-concise.js +97 -0
- package/dist/tools/vulnerability-scan-format.d.ts +137 -0
- package/dist/tools/vulnerability-scan-format.js +276 -0
- package/dist/tools/vulnerability-scan.d.ts +338 -18
- package/dist/tools/vulnerability-scan.js +84 -125
- package/dist/tools/what-should-i-know.d.ts +86 -19
- package/dist/tools/what-should-i-know.js +277 -283
- package/dist/types.d.ts +4 -136
- package/package.json +35 -35
- package/dist/schemas/agent-memory.json +0 -49
- package/dist/schemas/decision-enforcement.json +0 -22
- package/dist/schemas/decision-memory.json +0 -54
- package/dist/schemas/dependency-health.json +0 -27
- package/dist/schemas/developer-dna.json +0 -219
- package/dist/schemas/ecosystem-pulse.json +0 -17
- package/dist/schemas/get-actionable-signals.json +0 -29
- package/dist/schemas/get-context.json +0 -17
- package/dist/schemas/get-relevant-content.json +0 -26
- package/dist/schemas/knowledge-gaps.json +0 -14
- package/dist/schemas/record-feedback.json +0 -24
- package/dist/schemas/upgrade-planner.json +0 -22
- package/dist/schemas/vulnerability-scan.json +0 -26
- package/dist/schemas/what-should-i-know.json +0 -19
package/README.md
CHANGED
|
@@ -2,30 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@4da/mcp-server)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](https://nodejs.org/)
|
|
6
|
+
[](https://github.com/4DA-Systems/4da-mcp-server/actions/workflows/ci.yml)
|
|
7
7
|
|
|
8
|
-
**
|
|
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: "
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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: **"
|
|
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
|
-
|
|
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
|
|
93
|
-
- **npm registry** for
|
|
94
|
-
- **
|
|
95
|
-
- **
|
|
96
|
-
- **
|
|
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
|
|
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
|
-
"
|
|
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
|
|
144
|
+
## All 16 Tools
|
|
122
145
|
|
|
123
146
|
### Dependency Security
|
|
124
147
|
|
|
125
148
|
| Tool | What it does |
|
|
126
149
|
|------|-------------|
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
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
|
|
136
|
-
| `ecosystem_pulse` |
|
|
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
|
|
139
|
-
| `get_actionable_signals`* |
|
|
140
|
-
| `knowledge_gaps`* | Dependencies
|
|
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.
|
|
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
|
-
|
|
|
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
|
|
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.
|
|
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/
|
|
241
|
-
cd
|
|
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 #
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
206
|
+
* Record user feedback on an item
|
|
167
207
|
*/
|
|
168
|
-
|
|
208
|
+
recordFeedback(itemId: number, sourceType: string | undefined, action: FeedbackAction): FeedbackResult;
|
|
169
209
|
/**
|
|
170
|
-
*
|
|
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
|
-
|
|
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
|
*/
|