@4da/mcp-server 4.6.5 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.js +72 -37
- package/dist/http-transport.d.ts +12 -8
- package/dist/http-transport.js +23 -57
- package/dist/index.d.ts +7 -24
- package/dist/index.js +151 -131
- package/dist/setup.js +0 -0
- package/dist/tool-dispatch.d.ts +2 -6
- package/dist/tools/get-actionable-signals.js +5 -1
- package/dist/tools/get-relevant-content.js +4 -1
- package/package.json +28 -14
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@4da/mcp-server)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](https://nodejs.org/)
|
|
6
6
|
[](https://smithery.ai/server/@4da/mcp-server)
|
|
7
7
|
|
|
8
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.
|
package/dist/db.d.ts
CHANGED
|
@@ -107,7 +107,7 @@ export declare class FourDADatabase {
|
|
|
107
107
|
* Get relevant content items scored by the Rust pipeline (preferred) or
|
|
108
108
|
* TypeScript fallback scoring when Rust scores are unavailable.
|
|
109
109
|
*/
|
|
110
|
-
getRelevantContent(minScore?: number, sourceType?: string, limit?: number, sinceHours?: number): RelevantItem[];
|
|
110
|
+
getRelevantContent(minScore?: number, sourceType?: string, limit?: number, sinceHours?: number, requireCurrentVersion?: boolean): RelevantItem[];
|
|
111
111
|
/**
|
|
112
112
|
* Read the live freshness state of the feed database. Answered from ground truth, not from any
|
|
113
113
|
* claim that a refresh ran — so DB-backed tools can tell the caller whether the curated feed is
|
package/dist/db.js
CHANGED
|
@@ -635,15 +635,16 @@ export class FourDADatabase {
|
|
|
635
635
|
* Get relevant content items scored by the Rust pipeline (preferred) or
|
|
636
636
|
* TypeScript fallback scoring when Rust scores are unavailable.
|
|
637
637
|
*/
|
|
638
|
-
getRelevantContent(minScore = 0.35, sourceType, limit = 20, sinceHours = 24) {
|
|
638
|
+
getRelevantContent(minScore = 0.35, sourceType, limit = 20, sinceHours = 24, requireCurrentVersion = false) {
|
|
639
639
|
const sinceDate = new Date(Date.now() - sinceHours * 60 * 60 * 1000)
|
|
640
640
|
.toISOString()
|
|
641
641
|
.replace("T", " ")
|
|
642
642
|
.slice(0, 19);
|
|
643
643
|
const hasRustScores = this.hasColumn("source_items", "relevance_score");
|
|
644
644
|
if (hasRustScores) {
|
|
645
|
-
return this.getRelevantContentFromRust(minScore, sourceType, limit, sinceDate);
|
|
645
|
+
return this.getRelevantContentFromRust(minScore, sourceType, limit, sinceDate, requireCurrentVersion);
|
|
646
646
|
}
|
|
647
|
+
// TypeScript fallback computes scores fresh per call — current by construction.
|
|
647
648
|
return this.getRelevantContentFallback(minScore, sourceType, limit, sinceDate);
|
|
648
649
|
}
|
|
649
650
|
/**
|
|
@@ -722,13 +723,57 @@ export class FourDADatabase {
|
|
|
722
723
|
* Use Rust-computed relevance_score from the desktop app's scoring pipeline.
|
|
723
724
|
* These scores are from the full 5-axis PASIFA engine — much more accurate.
|
|
724
725
|
*/
|
|
725
|
-
getRelevantContentFromRust(minScore, sourceType, limit, sinceDate) {
|
|
726
|
+
getRelevantContentFromRust(minScore, sourceType, limit, sinceDate, requireCurrentVersion = false) {
|
|
726
727
|
// Grounding subquery — guarded: older/partial 4DA DBs may have relevance_score
|
|
727
728
|
// (so the Rust path runs) but lack source_item_dependencies. Fall back to 0
|
|
728
729
|
// (→ evidence_class "semantic_only") rather than throwing.
|
|
729
730
|
const depCount = this.hasColumn("source_item_dependencies", "source_item_id")
|
|
730
731
|
? `(SELECT COUNT(*) FROM source_item_dependencies d WHERE d.source_item_id = source_items.id)`
|
|
731
732
|
: `0`;
|
|
733
|
+
// Stale-epoch guard for deep-window fallbacks: when the caller widens to the
|
|
734
|
+
// 30-day window it reaches items whose stored score/signal columns may come
|
|
735
|
+
// from an OLDER scoring pipeline (a version bump makes the tail stale until
|
|
736
|
+
// the drain re-scores it). Ranking those raw scores against current ones —
|
|
737
|
+
// or trusting stale signal_type/signal_priority — produces claims the
|
|
738
|
+
// current pipeline doesn't stand behind. The MCP server can't import the
|
|
739
|
+
// Rust PIPELINE_VERSION constant, so the DB itself is the source of truth:
|
|
740
|
+
// MAX(scored_pipeline_version) IS the current brain's stamp (uncorrelated
|
|
741
|
+
// subquery, computed once per statement). Guarded on column existence for
|
|
742
|
+
// older desktop-app DBs.
|
|
743
|
+
const versionGuard = requireCurrentVersion && this.hasColumn("source_items", "scored_pipeline_version")
|
|
744
|
+
? ` AND scored_pipeline_version = (SELECT MAX(scored_pipeline_version) FROM source_items)`
|
|
745
|
+
: ``;
|
|
746
|
+
// Curation guard: never return an item the current pipeline EXPLICITLY
|
|
747
|
+
// rejected. `feed_relevant` is the analysis run's persisted verdict, and a
|
|
748
|
+
// score threshold alone does not respect it — the desktop content graph hit
|
|
749
|
+
// exactly this and adopted corpus parity in Phase 95 (W4-5).
|
|
750
|
+
//
|
|
751
|
+
// It matters because a verdict can be negative while the score stays above
|
|
752
|
+
// the threshold. v18's look-alike gate makes ungrounded registry releases
|
|
753
|
+
// categorically non-relevant while deliberately KEEPING their capped score
|
|
754
|
+
// (0.37 / 0.42) for ranking and display, so `relevance_score >= 0.35` alone
|
|
755
|
+
// still surfaces them. Live 2026-07-26: 30 crates_io items inside the
|
|
756
|
+
// 30-day window were already `feed_relevant = 0` and were still returned.
|
|
757
|
+
//
|
|
758
|
+
// NULL is KEPT, not excluded: an unjudged item is "not yet curated", not
|
|
759
|
+
// "rejected", and dropping it would blank the tool on a fresh corpus
|
|
760
|
+
// (cold-start doctrine). Guarded on column existence for older 4DA DBs.
|
|
761
|
+
//
|
|
762
|
+
// Deliberately NOT extended with `feed_verdict_version = <current>` even
|
|
763
|
+
// though Phase 101 added that stamp, and even though the 720h deep-fallback
|
|
764
|
+
// above DOES gate on `scored_pipeline_version`. The asymmetry is intentional:
|
|
765
|
+
// the version guard there narrows a last-resort widening, whereas this guard
|
|
766
|
+
// sits on the PRIMARY path, so excluding not-yet-reconciled verdicts would
|
|
767
|
+
// empty the tool after every pipeline bump until the reconciliation pass
|
|
768
|
+
// caught up (measured on the desktop: 94% of live graph nodes held a stale
|
|
769
|
+
// verdict immediately after the v18 bump). The reconciliation converging is
|
|
770
|
+
// the fix — see `analysis_backfill::reconcile_stale_verdicts_cycle`. This
|
|
771
|
+
// note exists because `check-mcp-server-sync.cjs` asks "do MCP queries need
|
|
772
|
+
// updating?" on every db-layer change; the answer for the verdict stamp is
|
|
773
|
+
// no, by design.
|
|
774
|
+
const curationGuard = this.hasColumn("source_items", "feed_relevant")
|
|
775
|
+
? ` AND (feed_relevant IS NULL OR feed_relevant = 1)`
|
|
776
|
+
: ``;
|
|
732
777
|
let query = `
|
|
733
778
|
SELECT id, source_type, source_id, url, title, content, content_hash,
|
|
734
779
|
created_at, last_seen, relevance_score, content_type,
|
|
@@ -736,7 +781,7 @@ export class FourDADatabase {
|
|
|
736
781
|
${depCount} AS dep_match_count
|
|
737
782
|
FROM source_items
|
|
738
783
|
WHERE relevance_score >= ?
|
|
739
|
-
AND datetime(created_at) >= datetime(?)
|
|
784
|
+
AND datetime(created_at) >= datetime(?)${versionGuard}${curationGuard}
|
|
740
785
|
`;
|
|
741
786
|
const params = [minScore, sinceDate];
|
|
742
787
|
if (sourceType) {
|
|
@@ -1008,12 +1053,13 @@ export class FourDADatabase {
|
|
|
1008
1053
|
context.ace.detected_tech.filter((dt) => itemTopics.some((t) => this.topicMatches(t, dt.name))).length *
|
|
1009
1054
|
0.05
|
|
1010
1055
|
: 0;
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
// Anti-penalty from exclusions
|
|
1056
|
+
// Learned affinity score DEMOTED in v19 (AD-029) — must mirror
|
|
1057
|
+
// computeRelevanceScore so the explanation matches what actually
|
|
1058
|
+
// scored (the two previously diverged on the anti-penalty weight too:
|
|
1059
|
+
// scorer used *0.3, this explanation used *0.5).
|
|
1060
|
+
const learnedScore = 0;
|
|
1061
|
+
// Anti-penalty from user-authored exclusions only (learned anti-topics
|
|
1062
|
+
// demoted with the rest of the behavioral stack, AD-029).
|
|
1017
1063
|
let antiPenalty = 0;
|
|
1018
1064
|
for (const exclusion of context.exclusions) {
|
|
1019
1065
|
if (itemTopics.some((t) => this.topicMatches(t, exclusion))) {
|
|
@@ -1021,13 +1067,6 @@ export class FourDADatabase {
|
|
|
1021
1067
|
break;
|
|
1022
1068
|
}
|
|
1023
1069
|
}
|
|
1024
|
-
if (antiPenalty === 0 && context.learned) {
|
|
1025
|
-
for (const antiTopic of context.learned.anti_topics) {
|
|
1026
|
-
if (itemTopics.some((t) => this.topicMatches(t, antiTopic.topic))) {
|
|
1027
|
-
antiPenalty = Math.max(antiPenalty, antiTopic.confidence * 0.5);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
1070
|
const finalScore = Math.max(0, Math.min(1, staticScore + aceScore + learnedScore - antiPenalty));
|
|
1032
1071
|
const scoreBreakdown = {
|
|
1033
1072
|
embedding_similarity: null, // Not computing embedding similarity in MCP server
|
|
@@ -1087,12 +1126,17 @@ export class FourDADatabase {
|
|
|
1087
1126
|
message: `Item ${itemId} of type ${sourceType} not found`,
|
|
1088
1127
|
};
|
|
1089
1128
|
}
|
|
1090
|
-
// Map action to signal strength
|
|
1129
|
+
// Map action to signal strength — v19: unified onto the canonical ACE
|
|
1130
|
+
// scale (src-tauri/src/ace/behavior/types.rs). The old MCP-only scale
|
|
1131
|
+
// (click 0.3 / save 0.8 / dismiss -0.2 / mark_irrelevant -0.5) meant an
|
|
1132
|
+
// agent-recorded rejection never crossed the Rust side's explicit-
|
|
1133
|
+
// negative (-0.8) or anti-topic (-0.5 exclusive) thresholds, so agent
|
|
1134
|
+
// feedback could never register a real rejection.
|
|
1091
1135
|
const signalStrength = {
|
|
1092
|
-
click: 0.
|
|
1093
|
-
save: 0
|
|
1094
|
-
dismiss: -0.
|
|
1095
|
-
mark_irrelevant: -0
|
|
1136
|
+
click: 0.55,
|
|
1137
|
+
save: 1.0,
|
|
1138
|
+
dismiss: -0.8,
|
|
1139
|
+
mark_irrelevant: -1.0,
|
|
1096
1140
|
};
|
|
1097
1141
|
try {
|
|
1098
1142
|
const stmt = this.db.prepare(`
|
|
@@ -1311,21 +1355,12 @@ export class FourDADatabase {
|
|
|
1311
1355
|
}
|
|
1312
1356
|
}
|
|
1313
1357
|
}
|
|
1314
|
-
// Learned
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
// Anti-topics penalty
|
|
1323
|
-
for (const antiTopic of context.learned.anti_topics) {
|
|
1324
|
-
if (itemTopics.some((t) => this.topicMatches(t, antiTopic.topic))) {
|
|
1325
|
-
score -= antiTopic.confidence * 0.3;
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1358
|
+
// Learned affinity/anti-topic weighting DEMOTED in v19 (AD-029) to
|
|
1359
|
+
// mirror the Rust pipeline: this TypeScript fallback scorer applied
|
|
1360
|
+
// its own behavioral weights (+affinity*0.1*confidence,
|
|
1361
|
+
// -anti_confidence*0.3) whenever Rust scores were absent — a second,
|
|
1362
|
+
// uncalibrated ranking regime fed by the same untrusted capture layer.
|
|
1363
|
+
// Static identity + ACE stack matching above remain the scoring basis.
|
|
1329
1364
|
return Math.max(0, Math.min(1, score));
|
|
1330
1365
|
}
|
|
1331
1366
|
/**
|
package/dist/http-transport.d.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 4DA MCP Server — Streamable HTTP Transport
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Serves MCP over HTTP using the v2 `createMcpHandler` entry (one factory,
|
|
5
|
+
* one endpoint, both protocol eras): 2026-07-28 requests are served natively;
|
|
6
|
+
* 2025-era requests get the established stateless serving (fresh instance per
|
|
7
|
+
* request — the same behavior as the previous per-request
|
|
8
|
+
* `StreamableHTTPServerTransport` wiring). Node's built-in http module only;
|
|
9
|
+
* no express or other framework needed.
|
|
6
10
|
*
|
|
7
11
|
* Security:
|
|
8
12
|
* - Binds to 127.0.0.1 only (localhost)
|
|
9
13
|
* - DNS rebinding protection via Origin header check
|
|
10
|
-
* - Stateless
|
|
14
|
+
* - Stateless (no session tracking)
|
|
11
15
|
*/
|
|
12
|
-
import { Server } from "@modelcontextprotocol/
|
|
16
|
+
import { type Server } from "@modelcontextprotocol/server";
|
|
13
17
|
export interface HttpServerOptions {
|
|
14
18
|
port: number;
|
|
15
19
|
host: string;
|
|
@@ -17,9 +21,9 @@ export interface HttpServerOptions {
|
|
|
17
21
|
/**
|
|
18
22
|
* Start a Streamable HTTP server for the MCP protocol.
|
|
19
23
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
24
|
+
* Takes a server factory: `createMcpHandler` builds a fresh instance per
|
|
25
|
+
* request (stateless — compatible with serverless environments and simple
|
|
26
|
+
* round-robin load balancing).
|
|
23
27
|
*/
|
|
24
|
-
export declare function startHttpServer(
|
|
28
|
+
export declare function startHttpServer(factory: () => Server, options?: HttpServerOptions): Promise<void>;
|
|
25
29
|
//# sourceMappingURL=http-transport.d.ts.map
|
package/dist/http-transport.js
CHANGED
|
@@ -2,52 +2,44 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* 4DA MCP Server — Streamable HTTP Transport
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Serves MCP over HTTP using the v2 `createMcpHandler` entry (one factory,
|
|
6
|
+
* one endpoint, both protocol eras): 2026-07-28 requests are served natively;
|
|
7
|
+
* 2025-era requests get the established stateless serving (fresh instance per
|
|
8
|
+
* request — the same behavior as the previous per-request
|
|
9
|
+
* `StreamableHTTPServerTransport` wiring). Node's built-in http module only;
|
|
10
|
+
* no express or other framework needed.
|
|
7
11
|
*
|
|
8
12
|
* Security:
|
|
9
13
|
* - Binds to 127.0.0.1 only (localhost)
|
|
10
14
|
* - DNS rebinding protection via Origin header check
|
|
11
|
-
* - Stateless
|
|
15
|
+
* - Stateless (no session tracking)
|
|
12
16
|
*/
|
|
13
17
|
import { createServer } from "node:http";
|
|
14
18
|
import { readFileSync } from "node:fs";
|
|
15
19
|
import { join, dirname } from "node:path";
|
|
16
20
|
import { fileURLToPath } from "node:url";
|
|
17
|
-
import {
|
|
21
|
+
import { createMcpHandler } from "@modelcontextprotocol/server";
|
|
22
|
+
import { toNodeHandler } from "@modelcontextprotocol/node";
|
|
18
23
|
import { extractAuthClaims } from "./auth.js";
|
|
19
24
|
const DEFAULT_PORT = 4840;
|
|
20
25
|
const DEFAULT_HOST = "127.0.0.1";
|
|
21
26
|
const __ht_dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
27
|
const SERVER_VERSION = JSON.parse(readFileSync(join(__ht_dirname, "..", "package.json"), "utf-8")).version;
|
|
23
|
-
/**
|
|
24
|
-
* Parse JSON body from an incoming HTTP request.
|
|
25
|
-
*/
|
|
26
|
-
function parseBody(req) {
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
const chunks = [];
|
|
29
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
30
|
-
req.on("end", () => {
|
|
31
|
-
try {
|
|
32
|
-
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
33
|
-
resolve(raw ? JSON.parse(raw) : undefined);
|
|
34
|
-
}
|
|
35
|
-
catch (e) {
|
|
36
|
-
reject(e);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
req.on("error", reject);
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
28
|
/**
|
|
43
29
|
* Start a Streamable HTTP server for the MCP protocol.
|
|
44
30
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
31
|
+
* Takes a server factory: `createMcpHandler` builds a fresh instance per
|
|
32
|
+
* request (stateless — compatible with serverless environments and simple
|
|
33
|
+
* round-robin load balancing).
|
|
48
34
|
*/
|
|
49
|
-
export async function startHttpServer(
|
|
35
|
+
export async function startHttpServer(factory, options = { port: DEFAULT_PORT, host: DEFAULT_HOST }) {
|
|
50
36
|
const { port, host } = options;
|
|
37
|
+
const mcpHandler = createMcpHandler(factory);
|
|
38
|
+
const handleMcpRequest = toNodeHandler(mcpHandler, {
|
|
39
|
+
onerror: (err) => {
|
|
40
|
+
console.error("MCP transport error:", err);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
51
43
|
const httpServer = createServer(async (req, res) => {
|
|
52
44
|
// DNS rebinding protection: reject cross-origin requests from non-local origins
|
|
53
45
|
const origin = req.headers.origin;
|
|
@@ -100,36 +92,9 @@ export async function startHttpServer(server, options = { port: DEFAULT_PORT, ho
|
|
|
100
92
|
res.end(JSON.stringify({ error: "Not Found" }));
|
|
101
93
|
return;
|
|
102
94
|
}
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
body = await parseBody(req);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
111
|
-
res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// Stateless mode: new transport per request
|
|
116
|
-
const transport = new StreamableHTTPServerTransport({
|
|
117
|
-
sessionIdGenerator: undefined,
|
|
118
|
-
});
|
|
119
|
-
res.on("close", () => {
|
|
120
|
-
transport.close();
|
|
121
|
-
});
|
|
122
|
-
try {
|
|
123
|
-
await server.connect(transport);
|
|
124
|
-
await transport.handleRequest(req, res, body);
|
|
125
|
-
}
|
|
126
|
-
catch (err) {
|
|
127
|
-
console.error("MCP transport error:", err);
|
|
128
|
-
if (!res.headersSent) {
|
|
129
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
130
|
-
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
131
|
-
}
|
|
132
|
-
}
|
|
95
|
+
// Hand off to the MCP handler — body parsing, era classification, and
|
|
96
|
+
// per-request instance construction happen inside the entry.
|
|
97
|
+
await handleMcpRequest(req, res);
|
|
133
98
|
});
|
|
134
99
|
httpServer.listen(port, host, () => {
|
|
135
100
|
console.error(`4DA MCP Server v${SERVER_VERSION} (Streamable HTTP) listening on http://${host}:${port}/mcp`);
|
|
@@ -138,6 +103,7 @@ export async function startHttpServer(server, options = { port: DEFAULT_PORT, ho
|
|
|
138
103
|
// Graceful shutdown
|
|
139
104
|
const shutdown = () => {
|
|
140
105
|
console.error("Shutting down HTTP server...");
|
|
106
|
+
void mcpHandler.close();
|
|
141
107
|
httpServer.close();
|
|
142
108
|
process.exit(0);
|
|
143
109
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,29 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/server";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Build a Server instance with every handler registered.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* 4DA MCP Server v4.6.3 — Dependency Intelligence for AI Coding Agents
|
|
16
|
-
*
|
|
17
|
-
* 14 tools across 5 categories. Live vulnerability scanning (OSV.dev),
|
|
18
|
-
* ecosystem news, persistent memory, and tech stack awareness for any MCP host.
|
|
19
|
-
*
|
|
20
|
-
* Categories (canonical — matches schema-registry.ts `ToolCategory`):
|
|
21
|
-
* Security (3) — vulnerability scanning, dependency health, upgrade planning
|
|
22
|
-
* Intelligence (7) — briefing, ecosystem pulse, context, content feed,
|
|
23
|
-
* actionable signals, knowledge gaps, feedback
|
|
24
|
-
* Decisions (2) — decision memory, alignment checking
|
|
25
|
-
* Agent (1) — cross-session persistent memory
|
|
26
|
-
* Identity (1) — developer DNA profile
|
|
6
|
+
* The v2 serving entries take a factory: `serveStdio` pins one instance per
|
|
7
|
+
* connection; stateless HTTP (`createMcpHandler`) builds one per request.
|
|
8
|
+
* Handlers close over the module-level database singleton, so every instance
|
|
9
|
+
* shares the same lazily-initialized database and live-intelligence layer.
|
|
27
10
|
*/
|
|
28
|
-
export
|
|
11
|
+
export declare function buildServer(): Server;
|
|
29
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -11,9 +11,6 @@
|
|
|
11
11
|
* SECURITY: the server trusts its local environment and does NOT implement
|
|
12
12
|
* authentication. The optional --http transport is intended for localhost use;
|
|
13
13
|
* do NOT expose it over a network without putting your own auth in front.
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* 4DA MCP Server v4.6.3 — Dependency Intelligence for AI Coding Agents
|
|
17
14
|
*
|
|
18
15
|
* 14 tools across 5 categories. Live vulnerability scanning (OSV.dev),
|
|
19
16
|
* ecosystem news, persistent memory, and tech stack awareness for any MCP host.
|
|
@@ -25,10 +22,14 @@
|
|
|
25
22
|
* Decisions (2) — decision memory, alignment checking
|
|
26
23
|
* Agent (1) — cross-session persistent memory
|
|
27
24
|
* Identity (1) — developer DNA profile
|
|
25
|
+
*
|
|
26
|
+
* Protocol: MCP TypeScript SDK v2. stdio serving goes through `serveStdio`,
|
|
27
|
+
* HTTP through `createMcpHandler` — both entries negotiate the era per
|
|
28
|
+
* connection/request, so 2025-era hosts (classic `initialize` handshake) and
|
|
29
|
+
* 2026-07-28 hosts (stateless `server/discover`) are both supported.
|
|
28
30
|
*/
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
31
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
32
|
+
import { Server } from "@modelcontextprotocol/server";
|
|
32
33
|
import { readFileSync, existsSync } from "node:fs";
|
|
33
34
|
import { join, dirname } from "node:path";
|
|
34
35
|
import { fileURLToPath } from "node:url";
|
|
@@ -61,15 +62,18 @@ import { createDatabase, FourDADatabase } from "./db.js";
|
|
|
61
62
|
// =============================================================================
|
|
62
63
|
// Server Setup
|
|
63
64
|
// =============================================================================
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Project root for scanning/scoping. FOURDA_PROJECT_DIR overrides cwd for
|
|
67
|
+
* hosts where cwd is meaningless (Claude Desktop launches extension servers
|
|
68
|
+
* from an app directory) — the .mcpb bundle wires its directory picker to it.
|
|
69
|
+
* A value still containing "${" is an unsubstituted host template; ignore it.
|
|
70
|
+
*/
|
|
71
|
+
function resolveProjectDir() {
|
|
72
|
+
const dir = process.env.FOURDA_PROJECT_DIR?.trim();
|
|
73
|
+
if (dir && !dir.includes("${"))
|
|
74
|
+
return dir;
|
|
75
|
+
return process.cwd();
|
|
76
|
+
}
|
|
73
77
|
// Database instance (lazy initialized)
|
|
74
78
|
let db = null;
|
|
75
79
|
let liveIntel = null;
|
|
@@ -85,9 +89,12 @@ function getDatabase() {
|
|
|
85
89
|
// Initialize live intelligence layer
|
|
86
90
|
liveIntel = new LiveIntelligence(db.getRawDb());
|
|
87
91
|
setLiveIntelligence(liveIntel);
|
|
88
|
-
// Standalone mode: auto-populate from project scan
|
|
92
|
+
// Standalone mode: auto-populate from project scan.
|
|
93
|
+
// FOURDA_PROJECT_DIR overrides cwd for hosts where cwd is meaningless
|
|
94
|
+
// (Claude Desktop launches extension servers from an app directory, not
|
|
95
|
+
// the user's code) — the .mcpb bundle wires its directory picker to this.
|
|
89
96
|
if (db.isStandalone) {
|
|
90
|
-
const cwd =
|
|
97
|
+
const cwd = resolveProjectDir();
|
|
91
98
|
const scan = scanCurrentProject(cwd);
|
|
92
99
|
db.populateFromScan(scan);
|
|
93
100
|
const detected = [
|
|
@@ -132,7 +139,7 @@ function getDatabase() {
|
|
|
132
139
|
// database (the ACE engine indexes every local project) must not bleed
|
|
133
140
|
// into this project's vulnerability scan.
|
|
134
141
|
const norm = (p) => p.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
|
|
135
|
-
const rootNorm = norm(
|
|
142
|
+
const rootNorm = norm(resolveProjectDir());
|
|
136
143
|
const groups = new Map();
|
|
137
144
|
for (const row of rows) {
|
|
138
145
|
const projectPath = row.project_path || process.cwd();
|
|
@@ -168,128 +175,136 @@ function getDatabase() {
|
|
|
168
175
|
return db;
|
|
169
176
|
}
|
|
170
177
|
// =============================================================================
|
|
171
|
-
//
|
|
178
|
+
// Server Factory
|
|
172
179
|
// =============================================================================
|
|
173
180
|
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* Returns one-liner descriptions only (~500 tokens vs ~4500).
|
|
177
|
-
* Full schemas available via MCP Resources: 4da://schema/{tool_name}
|
|
178
|
-
*/
|
|
179
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
180
|
-
const database = getDatabase();
|
|
181
|
-
return {
|
|
182
|
-
tools: getSlimToolList(database.isStandalone ? true : undefined),
|
|
183
|
-
};
|
|
184
|
-
});
|
|
185
|
-
/**
|
|
186
|
-
* List schema resources
|
|
181
|
+
* Build a Server instance with every handler registered.
|
|
187
182
|
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
183
|
+
* The v2 serving entries take a factory: `serveStdio` pins one instance per
|
|
184
|
+
* connection; stateless HTTP (`createMcpHandler`) builds one per request.
|
|
185
|
+
* Handlers close over the module-level database singleton, so every instance
|
|
186
|
+
* shares the same lazily-initialized database and live-intelligence layer.
|
|
190
187
|
*/
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
mimeType: "application/json",
|
|
200
|
-
},
|
|
201
|
-
// Category manifest for tool discovery
|
|
202
|
-
{
|
|
203
|
-
uri: "4da://categories",
|
|
204
|
-
name: "Tool categories",
|
|
205
|
-
description: "Tool groupings by category with tag metadata",
|
|
206
|
-
mimeType: "application/json",
|
|
188
|
+
export function buildServer() {
|
|
189
|
+
const server = new Server({
|
|
190
|
+
name: "4da-server",
|
|
191
|
+
version: SERVER_VERSION,
|
|
192
|
+
}, {
|
|
193
|
+
capabilities: {
|
|
194
|
+
tools: { listChanged: true },
|
|
195
|
+
resources: {},
|
|
207
196
|
},
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
*/
|
|
214
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
215
|
-
const uri = request.params.uri;
|
|
216
|
-
// Handle category manifest
|
|
217
|
-
if (uri === "4da://categories") {
|
|
197
|
+
});
|
|
198
|
+
// List available tools (SLIM): one-liner descriptions only (~500 tokens vs
|
|
199
|
+
// ~4500). Full schemas available via MCP Resources: 4da://schema/{tool_name}
|
|
200
|
+
server.setRequestHandler("tools/list", async () => {
|
|
201
|
+
const database = getDatabase();
|
|
218
202
|
return {
|
|
219
|
-
|
|
220
|
-
{
|
|
221
|
-
uri,
|
|
222
|
-
mimeType: "application/json",
|
|
223
|
-
text: JSON.stringify(getCategoryManifest(), null, 2),
|
|
224
|
-
},
|
|
225
|
-
],
|
|
203
|
+
tools: getSlimToolList(database.isStandalone ? true : undefined),
|
|
226
204
|
};
|
|
227
|
-
}
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
205
|
+
});
|
|
206
|
+
// List schema resources: full tool schemas as MCP Resources for lazy
|
|
207
|
+
// loading, plus the skill and category manifests.
|
|
208
|
+
server.setRequestHandler("resources/list", async () => {
|
|
209
|
+
const resources = [
|
|
210
|
+
...getSchemaResources(),
|
|
211
|
+
// Skill manifest for agent dispatch
|
|
212
|
+
{
|
|
213
|
+
uri: "4da://skills",
|
|
214
|
+
name: "Skill manifest",
|
|
215
|
+
description: "Registry of 4DA skills for Claude Code agent dispatch",
|
|
216
|
+
mimeType: "application/json",
|
|
217
|
+
},
|
|
218
|
+
// Category manifest for tool discovery
|
|
219
|
+
{
|
|
220
|
+
uri: "4da://categories",
|
|
221
|
+
name: "Tool categories",
|
|
222
|
+
description: "Tool groupings by category with tag metadata",
|
|
223
|
+
mimeType: "application/json",
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
return { resources };
|
|
227
|
+
});
|
|
228
|
+
// Read a resource (schema or skill manifest)
|
|
229
|
+
server.setRequestHandler("resources/read", async (request) => {
|
|
230
|
+
const uri = request.params.uri;
|
|
231
|
+
// Handle category manifest
|
|
232
|
+
if (uri === "4da://categories") {
|
|
233
|
+
return {
|
|
234
|
+
contents: [
|
|
235
|
+
{
|
|
236
|
+
uri,
|
|
237
|
+
mimeType: "application/json",
|
|
238
|
+
text: JSON.stringify(getCategoryManifest(), null, 2),
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
};
|
|
233
242
|
}
|
|
234
|
-
|
|
243
|
+
// Handle skill manifest
|
|
244
|
+
if (uri === "4da://skills") {
|
|
245
|
+
const skillsPath = join(homedir(), ".local", "share", "4da", "skills", "registry.json");
|
|
246
|
+
if (!existsSync(skillsPath)) {
|
|
247
|
+
throw new Error("Skill manifest not found. Run 4DA setup to create it.");
|
|
248
|
+
}
|
|
249
|
+
const skillsContent = readFileSync(skillsPath, "utf-8");
|
|
250
|
+
return {
|
|
251
|
+
contents: [
|
|
252
|
+
{
|
|
253
|
+
uri,
|
|
254
|
+
mimeType: "application/json",
|
|
255
|
+
text: skillsContent,
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
// Parse tool name from URI: 4da://schema/{tool_name}
|
|
261
|
+
const match = uri.match(/^4da:\/\/schema\/(.+)$/);
|
|
262
|
+
if (!match) {
|
|
263
|
+
throw new Error(`Invalid resource URI: ${uri}`);
|
|
264
|
+
}
|
|
265
|
+
const toolName = match[1];
|
|
266
|
+
if (!hasToolSchema(toolName)) {
|
|
267
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
268
|
+
}
|
|
269
|
+
const schemaFile = getSchemaFilename(toolName);
|
|
270
|
+
if (!schemaFile) {
|
|
271
|
+
throw new Error(`No schema file for tool: ${toolName}`);
|
|
272
|
+
}
|
|
273
|
+
// Read schema from file
|
|
274
|
+
const schemaPath = join(__dirname, "schemas", schemaFile);
|
|
275
|
+
const schemaContent = readFileSync(schemaPath, "utf-8");
|
|
235
276
|
return {
|
|
236
277
|
contents: [
|
|
237
278
|
{
|
|
238
279
|
uri,
|
|
239
280
|
mimeType: "application/json",
|
|
240
|
-
text:
|
|
241
|
-
},
|
|
242
|
-
],
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
// Parse tool name from URI: 4da://schema/{tool_name}
|
|
246
|
-
const match = uri.match(/^4da:\/\/schema\/(.+)$/);
|
|
247
|
-
if (!match) {
|
|
248
|
-
throw new Error(`Invalid resource URI: ${uri}`);
|
|
249
|
-
}
|
|
250
|
-
const toolName = match[1];
|
|
251
|
-
if (!hasToolSchema(toolName)) {
|
|
252
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
253
|
-
}
|
|
254
|
-
const schemaFile = getSchemaFilename(toolName);
|
|
255
|
-
if (!schemaFile) {
|
|
256
|
-
throw new Error(`No schema file for tool: ${toolName}`);
|
|
257
|
-
}
|
|
258
|
-
// Read schema from file
|
|
259
|
-
const schemaPath = join(__dirname, "schemas", schemaFile);
|
|
260
|
-
const schemaContent = readFileSync(schemaPath, "utf-8");
|
|
261
|
-
return {
|
|
262
|
-
contents: [
|
|
263
|
-
{
|
|
264
|
-
uri,
|
|
265
|
-
mimeType: "application/json",
|
|
266
|
-
text: schemaContent,
|
|
267
|
-
},
|
|
268
|
-
],
|
|
269
|
-
};
|
|
270
|
-
});
|
|
271
|
-
/**
|
|
272
|
-
* Execute a tool
|
|
273
|
-
*/
|
|
274
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
275
|
-
const { name, arguments: args } = request.params;
|
|
276
|
-
try {
|
|
277
|
-
const database = getDatabase();
|
|
278
|
-
return await dispatchTool(name, database, args);
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
282
|
-
return {
|
|
283
|
-
content: [
|
|
284
|
-
{
|
|
285
|
-
type: "text",
|
|
286
|
-
text: JSON.stringify({ error: errorMessage }, null, 2),
|
|
281
|
+
text: schemaContent,
|
|
287
282
|
},
|
|
288
283
|
],
|
|
289
|
-
isError: true,
|
|
290
284
|
};
|
|
291
|
-
}
|
|
292
|
-
|
|
285
|
+
});
|
|
286
|
+
// Execute a tool
|
|
287
|
+
server.setRequestHandler("tools/call", async (request) => {
|
|
288
|
+
const { name, arguments: args } = request.params;
|
|
289
|
+
try {
|
|
290
|
+
const database = getDatabase();
|
|
291
|
+
return await dispatchTool(name, database, args);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
295
|
+
return {
|
|
296
|
+
content: [
|
|
297
|
+
{
|
|
298
|
+
type: "text",
|
|
299
|
+
text: JSON.stringify({ error: errorMessage }, null, 2),
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
isError: true,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
return server;
|
|
307
|
+
}
|
|
293
308
|
// =============================================================================
|
|
294
309
|
// Server Lifecycle
|
|
295
310
|
// =============================================================================
|
|
@@ -297,8 +312,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
297
312
|
* Start the MCP server
|
|
298
313
|
*
|
|
299
314
|
* Supports three modes:
|
|
300
|
-
* (default) stdio transport —
|
|
301
|
-
* --http Streamable HTTP transport
|
|
315
|
+
* (default) stdio transport — serves 2025-era and 2026-07-28 hosts
|
|
316
|
+
* --http Streamable HTTP transport — stateless, both eras
|
|
302
317
|
* --setup Configure editors for 4DA MCP
|
|
303
318
|
*/
|
|
304
319
|
async function main() {
|
|
@@ -353,7 +368,7 @@ async function main() {
|
|
|
353
368
|
const portIndex = args.indexOf("--port");
|
|
354
369
|
const port = portIndex !== -1 ? parseInt(args[portIndex + 1], 10) : 4840;
|
|
355
370
|
const host = args.includes("--host") ? args[args.indexOf("--host") + 1] : "127.0.0.1";
|
|
356
|
-
await startHttpServer(
|
|
371
|
+
await startHttpServer(buildServer, { port, host });
|
|
357
372
|
return;
|
|
358
373
|
}
|
|
359
374
|
// -------------------------------------------------------------------------
|
|
@@ -377,9 +392,14 @@ async function main() {
|
|
|
377
392
|
console.error(` Or run: npx @4da/mcp-server --doctor for diagnostics`);
|
|
378
393
|
console.error(``);
|
|
379
394
|
}
|
|
380
|
-
// Default: stdio
|
|
381
|
-
|
|
382
|
-
|
|
395
|
+
// Default: stdio serving. `serveStdio` owns the era decision per connection —
|
|
396
|
+
// a 2025-era `initialize` opening is served exactly as before; a 2026-07-28
|
|
397
|
+
// `server/discover` opening gets the stateless modern protocol.
|
|
398
|
+
serveStdio(buildServer, {
|
|
399
|
+
onerror: (error) => {
|
|
400
|
+
console.error(`[4DA] stdio serving error: ${error.message}`);
|
|
401
|
+
},
|
|
402
|
+
});
|
|
383
403
|
// Handle graceful shutdown
|
|
384
404
|
process.on("SIGINT", () => {
|
|
385
405
|
console.error("[4DA] Received SIGINT — shutting down gracefully");
|
|
@@ -394,7 +414,7 @@ async function main() {
|
|
|
394
414
|
process.exit(0);
|
|
395
415
|
});
|
|
396
416
|
const toolCount = getSlimToolList().length;
|
|
397
|
-
console.error(`4DA MCP Server
|
|
417
|
+
console.error(`4DA MCP Server v${SERVER_VERSION} started — ${toolCount} tools, stdio transport`);
|
|
398
418
|
console.error(" Use --http for Streamable HTTP, --setup to configure editors, --doctor to check health");
|
|
399
419
|
}
|
|
400
420
|
main().catch((error) => {
|
package/dist/setup.js
CHANGED
|
File without changes
|
package/dist/tool-dispatch.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Map-based dispatch for the 14 active tools.
|
|
5
5
|
* Adding a new tool = add to this map + schema-registry + barrel export.
|
|
6
6
|
*/
|
|
7
|
+
import type { CallToolResult } from "@modelcontextprotocol/server";
|
|
7
8
|
import type { FourDADatabase } from "./db.js";
|
|
8
9
|
/**
|
|
9
10
|
* Executor signature — all tool execute functions follow this shape.
|
|
@@ -14,12 +15,7 @@ export type ToolExecutor = (db: FourDADatabase, params: any) => unknown | Promis
|
|
|
14
15
|
* Dispatch a tool call by name. Awaits uniformly (no-op for sync executors).
|
|
15
16
|
* Returns MCP-formatted response with JSON-serialized result.
|
|
16
17
|
*/
|
|
17
|
-
export declare function dispatchTool(name: string, db: FourDADatabase, args: Record<string, unknown> | undefined): Promise<
|
|
18
|
-
content: Array<{
|
|
19
|
-
type: string;
|
|
20
|
-
text: string;
|
|
21
|
-
}>;
|
|
22
|
-
}>;
|
|
18
|
+
export declare function dispatchTool(name: string, db: FourDADatabase, args: Record<string, unknown> | undefined): Promise<CallToolResult>;
|
|
23
19
|
/** Check if a tool exists in the dispatch map */
|
|
24
20
|
export declare function hasDispatchTool(name: string): boolean;
|
|
25
21
|
/** Get count of registered tool executors */
|
|
@@ -250,7 +250,11 @@ export function executeGetActionableSignals(db, params) {
|
|
|
250
250
|
items = db.getRelevantContent(0.1, undefined, 200, 168); // Try 7 days
|
|
251
251
|
}
|
|
252
252
|
if (items.length === 0) {
|
|
253
|
-
|
|
253
|
+
// Deep 30-day/any-score fallback reaches the stale-epoch tail after a
|
|
254
|
+
// pipeline-version bump. This path trusts stored signal_type/signal_priority
|
|
255
|
+
// verbatim (confidence 0.90 below), so it MUST only see current-version
|
|
256
|
+
// rows — stale persisted signals are claims the live brain no longer makes.
|
|
257
|
+
items = db.getRelevantContent(0.0, undefined, 200, 720, true);
|
|
254
258
|
}
|
|
255
259
|
// Get user's detected tech for cross-referencing
|
|
256
260
|
const context = db.getUserContext(true, false);
|
|
@@ -60,7 +60,10 @@ export function executeGetRelevantContent(db, params) {
|
|
|
60
60
|
items = db.getRelevantContent(minScore, params.source_type, limit, 168);
|
|
61
61
|
}
|
|
62
62
|
if (items.length === 0) {
|
|
63
|
-
|
|
63
|
+
// Deep 30-day fallback at zero floor reaches the stale-epoch tail after a
|
|
64
|
+
// pipeline-version bump — require current-version scores so we never rank
|
|
65
|
+
// numbers the live scoring brain doesn't stand behind.
|
|
66
|
+
items = db.getRelevantContent(0.0, params.source_type, limit, 720, true);
|
|
64
67
|
}
|
|
65
68
|
return items;
|
|
66
69
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@4da/mcp-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"mcpName": "io.github.4DA-Systems/4da-mcp-server",
|
|
5
5
|
"description": "Dependency intelligence for AI coding agents. Live CVE scanning, dependency health, upgrade planning, ecosystem news, decision memory — zero config, privacy-first. 9 tools standalone, 14 total with desktop app.",
|
|
6
6
|
"type": "module",
|
|
@@ -18,6 +18,17 @@
|
|
|
18
18
|
"README.md",
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc && pnpm run copy-schemas",
|
|
23
|
+
"copy-schemas": "node -e \"const fs=require('fs');const path=require('path');const src='src/schemas';const dst='dist/schemas';if(!fs.existsSync(dst))fs.mkdirSync(dst,{recursive:true});fs.readdirSync(src).filter(f=>f.endsWith('.json')).forEach(f=>fs.copyFileSync(path.join(src,f),path.join(dst,f)))\"",
|
|
24
|
+
"dev": "tsc --watch",
|
|
25
|
+
"start": "node dist/index.js",
|
|
26
|
+
"inspect": "npx @modelcontextprotocol/inspector node dist/index.js",
|
|
27
|
+
"setup": "node dist/setup.js",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"prepublishOnly": "pnpm run build"
|
|
31
|
+
},
|
|
21
32
|
"keywords": [
|
|
22
33
|
"mcp",
|
|
23
34
|
"mcp-server",
|
|
@@ -69,8 +80,9 @@
|
|
|
69
80
|
"url": "https://github.com/4DA-Systems/4DA/issues"
|
|
70
81
|
},
|
|
71
82
|
"dependencies": {
|
|
72
|
-
"
|
|
73
|
-
"
|
|
83
|
+
"better-sqlite3": "^12.9.0",
|
|
84
|
+
"@modelcontextprotocol/node": "^2.0.0",
|
|
85
|
+
"@modelcontextprotocol/server": "^2.0.0"
|
|
74
86
|
},
|
|
75
87
|
"devDependencies": {
|
|
76
88
|
"@types/better-sqlite3": "^7.6.11",
|
|
@@ -80,16 +92,18 @@
|
|
|
80
92
|
"vitest": "^4.1.5"
|
|
81
93
|
},
|
|
82
94
|
"engines": {
|
|
83
|
-
"node": ">=
|
|
95
|
+
"node": ">=20.0.0"
|
|
84
96
|
},
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
"pnpm": {
|
|
98
|
+
"onlyBuiltDependencies": [
|
|
99
|
+
"better-sqlite3",
|
|
100
|
+
"esbuild"
|
|
101
|
+
],
|
|
102
|
+
"overrides": {
|
|
103
|
+
"fast-uri": ">=3.1.2",
|
|
104
|
+
"ip-address": "^10.1.1",
|
|
105
|
+
"vite": ">=7.3.5 <8",
|
|
106
|
+
"esbuild": ">=0.28.1"
|
|
107
|
+
}
|
|
94
108
|
}
|
|
95
|
-
}
|
|
109
|
+
}
|