@4da/mcp-server 4.6.1 → 4.6.3
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/README.md +3 -1
- package/dist/db.d.ts +21 -2
- package/dist/db.js +66 -16
- package/dist/doctor.js +5 -3
- package/dist/embeddings.d.ts +88 -0
- package/dist/embeddings.js +292 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.js +21 -15
- package/dist/live/cvss.d.ts +16 -0
- package/dist/live/cvss.js +62 -0
- package/dist/live/index.d.ts +9 -1
- package/dist/live/index.js +40 -3
- package/dist/live/npm-registry.js +29 -6
- package/dist/live/osv-scanner.d.ts +8 -0
- package/dist/live/osv-scanner.js +115 -30
- package/dist/live/platform.d.ts +32 -0
- package/dist/live/platform.js +156 -0
- package/dist/live/semver-utils.d.ts +8 -0
- package/dist/live/semver-utils.js +24 -0
- package/dist/live/types.d.ts +31 -9
- package/dist/live/version-resolver.d.ts +12 -2
- package/dist/live/version-resolver.js +63 -20
- package/dist/llm.js +1 -1
- package/dist/project-scanner.d.ts +2 -0
- package/dist/project-scanner.js +34 -0
- package/dist/scheduler/core.d.ts +25 -0
- package/dist/scheduler/core.js +61 -0
- package/dist/scheduler/freshness.d.ts +17 -0
- package/dist/scheduler/freshness.js +41 -0
- package/dist/scheduler/index.d.ts +21 -0
- package/dist/scheduler/index.js +32 -0
- package/dist/scheduler/refreshers.d.ts +21 -0
- package/dist/scheduler/refreshers.js +21 -0
- package/dist/schemas/get-relevant-content.json +1 -1
- package/dist/schemas/upgrade-planner.json +1 -1
- package/dist/schemas/what-should-i-know.json +1 -1
- package/dist/tools/agent-memory.d.ts +11 -1
- package/dist/tools/agent-memory.js +120 -14
- package/dist/tools/decision-enforcement.d.ts +17 -1
- package/dist/tools/decision-enforcement.js +109 -81
- package/dist/tools/decision-memory.d.ts +1 -1
- package/dist/tools/decision-memory.js +58 -28
- package/dist/tools/decision-recall.d.ts +59 -0
- package/dist/tools/decision-recall.js +87 -0
- package/dist/tools/dependency-health.js +6 -4
- package/dist/tools/developer-dna.js +4 -2
- package/dist/tools/knowledge-gaps.js +16 -4
- package/dist/tools/recall.d.ts +33 -0
- package/dist/tools/recall.js +134 -0
- package/dist/tools/upgrade-planner.d.ts +25 -1
- package/dist/tools/upgrade-planner.js +111 -30
- package/dist/tools/vulnerability-scan.d.ts +46 -0
- package/dist/tools/vulnerability-scan.js +64 -24
- package/dist/tools/what-should-i-know.d.ts +3 -1
- package/dist/tools/what-should-i-know.js +210 -106
- package/dist/types.d.ts +9 -0
- package/package.json +8 -3
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/db.d.ts.map +0 -1
- package/dist/db.js.map +0 -1
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/http-transport.d.ts.map +0 -1
- package/dist/http-transport.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/live/cache.d.ts.map +0 -1
- package/dist/live/cache.js.map +0 -1
- package/dist/live/crates-registry.d.ts.map +0 -1
- package/dist/live/crates-registry.js.map +0 -1
- package/dist/live/go-registry.d.ts.map +0 -1
- package/dist/live/go-registry.js.map +0 -1
- package/dist/live/hn-fetcher.d.ts.map +0 -1
- package/dist/live/hn-fetcher.js.map +0 -1
- package/dist/live/http-utils.d.ts.map +0 -1
- package/dist/live/http-utils.js.map +0 -1
- package/dist/live/index.d.ts.map +0 -1
- package/dist/live/index.js.map +0 -1
- package/dist/live/npm-registry.d.ts.map +0 -1
- package/dist/live/npm-registry.js.map +0 -1
- package/dist/live/osv-scanner.d.ts.map +0 -1
- package/dist/live/osv-scanner.js.map +0 -1
- package/dist/live/pypi-registry.d.ts.map +0 -1
- package/dist/live/pypi-registry.js.map +0 -1
- package/dist/live/rate-limiter.d.ts.map +0 -1
- package/dist/live/rate-limiter.js.map +0 -1
- package/dist/live/semver-utils.d.ts.map +0 -1
- package/dist/live/semver-utils.js.map +0 -1
- package/dist/live/types.d.ts.map +0 -1
- package/dist/live/types.js.map +0 -1
- package/dist/live/version-resolver.d.ts.map +0 -1
- package/dist/live/version-resolver.js.map +0 -1
- package/dist/live-singleton.d.ts.map +0 -1
- package/dist/live-singleton.js.map +0 -1
- package/dist/llm.d.ts.map +0 -1
- package/dist/llm.js.map +0 -1
- package/dist/output-manager.d.ts.map +0 -1
- package/dist/output-manager.js.map +0 -1
- package/dist/project-scanner.d.ts.map +0 -1
- package/dist/project-scanner.js.map +0 -1
- package/dist/schema-registry.d.ts.map +0 -1
- package/dist/schema-registry.js.map +0 -1
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js.map +0 -1
- package/dist/tool-dispatch.d.ts.map +0 -1
- package/dist/tool-dispatch.js.map +0 -1
- package/dist/tools/agent-memory.d.ts.map +0 -1
- package/dist/tools/agent-memory.js.map +0 -1
- package/dist/tools/decision-enforcement.d.ts.map +0 -1
- package/dist/tools/decision-enforcement.js.map +0 -1
- package/dist/tools/decision-memory.d.ts.map +0 -1
- package/dist/tools/decision-memory.js.map +0 -1
- package/dist/tools/dependency-health.d.ts.map +0 -1
- package/dist/tools/dependency-health.js.map +0 -1
- package/dist/tools/developer-dna.d.ts.map +0 -1
- package/dist/tools/developer-dna.js.map +0 -1
- package/dist/tools/ecosystem-pulse.d.ts.map +0 -1
- package/dist/tools/ecosystem-pulse.js.map +0 -1
- package/dist/tools/get-actionable-signals.d.ts.map +0 -1
- package/dist/tools/get-actionable-signals.js.map +0 -1
- package/dist/tools/get-context.d.ts.map +0 -1
- package/dist/tools/get-context.js.map +0 -1
- package/dist/tools/get-relevant-content.d.ts.map +0 -1
- package/dist/tools/get-relevant-content.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/knowledge-gaps.d.ts.map +0 -1
- package/dist/tools/knowledge-gaps.js.map +0 -1
- package/dist/tools/record-feedback.d.ts.map +0 -1
- package/dist/tools/record-feedback.js.map +0 -1
- package/dist/tools/upgrade-planner.d.ts.map +0 -1
- package/dist/tools/upgrade-planner.js.map +0 -1
- package/dist/tools/vulnerability-scan.d.ts.map +0 -1
- package/dist/tools/vulnerability-scan.js.map +0 -1
- package/dist/tools/what-should-i-know.d.ts.map +0 -1
- package/dist/tools/what-should-i-know.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -100,6 +100,8 @@ Results are cached (24h for registry data, 1h for vulnerabilities, 30min for new
|
|
|
100
100
|
|
|
101
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.
|
|
102
102
|
|
|
103
|
+
> 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
|
+
|
|
103
105
|
**Ecosystems supported:** npm, crates.io (Rust), PyPI (Python), Go.
|
|
104
106
|
|
|
105
107
|
## What You Can Ask
|
|
@@ -206,7 +208,7 @@ npx @4da/mcp-server --version # Print version
|
|
|
206
208
|
## FAQ
|
|
207
209
|
|
|
208
210
|
**Does this send my code anywhere?**
|
|
209
|
-
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.
|
|
211
|
+
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.)
|
|
210
212
|
|
|
211
213
|
**Do I need the 4DA desktop app?**
|
|
212
214
|
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 scored content from 20+ sources that compounds over time.
|
package/dist/db.d.ts
CHANGED
|
@@ -53,14 +53,26 @@ export declare class FourDADatabase {
|
|
|
53
53
|
*/
|
|
54
54
|
get isStandalone(): boolean;
|
|
55
55
|
/**
|
|
56
|
-
* Validate that a database file exists
|
|
56
|
+
* Validate that a database file exists and is a readable SQLite database.
|
|
57
57
|
*
|
|
58
58
|
* Use this before accepting tool calls to ensure the database is in a good state.
|
|
59
59
|
*
|
|
60
|
+
* By default this runs a CHEAP probe (open + list tables, ~0.1s) which surfaces
|
|
61
|
+
* a missing, locked, or corrupt-header file. It deliberately does NOT run a full
|
|
62
|
+
* `PRAGMA integrity_check`: that is an O(database-size) scan (~11s on a 1.7 GB DB,
|
|
63
|
+
* and growing) and, because better-sqlite3 is synchronous, it blocks the event
|
|
64
|
+
* loop — running it on the startup path stalls the MCP stdio handshake until it
|
|
65
|
+
* finishes, so the host's connect timeout can fire and the server looks like it
|
|
66
|
+
* "failed to start". Pass `{ deep: true }` for the full integrity_check on the
|
|
67
|
+
* explicit `--doctor` diagnostic path, where a slow, thorough scan is expected.
|
|
68
|
+
*
|
|
60
69
|
* @param dbPath - Path to the database file. If omitted, uses the default resolution.
|
|
70
|
+
* @param opts.deep - Run the full `PRAGMA integrity_check` (slow, blocking). Default false.
|
|
61
71
|
* @returns Validation result with table list on success, or error details on failure.
|
|
62
72
|
*/
|
|
63
|
-
static validateDatabase(dbPath?: string
|
|
73
|
+
static validateDatabase(dbPath?: string, opts?: {
|
|
74
|
+
deep?: boolean;
|
|
75
|
+
}): DatabaseValidationResult;
|
|
64
76
|
/**
|
|
65
77
|
* Get the raw better-sqlite3 database instance for custom queries
|
|
66
78
|
*/
|
|
@@ -105,6 +117,13 @@ export declare class FourDADatabase {
|
|
|
105
117
|
/** `MAX(sources.last_fetch)`, tolerant of a missing `sources` table (returns null). */
|
|
106
118
|
private safeMaxLastFetch;
|
|
107
119
|
private hasColumn;
|
|
120
|
+
/**
|
|
121
|
+
* Idempotently add a column to a table if it does not already exist.
|
|
122
|
+
* Used to add the optional `embedding` / `embedding_model` columns to
|
|
123
|
+
* pre-existing databases created before semantic recall shipped. Column/type
|
|
124
|
+
* are server-controlled constants (never user input), so the inline SQL is safe.
|
|
125
|
+
*/
|
|
126
|
+
ensureColumn(table: string, column: string, type: string): void;
|
|
108
127
|
/**
|
|
109
128
|
* Use Rust-computed relevance_score from the desktop app's scoring pipeline.
|
|
110
129
|
* These scores are from the full 5-axis PASIFA engine — much more accurate.
|
package/dist/db.js
CHANGED
|
@@ -121,14 +121,24 @@ export class FourDADatabase {
|
|
|
121
121
|
return this._isStandalone;
|
|
122
122
|
}
|
|
123
123
|
/**
|
|
124
|
-
* Validate that a database file exists
|
|
124
|
+
* Validate that a database file exists and is a readable SQLite database.
|
|
125
125
|
*
|
|
126
126
|
* Use this before accepting tool calls to ensure the database is in a good state.
|
|
127
127
|
*
|
|
128
|
+
* By default this runs a CHEAP probe (open + list tables, ~0.1s) which surfaces
|
|
129
|
+
* a missing, locked, or corrupt-header file. It deliberately does NOT run a full
|
|
130
|
+
* `PRAGMA integrity_check`: that is an O(database-size) scan (~11s on a 1.7 GB DB,
|
|
131
|
+
* and growing) and, because better-sqlite3 is synchronous, it blocks the event
|
|
132
|
+
* loop — running it on the startup path stalls the MCP stdio handshake until it
|
|
133
|
+
* finishes, so the host's connect timeout can fire and the server looks like it
|
|
134
|
+
* "failed to start". Pass `{ deep: true }` for the full integrity_check on the
|
|
135
|
+
* explicit `--doctor` diagnostic path, where a slow, thorough scan is expected.
|
|
136
|
+
*
|
|
128
137
|
* @param dbPath - Path to the database file. If omitted, uses the default resolution.
|
|
138
|
+
* @param opts.deep - Run the full `PRAGMA integrity_check` (slow, blocking). Default false.
|
|
129
139
|
* @returns Validation result with table list on success, or error details on failure.
|
|
130
140
|
*/
|
|
131
|
-
static validateDatabase(dbPath) {
|
|
141
|
+
static validateDatabase(dbPath, opts) {
|
|
132
142
|
const resolvedPath = dbPath || getDefaultDbPath();
|
|
133
143
|
const absolutePath = path.isAbsolute(resolvedPath)
|
|
134
144
|
? resolvedPath
|
|
@@ -141,21 +151,24 @@ export class FourDADatabase {
|
|
|
141
151
|
error: `No existing database at ${absolutePath}. Standalone mode will create one on startup.`,
|
|
142
152
|
};
|
|
143
153
|
}
|
|
144
|
-
// Try to open and run integrity check
|
|
145
154
|
let testDb = null;
|
|
146
155
|
try {
|
|
147
156
|
testDb = new Database(absolutePath, { readonly: true });
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
// Deep, opt-in integrity check (slow: full-database scan). Only on --doctor.
|
|
158
|
+
if (opts?.deep) {
|
|
159
|
+
const integrityResult = testDb.pragma("integrity_check");
|
|
160
|
+
const integrityStatus = integrityResult[0]?.integrity_check;
|
|
161
|
+
if (integrityStatus !== "ok") {
|
|
162
|
+
return {
|
|
163
|
+
valid: false,
|
|
164
|
+
error: `Database integrity check failed: ${integrityStatus}. `
|
|
165
|
+
+ "The database file may be corrupt. Try deleting data/4da.db and restarting 4DA.",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
157
168
|
}
|
|
158
|
-
// List tables
|
|
169
|
+
// List tables. This reads the schema, so a truncated/corrupt-header file
|
|
170
|
+
// (not a valid SQLite DB) throws here and is reported below — giving the
|
|
171
|
+
// cheap path meaningful corruption detection without a full scan.
|
|
159
172
|
const tables = testDb
|
|
160
173
|
.prepare("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
|
161
174
|
.all();
|
|
@@ -356,6 +369,8 @@ export class FourDADatabase {
|
|
|
356
369
|
CREATE INDEX IF NOT EXISTS idx_deps_project ON project_dependencies(project_path);
|
|
357
370
|
|
|
358
371
|
-- Developer decisions — queried by tech_radar, decision_memory
|
|
372
|
+
-- embedding/embedding_model are populated lazily ONLY when an embedding
|
|
373
|
+
-- provider is configured (optional semantic recall); null otherwise.
|
|
359
374
|
CREATE TABLE IF NOT EXISTS developer_decisions (
|
|
360
375
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
361
376
|
decision_type TEXT NOT NULL,
|
|
@@ -368,7 +383,9 @@ export class FourDADatabase {
|
|
|
368
383
|
status TEXT NOT NULL DEFAULT 'active',
|
|
369
384
|
superseded_by INTEGER,
|
|
370
385
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
371
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
386
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
387
|
+
embedding BLOB,
|
|
388
|
+
embedding_model TEXT
|
|
372
389
|
);
|
|
373
390
|
CREATE INDEX IF NOT EXISTS idx_decisions_type ON developer_decisions(decision_type);
|
|
374
391
|
CREATE INDEX IF NOT EXISTS idx_decisions_subject ON developer_decisions(subject);
|
|
@@ -399,6 +416,8 @@ export class FourDADatabase {
|
|
|
399
416
|
);
|
|
400
417
|
|
|
401
418
|
-- Agent memory — queried by agent_memory, agent_session_brief
|
|
419
|
+
-- embedding/embedding_model are populated lazily ONLY when an embedding
|
|
420
|
+
-- provider is configured (optional semantic recall); null otherwise.
|
|
402
421
|
CREATE TABLE IF NOT EXISTS agent_memory (
|
|
403
422
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
404
423
|
session_id TEXT NOT NULL,
|
|
@@ -409,7 +428,9 @@ export class FourDADatabase {
|
|
|
409
428
|
context_tags TEXT DEFAULT '[]',
|
|
410
429
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
411
430
|
expires_at TEXT,
|
|
412
|
-
promoted_to_decision_id INTEGER
|
|
431
|
+
promoted_to_decision_id INTEGER,
|
|
432
|
+
embedding BLOB,
|
|
433
|
+
embedding_model TEXT
|
|
413
434
|
);
|
|
414
435
|
CREATE INDEX IF NOT EXISTS idx_agent_memory_type ON agent_memory(memory_type);
|
|
415
436
|
CREATE INDEX IF NOT EXISTS idx_agent_memory_session ON agent_memory(session_id);
|
|
@@ -686,15 +707,33 @@ export class FourDADatabase {
|
|
|
686
707
|
return false;
|
|
687
708
|
}
|
|
688
709
|
}
|
|
710
|
+
/**
|
|
711
|
+
* Idempotently add a column to a table if it does not already exist.
|
|
712
|
+
* Used to add the optional `embedding` / `embedding_model` columns to
|
|
713
|
+
* pre-existing databases created before semantic recall shipped. Column/type
|
|
714
|
+
* are server-controlled constants (never user input), so the inline SQL is safe.
|
|
715
|
+
*/
|
|
716
|
+
ensureColumn(table, column, type) {
|
|
717
|
+
if (this.hasColumn(table, column))
|
|
718
|
+
return;
|
|
719
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
|
|
720
|
+
}
|
|
689
721
|
/**
|
|
690
722
|
* Use Rust-computed relevance_score from the desktop app's scoring pipeline.
|
|
691
723
|
* These scores are from the full 5-axis PASIFA engine — much more accurate.
|
|
692
724
|
*/
|
|
693
725
|
getRelevantContentFromRust(minScore, sourceType, limit, sinceDate) {
|
|
726
|
+
// Grounding subquery — guarded: older/partial 4DA DBs may have relevance_score
|
|
727
|
+
// (so the Rust path runs) but lack source_item_dependencies. Fall back to 0
|
|
728
|
+
// (→ evidence_class "semantic_only") rather than throwing.
|
|
729
|
+
const depCount = this.hasColumn("source_item_dependencies", "source_item_id")
|
|
730
|
+
? `(SELECT COUNT(*) FROM source_item_dependencies d WHERE d.source_item_id = source_items.id)`
|
|
731
|
+
: `0`;
|
|
694
732
|
let query = `
|
|
695
733
|
SELECT id, source_type, source_id, url, title, content, content_hash,
|
|
696
734
|
created_at, last_seen, relevance_score, content_type,
|
|
697
|
-
signal_type, signal_priority
|
|
735
|
+
signal_type, signal_priority,
|
|
736
|
+
${depCount} AS dep_match_count
|
|
698
737
|
FROM source_items
|
|
699
738
|
WHERE relevance_score >= ?
|
|
700
739
|
AND datetime(created_at) >= datetime(?)
|
|
@@ -718,6 +757,14 @@ export class FourDADatabase {
|
|
|
718
757
|
? `${hoursAgo} hours ago`
|
|
719
758
|
: `${Math.round(hoursAgo / 24)} days ago`;
|
|
720
759
|
const necessity = this.getNecessityForItem(item.id);
|
|
760
|
+
// Provenance: grounded (matched one of the user's installed deps) vs
|
|
761
|
+
// semantic-only. A grounded security advisory is the OSV-verified gold tier.
|
|
762
|
+
const grounded = (item.dep_match_count ?? 0) > 0;
|
|
763
|
+
const evidence_class = grounded
|
|
764
|
+
? item.content_type === "security_advisory"
|
|
765
|
+
? "osv_verified"
|
|
766
|
+
: "dependency_grounded"
|
|
767
|
+
: "semantic_only";
|
|
721
768
|
return {
|
|
722
769
|
id: item.id,
|
|
723
770
|
source_type: item.source_type,
|
|
@@ -734,6 +781,7 @@ export class FourDADatabase {
|
|
|
734
781
|
necessity_urgency: necessity.urgency,
|
|
735
782
|
signal_type: item.signal_type ?? null,
|
|
736
783
|
signal_priority: item.signal_priority ?? null,
|
|
784
|
+
evidence_class,
|
|
737
785
|
};
|
|
738
786
|
});
|
|
739
787
|
}
|
|
@@ -785,6 +833,8 @@ export class FourDADatabase {
|
|
|
785
833
|
necessity_urgency: necessity.urgency,
|
|
786
834
|
signal_type: null,
|
|
787
835
|
signal_priority: null,
|
|
836
|
+
// Standalone keyword scorer — no Rust pipeline, no dependency grounding.
|
|
837
|
+
evidence_class: "keyword_heuristic",
|
|
788
838
|
});
|
|
789
839
|
}
|
|
790
840
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -34,8 +34,10 @@ export function runDoctor() {
|
|
|
34
34
|
status: "pass",
|
|
35
35
|
detail: "better-sqlite3 loaded successfully",
|
|
36
36
|
});
|
|
37
|
-
// 3. Database discovery
|
|
38
|
-
|
|
37
|
+
// 3. Database discovery — deep integrity_check is appropriate here: --doctor is
|
|
38
|
+
// an explicit, user-invoked diagnostic where a slow full scan is expected. The
|
|
39
|
+
// startup path uses the cheap default to keep the MCP handshake responsive.
|
|
40
|
+
const validation = FourDADatabase.validateDatabase(undefined, { deep: true });
|
|
39
41
|
if (validation.valid) {
|
|
40
42
|
checks.push({
|
|
41
43
|
name: "4DA database",
|
|
@@ -103,7 +105,7 @@ export function runDoctor() {
|
|
|
103
105
|
const dbCheck = checks.find((c) => c.name === "4DA database");
|
|
104
106
|
if (dbCheck && dbCheck.status === "warn") {
|
|
105
107
|
console.log(" The MCP server can work standalone or with the 4DA desktop app.");
|
|
106
|
-
console.log(" Standalone: scans your
|
|
108
|
+
console.log(" Standalone: scans your dependencies for known CVEs and provides project context.");
|
|
107
109
|
console.log(" Full app: adds content scoring, source monitoring, and more.");
|
|
108
110
|
console.log("");
|
|
109
111
|
console.log(" Desktop app: https://github.com/runyourempire/4DA/releases/latest");
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional, provider-backed semantic embeddings for local knowledge recall.
|
|
3
|
+
*
|
|
4
|
+
* The MCP server ships ZERO embedding dependencies — that is the whole point of
|
|
5
|
+
* the `npx` zero-install promise. Semantic recall therefore lights up ONLY when
|
|
6
|
+
* the user already has an embedding provider reachable:
|
|
7
|
+
* - Ollama (local, private, free) — set settings.embedding.provider = "ollama"
|
|
8
|
+
* - OpenAI (cloud, explicit opt-in) — set settings.embedding.provider = "openai"
|
|
9
|
+
*
|
|
10
|
+
* When no provider is configured, the provider is unreachable, the embed model is
|
|
11
|
+
* missing, or FOURDA_OFFLINE is set, every caller falls back to ranked lexical
|
|
12
|
+
* recall and the response says so via `recall_mode`. Critically, the server embeds
|
|
13
|
+
* BOTH the query and the stored rows with the SAME provider/model, so the vector
|
|
14
|
+
* space is always self-consistent — we never mix our vectors with the desktop
|
|
15
|
+
* app's fastembed vectors.
|
|
16
|
+
*
|
|
17
|
+
* No sqlite-vec: similarity is a plain JS cosine over Float32 BLOBs, which is
|
|
18
|
+
* trivially fast for the few-hundred-row local knowledge tables this serves.
|
|
19
|
+
*/
|
|
20
|
+
import type { FourDADatabase } from "./db.js";
|
|
21
|
+
export interface EmbeddingConfig {
|
|
22
|
+
provider: "ollama" | "openai";
|
|
23
|
+
model: string;
|
|
24
|
+
/** Ollama base URL (ignored for openai). */
|
|
25
|
+
url?: string;
|
|
26
|
+
/** OpenAI API key (ignored for ollama). */
|
|
27
|
+
apiKey?: string;
|
|
28
|
+
}
|
|
29
|
+
/** True when network calls are disabled by the FOURDA_OFFLINE escape hatch. */
|
|
30
|
+
export declare function isOfflineMode(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the embedding provider. Order of precedence:
|
|
33
|
+
* 1. FOURDA_OFFLINE -> always null (no network)
|
|
34
|
+
* 2. environment variables -> FOURDA_EMBED_PROVIDER / FOURDA_EMBED_MODEL / ...
|
|
35
|
+
* 3. settings.json -> the `embedding` block
|
|
36
|
+
* 4. (nothing) -> null, callers stay lexical
|
|
37
|
+
*
|
|
38
|
+
* Deliberately does NOT auto-derive from the chat `llm` block: enabling semantic
|
|
39
|
+
* recall is an explicit choice so a recall never makes a surprise network call.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getEmbeddingConfig(): EmbeddingConfig | null;
|
|
42
|
+
/** A short, stable identity for the active model, stored alongside each vector. */
|
|
43
|
+
export declare function modelTag(config: EmbeddingConfig): string;
|
|
44
|
+
/**
|
|
45
|
+
* Embed a single text. Returns null on ANY failure (provider down, model
|
|
46
|
+
* missing, bad response, empty input) so callers degrade to lexical silently.
|
|
47
|
+
*/
|
|
48
|
+
export declare function embedText(text: string, config: EmbeddingConfig, signal?: AbortSignal): Promise<Float32Array | null>;
|
|
49
|
+
/** Pack a vector into a little-endian Float32 BLOB for SQLite storage. */
|
|
50
|
+
export declare function vectorToBlob(vec: Float32Array): Buffer;
|
|
51
|
+
/**
|
|
52
|
+
* Unpack a stored BLOB back into a Float32Array. Returns null for empty/
|
|
53
|
+
* misaligned blobs so a corrupt cell never throws mid-recall. Copies the bytes
|
|
54
|
+
* to guarantee 4-byte alignment regardless of the source buffer's offset.
|
|
55
|
+
*/
|
|
56
|
+
export declare function blobToVector(blob: Buffer | Uint8Array | null | undefined): Float32Array | null;
|
|
57
|
+
/** Cosine similarity in [-1, 1]; 0 for length mismatch or a zero vector. */
|
|
58
|
+
export declare function cosineSimilarity(a: Float32Array, b: Float32Array): number;
|
|
59
|
+
export interface SemanticScoreResult {
|
|
60
|
+
/** id -> cosine similarity to the query (only for rows that have a vector). */
|
|
61
|
+
semanticById: Map<number, number>;
|
|
62
|
+
/** False when the query itself could not be embedded — caller stays lexical. */
|
|
63
|
+
queryEmbedded: boolean;
|
|
64
|
+
/** How many candidate rows contributed a semantic score this call. */
|
|
65
|
+
embeddedCount: number;
|
|
66
|
+
/** How many rows were embedded fresh this call (lazy backfill). */
|
|
67
|
+
newlyEmbedded: number;
|
|
68
|
+
/** The provider:model tag the scores were produced with. */
|
|
69
|
+
model: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Score `items` (id + the text to embed) against a query using cached, lazily
|
|
73
|
+
* backfilled embeddings stored in `<table>.embedding` / `<table>.embedding_model`.
|
|
74
|
+
*
|
|
75
|
+
* Shared by every semantic-recall surface (agent_memory, developer_decisions) so
|
|
76
|
+
* the embed/cache/cosine logic lives in exactly one place. `table` is always a
|
|
77
|
+
* server-controlled constant (never user input), so the interpolated SQL is safe.
|
|
78
|
+
*
|
|
79
|
+
* Returns `queryEmbedded: false` (and an empty map) when the provider is
|
|
80
|
+
* unreachable, so callers fall back to pure lexical recall.
|
|
81
|
+
*/
|
|
82
|
+
export declare function semanticScores(db: FourDADatabase, table: string, query: string, items: Array<{
|
|
83
|
+
id: number;
|
|
84
|
+
text: string;
|
|
85
|
+
}>, config: EmbeddingConfig, opts?: {
|
|
86
|
+
maxNewEmbeds?: number;
|
|
87
|
+
}): Promise<SemanticScoreResult>;
|
|
88
|
+
//# sourceMappingURL=embeddings.d.ts.map
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Optional, provider-backed semantic embeddings for local knowledge recall.
|
|
4
|
+
*
|
|
5
|
+
* The MCP server ships ZERO embedding dependencies — that is the whole point of
|
|
6
|
+
* the `npx` zero-install promise. Semantic recall therefore lights up ONLY when
|
|
7
|
+
* the user already has an embedding provider reachable:
|
|
8
|
+
* - Ollama (local, private, free) — set settings.embedding.provider = "ollama"
|
|
9
|
+
* - OpenAI (cloud, explicit opt-in) — set settings.embedding.provider = "openai"
|
|
10
|
+
*
|
|
11
|
+
* When no provider is configured, the provider is unreachable, the embed model is
|
|
12
|
+
* missing, or FOURDA_OFFLINE is set, every caller falls back to ranked lexical
|
|
13
|
+
* recall and the response says so via `recall_mode`. Critically, the server embeds
|
|
14
|
+
* BOTH the query and the stored rows with the SAME provider/model, so the vector
|
|
15
|
+
* space is always self-consistent — we never mix our vectors with the desktop
|
|
16
|
+
* app's fastembed vectors.
|
|
17
|
+
*
|
|
18
|
+
* No sqlite-vec: similarity is a plain JS cosine over Float32 BLOBs, which is
|
|
19
|
+
* trivially fast for the few-hundred-row local knowledge tables this serves.
|
|
20
|
+
*/
|
|
21
|
+
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
22
|
+
import { join, dirname } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const DEFAULT_OLLAMA_EMBED_MODEL = "nomic-embed-text";
|
|
26
|
+
const DEFAULT_OPENAI_EMBED_MODEL = "text-embedding-3-small";
|
|
27
|
+
const DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
28
|
+
/** True when network calls are disabled by the FOURDA_OFFLINE escape hatch. */
|
|
29
|
+
export function isOfflineMode() {
|
|
30
|
+
return String(process.env.FOURDA_OFFLINE || "").toLowerCase() === "true";
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Locate settings.json the same way llm.ts does, so embedding config lives
|
|
34
|
+
* alongside the rest of the user's local configuration.
|
|
35
|
+
*/
|
|
36
|
+
function findSettingsFile() {
|
|
37
|
+
const envPath = process.env.FOURDA_SETTINGS_PATH;
|
|
38
|
+
if (envPath && existsSync(envPath))
|
|
39
|
+
return envPath;
|
|
40
|
+
const dbPath = process.env.FOURDA_DB_PATH;
|
|
41
|
+
if (dbPath) {
|
|
42
|
+
const sibling = join(dirname(dbPath), "settings.json");
|
|
43
|
+
if (existsSync(sibling))
|
|
44
|
+
return sibling;
|
|
45
|
+
}
|
|
46
|
+
const candidates = [
|
|
47
|
+
join(process.cwd(), "data", "settings.json"),
|
|
48
|
+
join(__dirname, "..", "data", "settings.json"),
|
|
49
|
+
join(process.cwd(), "..", "data", "settings.json"),
|
|
50
|
+
];
|
|
51
|
+
for (const candidate of candidates) {
|
|
52
|
+
if (existsSync(candidate))
|
|
53
|
+
return candidate;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
let _cached = null;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the embedding provider. Order of precedence:
|
|
60
|
+
* 1. FOURDA_OFFLINE -> always null (no network)
|
|
61
|
+
* 2. environment variables -> FOURDA_EMBED_PROVIDER / FOURDA_EMBED_MODEL / ...
|
|
62
|
+
* 3. settings.json -> the `embedding` block
|
|
63
|
+
* 4. (nothing) -> null, callers stay lexical
|
|
64
|
+
*
|
|
65
|
+
* Deliberately does NOT auto-derive from the chat `llm` block: enabling semantic
|
|
66
|
+
* recall is an explicit choice so a recall never makes a surprise network call.
|
|
67
|
+
*/
|
|
68
|
+
export function getEmbeddingConfig() {
|
|
69
|
+
if (isOfflineMode())
|
|
70
|
+
return null;
|
|
71
|
+
// 2. Environment override
|
|
72
|
+
const envProvider = (process.env.FOURDA_EMBED_PROVIDER || "").toLowerCase();
|
|
73
|
+
if (envProvider === "ollama" || envProvider === "openai") {
|
|
74
|
+
return normalizeConfig({
|
|
75
|
+
provider: envProvider,
|
|
76
|
+
model: process.env.FOURDA_EMBED_MODEL,
|
|
77
|
+
url: process.env.OLLAMA_URL,
|
|
78
|
+
api_key: process.env.OPENAI_API_KEY,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// 3. settings.json `embedding` block (mtime-cached)
|
|
82
|
+
const settingsPath = findSettingsFile();
|
|
83
|
+
if (!settingsPath)
|
|
84
|
+
return null;
|
|
85
|
+
try {
|
|
86
|
+
const mtimeMs = statSync(settingsPath).mtimeMs;
|
|
87
|
+
if (_cached && _cached.path === settingsPath && _cached.mtimeMs === mtimeMs) {
|
|
88
|
+
return _cached.config;
|
|
89
|
+
}
|
|
90
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
91
|
+
const raw = settings.embedding;
|
|
92
|
+
const config = raw
|
|
93
|
+
? normalizeConfig({
|
|
94
|
+
provider: (raw.provider || "").toLowerCase(),
|
|
95
|
+
model: raw.model,
|
|
96
|
+
url: raw.url || raw.base_url,
|
|
97
|
+
api_key: raw.api_key,
|
|
98
|
+
})
|
|
99
|
+
: null;
|
|
100
|
+
_cached = { config, path: settingsPath, mtimeMs };
|
|
101
|
+
return config;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function normalizeConfig(raw) {
|
|
108
|
+
if (raw.provider === "ollama") {
|
|
109
|
+
return {
|
|
110
|
+
provider: "ollama",
|
|
111
|
+
model: raw.model || DEFAULT_OLLAMA_EMBED_MODEL,
|
|
112
|
+
url: raw.url || DEFAULT_OLLAMA_URL,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
if (raw.provider === "openai") {
|
|
116
|
+
if (!raw.api_key)
|
|
117
|
+
return null; // openai needs a key — no key, no semantic
|
|
118
|
+
return {
|
|
119
|
+
provider: "openai",
|
|
120
|
+
model: raw.model || DEFAULT_OPENAI_EMBED_MODEL,
|
|
121
|
+
apiKey: raw.api_key,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
/** A short, stable identity for the active model, stored alongside each vector. */
|
|
127
|
+
export function modelTag(config) {
|
|
128
|
+
return `${config.provider}:${config.model}`;
|
|
129
|
+
}
|
|
130
|
+
// =============================================================================
|
|
131
|
+
// Embedding calls
|
|
132
|
+
// =============================================================================
|
|
133
|
+
/** Cap on the text length sent to the embedder, to bound payload size. */
|
|
134
|
+
const MAX_EMBED_CHARS = 8000;
|
|
135
|
+
/**
|
|
136
|
+
* Embed a single text. Returns null on ANY failure (provider down, model
|
|
137
|
+
* missing, bad response, empty input) so callers degrade to lexical silently.
|
|
138
|
+
*/
|
|
139
|
+
export async function embedText(text, config, signal) {
|
|
140
|
+
const input = (text || "").slice(0, MAX_EMBED_CHARS).trim();
|
|
141
|
+
if (!input)
|
|
142
|
+
return null;
|
|
143
|
+
try {
|
|
144
|
+
if (config.provider === "ollama") {
|
|
145
|
+
const base = config.url || DEFAULT_OLLAMA_URL;
|
|
146
|
+
const res = await fetch(`${base}/api/embeddings`, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: { "Content-Type": "application/json" },
|
|
149
|
+
body: JSON.stringify({ model: config.model, prompt: input }),
|
|
150
|
+
signal,
|
|
151
|
+
});
|
|
152
|
+
if (!res.ok)
|
|
153
|
+
return null;
|
|
154
|
+
const data = (await res.json());
|
|
155
|
+
return data.embedding && data.embedding.length
|
|
156
|
+
? Float32Array.from(data.embedding)
|
|
157
|
+
: null;
|
|
158
|
+
}
|
|
159
|
+
// openai
|
|
160
|
+
const res = await fetch("https://api.openai.com/v1/embeddings", {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: {
|
|
163
|
+
"Content-Type": "application/json",
|
|
164
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify({ model: config.model, input }),
|
|
167
|
+
signal,
|
|
168
|
+
});
|
|
169
|
+
if (!res.ok)
|
|
170
|
+
return null;
|
|
171
|
+
const data = (await res.json());
|
|
172
|
+
const emb = data.data?.[0]?.embedding;
|
|
173
|
+
return emb && emb.length ? Float32Array.from(emb) : null;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// =============================================================================
|
|
180
|
+
// Vector helpers (BLOB <-> Float32Array, cosine)
|
|
181
|
+
// =============================================================================
|
|
182
|
+
/** Pack a vector into a little-endian Float32 BLOB for SQLite storage. */
|
|
183
|
+
export function vectorToBlob(vec) {
|
|
184
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Unpack a stored BLOB back into a Float32Array. Returns null for empty/
|
|
188
|
+
* misaligned blobs so a corrupt cell never throws mid-recall. Copies the bytes
|
|
189
|
+
* to guarantee 4-byte alignment regardless of the source buffer's offset.
|
|
190
|
+
*/
|
|
191
|
+
export function blobToVector(blob) {
|
|
192
|
+
if (!blob || blob.byteLength === 0 || blob.byteLength % 4 !== 0)
|
|
193
|
+
return null;
|
|
194
|
+
const copy = Buffer.from(blob);
|
|
195
|
+
return new Float32Array(copy.buffer, copy.byteOffset, copy.byteLength / 4);
|
|
196
|
+
}
|
|
197
|
+
/** Cosine similarity in [-1, 1]; 0 for length mismatch or a zero vector. */
|
|
198
|
+
export function cosineSimilarity(a, b) {
|
|
199
|
+
if (a.length !== b.length || a.length === 0)
|
|
200
|
+
return 0;
|
|
201
|
+
let dot = 0;
|
|
202
|
+
let na = 0;
|
|
203
|
+
let nb = 0;
|
|
204
|
+
for (let i = 0; i < a.length; i++) {
|
|
205
|
+
dot += a[i] * b[i];
|
|
206
|
+
na += a[i] * a[i];
|
|
207
|
+
nb += b[i] * b[i];
|
|
208
|
+
}
|
|
209
|
+
if (na === 0 || nb === 0)
|
|
210
|
+
return 0;
|
|
211
|
+
return dot / (Math.sqrt(na) * Math.sqrt(nb));
|
|
212
|
+
}
|
|
213
|
+
// =============================================================================
|
|
214
|
+
// Generic semantic scoring over a local knowledge table
|
|
215
|
+
// =============================================================================
|
|
216
|
+
/** Default cap on rows embedded per call, so a cold cache cannot stall a response. */
|
|
217
|
+
const DEFAULT_MAX_NEW_EMBEDS = 48;
|
|
218
|
+
/**
|
|
219
|
+
* Score `items` (id + the text to embed) against a query using cached, lazily
|
|
220
|
+
* backfilled embeddings stored in `<table>.embedding` / `<table>.embedding_model`.
|
|
221
|
+
*
|
|
222
|
+
* Shared by every semantic-recall surface (agent_memory, developer_decisions) so
|
|
223
|
+
* the embed/cache/cosine logic lives in exactly one place. `table` is always a
|
|
224
|
+
* server-controlled constant (never user input), so the interpolated SQL is safe.
|
|
225
|
+
*
|
|
226
|
+
* Returns `queryEmbedded: false` (and an empty map) when the provider is
|
|
227
|
+
* unreachable, so callers fall back to pure lexical recall.
|
|
228
|
+
*/
|
|
229
|
+
export async function semanticScores(db, table, query, items, config, opts = {}) {
|
|
230
|
+
const tag = modelTag(config);
|
|
231
|
+
const queryVec = await embedText(query, config);
|
|
232
|
+
if (!queryVec) {
|
|
233
|
+
return { semanticById: new Map(), queryEmbedded: false, embeddedCount: 0, newlyEmbedded: 0, model: tag };
|
|
234
|
+
}
|
|
235
|
+
// Cache columns are optional and added lazily for databases that predate them.
|
|
236
|
+
db.ensureColumn(table, "embedding", "BLOB");
|
|
237
|
+
db.ensureColumn(table, "embedding_model", "TEXT");
|
|
238
|
+
const rawDb = db.getRawDb();
|
|
239
|
+
const stored = loadStoredEmbeddings(rawDb, table, items.map((i) => i.id), tag);
|
|
240
|
+
const updateStmt = rawDb.prepare(`UPDATE ${table} SET embedding = ?, embedding_model = ? WHERE id = ?`);
|
|
241
|
+
const maxNew = opts.maxNewEmbeds ?? DEFAULT_MAX_NEW_EMBEDS;
|
|
242
|
+
const semanticById = new Map();
|
|
243
|
+
let embeddedCount = 0;
|
|
244
|
+
let newlyEmbedded = 0;
|
|
245
|
+
for (const item of items) {
|
|
246
|
+
let vec = stored.get(item.id) ?? null;
|
|
247
|
+
if (!vec && newlyEmbedded < maxNew) {
|
|
248
|
+
vec = await embedText(item.text, config);
|
|
249
|
+
if (vec) {
|
|
250
|
+
try {
|
|
251
|
+
updateStmt.run(vectorToBlob(vec), tag, item.id);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
// Persisting the cache is best-effort; scoring still proceeds.
|
|
255
|
+
}
|
|
256
|
+
newlyEmbedded++;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (vec && vec.length === queryVec.length) {
|
|
260
|
+
embeddedCount++;
|
|
261
|
+
semanticById.set(item.id, cosineSimilarity(queryVec, vec));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return { semanticById, queryEmbedded: true, embeddedCount, newlyEmbedded, model: tag };
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Load cached embeddings for the given ids that were produced by the CURRENT model
|
|
268
|
+
* (a model change invalidates old vectors). Returns a map of id -> vector.
|
|
269
|
+
*/
|
|
270
|
+
function loadStoredEmbeddings(rawDb, table, ids, modelTagValue) {
|
|
271
|
+
const out = new Map();
|
|
272
|
+
if (ids.length === 0)
|
|
273
|
+
return out;
|
|
274
|
+
try {
|
|
275
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
276
|
+
const rows = rawDb
|
|
277
|
+
.prepare(`SELECT id, embedding, embedding_model FROM ${table} WHERE id IN (${placeholders})`)
|
|
278
|
+
.all(...ids);
|
|
279
|
+
for (const row of rows) {
|
|
280
|
+
if (row.embedding && row.embedding_model === modelTagValue) {
|
|
281
|
+
const vec = blobToVector(row.embedding);
|
|
282
|
+
if (vec)
|
|
283
|
+
out.set(row.id, vec);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// Defensive: a missing column simply means no cache, so everything re-embeds.
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=embeddings.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* 4DA MCP Server
|
|
3
|
+
* 4DA MCP Server
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Provides dependency-intelligence tools (CVE scanning, dependency health,
|
|
6
|
+
* upgrade planning, ecosystem news, decision/agent memory) to Claude Code,
|
|
7
|
+
* Claude Desktop, and other MCP hosts. Runs locally; the only data leaving the
|
|
8
|
+
* machine is public package names/versions sent to registries (OSV, npm, etc.).
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Do NOT expose this server over a network without adding proper auth.
|
|
10
|
+
* SECURITY: the server trusts its local environment and does NOT implement
|
|
11
|
+
* authentication. The optional --http transport is intended for localhost use;
|
|
12
|
+
* do NOT expose it over a network without putting your own auth in front.
|
|
13
13
|
*/
|
|
14
14
|
/**
|
|
15
|
-
* 4DA MCP Server v4.6.
|
|
15
|
+
* 4DA MCP Server v4.6.3 — Dependency Intelligence for AI Coding Agents
|
|
16
16
|
*
|
|
17
17
|
* 14 tools across 5 categories. Live vulnerability scanning (OSV.dev),
|
|
18
18
|
* ecosystem news, persistent memory, and tech stack awareness for any MCP host.
|