@4da/mcp-server 4.6.6 → 5.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/README.md +19 -1
- package/dist/auth-context.d.ts +49 -0
- package/dist/auth-context.js +72 -0
- package/dist/auth.d.ts +42 -15
- package/dist/auth.js +160 -49
- package/dist/db.d.ts +1 -1
- package/dist/db.js +72 -37
- package/dist/http-transport.d.ts +72 -10
- package/dist/http-transport.js +175 -99
- package/dist/index.d.ts +7 -24
- package/dist/index.js +178 -135
- package/dist/setup.js +0 -0
- package/dist/tool-dispatch.d.ts +6 -6
- package/dist/tool-dispatch.js +6 -0
- package/dist/tools/get-actionable-signals.js +5 -1
- package/dist/tools/get-relevant-content.js +4 -1
- package/package.json +31 -14
package/dist/index.js
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* Claude Desktop, and other MCP hosts. Runs locally; the only data leaving the
|
|
9
9
|
* machine is public package names/versions sent to registries (OSV, npm, etc.).
|
|
10
10
|
*
|
|
11
|
-
* SECURITY:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*
|
|
11
|
+
* SECURITY: stdio serving is unauthenticated by design — the MCP host launches
|
|
12
|
+
* this process and already holds local process rights. The optional --http
|
|
13
|
+
* transport defaults to 127.0.0.1 and applies a Host-header DNS rebinding
|
|
14
|
+
* guard to every request. Binding it anywhere else requires a shared auth
|
|
15
|
+
* secret (MCP_AUTH_SECRET) and then verifies an HMAC-SHA256-signed Bearer
|
|
16
|
+
* token on every request, enforcing the token's role at tool dispatch.
|
|
17
17
|
*
|
|
18
18
|
* 14 tools across 5 categories. Live vulnerability scanning (OSV.dev),
|
|
19
19
|
* ecosystem news, persistent memory, and tech stack awareness for any MCP host.
|
|
@@ -25,10 +25,14 @@
|
|
|
25
25
|
* Decisions (2) — decision memory, alignment checking
|
|
26
26
|
* Agent (1) — cross-session persistent memory
|
|
27
27
|
* Identity (1) — developer DNA profile
|
|
28
|
+
*
|
|
29
|
+
* Protocol: MCP TypeScript SDK v2. stdio serving goes through `serveStdio`,
|
|
30
|
+
* HTTP through `createMcpHandler` — both entries negotiate the era per
|
|
31
|
+
* connection/request, so 2025-era hosts (classic `initialize` handshake) and
|
|
32
|
+
* 2026-07-28 hosts (stateless `server/discover`) are both supported.
|
|
28
33
|
*/
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
34
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
35
|
+
import { Server } from "@modelcontextprotocol/server";
|
|
32
36
|
import { readFileSync, existsSync } from "node:fs";
|
|
33
37
|
import { join, dirname } from "node:path";
|
|
34
38
|
import { fileURLToPath } from "node:url";
|
|
@@ -73,15 +77,6 @@ function resolveProjectDir() {
|
|
|
73
77
|
return dir;
|
|
74
78
|
return process.cwd();
|
|
75
79
|
}
|
|
76
|
-
const server = new Server({
|
|
77
|
-
name: "4da-server",
|
|
78
|
-
version: SERVER_VERSION,
|
|
79
|
-
}, {
|
|
80
|
-
capabilities: {
|
|
81
|
-
tools: { listChanged: true },
|
|
82
|
-
resources: {},
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
80
|
// Database instance (lazy initialized)
|
|
86
81
|
let db = null;
|
|
87
82
|
let liveIntel = null;
|
|
@@ -183,128 +178,136 @@ function getDatabase() {
|
|
|
183
178
|
return db;
|
|
184
179
|
}
|
|
185
180
|
// =============================================================================
|
|
186
|
-
//
|
|
181
|
+
// Server Factory
|
|
187
182
|
// =============================================================================
|
|
188
183
|
/**
|
|
189
|
-
*
|
|
184
|
+
* Build a Server instance with every handler registered.
|
|
190
185
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
186
|
+
* The v2 serving entries take a factory: `serveStdio` pins one instance per
|
|
187
|
+
* connection; stateless HTTP (`createMcpHandler`) builds one per request.
|
|
188
|
+
* Handlers close over the module-level database singleton, so every instance
|
|
189
|
+
* shares the same lazily-initialized database and live-intelligence layer.
|
|
193
190
|
*/
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
*
|
|
203
|
-
* Exposes full tool schemas as MCP Resources for lazy loading.
|
|
204
|
-
* Also exposes skill manifest for agent dispatch.
|
|
205
|
-
*/
|
|
206
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
207
|
-
const resources = [
|
|
208
|
-
...getSchemaResources(),
|
|
209
|
-
// Skill manifest for agent dispatch
|
|
210
|
-
{
|
|
211
|
-
uri: "4da://skills",
|
|
212
|
-
name: "Skill manifest",
|
|
213
|
-
description: "Registry of 4DA skills for Claude Code agent dispatch",
|
|
214
|
-
mimeType: "application/json",
|
|
215
|
-
},
|
|
216
|
-
// Category manifest for tool discovery
|
|
217
|
-
{
|
|
218
|
-
uri: "4da://categories",
|
|
219
|
-
name: "Tool categories",
|
|
220
|
-
description: "Tool groupings by category with tag metadata",
|
|
221
|
-
mimeType: "application/json",
|
|
191
|
+
export function buildServer() {
|
|
192
|
+
const server = new Server({
|
|
193
|
+
name: "4da-server",
|
|
194
|
+
version: SERVER_VERSION,
|
|
195
|
+
}, {
|
|
196
|
+
capabilities: {
|
|
197
|
+
tools: { listChanged: true },
|
|
198
|
+
resources: {},
|
|
222
199
|
},
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
*/
|
|
229
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
230
|
-
const uri = request.params.uri;
|
|
231
|
-
// Handle category manifest
|
|
232
|
-
if (uri === "4da://categories") {
|
|
200
|
+
});
|
|
201
|
+
// List available tools (SLIM): one-liner descriptions only (~500 tokens vs
|
|
202
|
+
// ~4500). Full schemas available via MCP Resources: 4da://schema/{tool_name}
|
|
203
|
+
server.setRequestHandler("tools/list", async () => {
|
|
204
|
+
const database = getDatabase();
|
|
233
205
|
return {
|
|
234
|
-
|
|
235
|
-
{
|
|
236
|
-
uri,
|
|
237
|
-
mimeType: "application/json",
|
|
238
|
-
text: JSON.stringify(getCategoryManifest(), null, 2),
|
|
239
|
-
},
|
|
240
|
-
],
|
|
206
|
+
tools: getSlimToolList(database.isStandalone ? true : undefined),
|
|
241
207
|
};
|
|
242
|
-
}
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
208
|
+
});
|
|
209
|
+
// List schema resources: full tool schemas as MCP Resources for lazy
|
|
210
|
+
// loading, plus the skill and category manifests.
|
|
211
|
+
server.setRequestHandler("resources/list", async () => {
|
|
212
|
+
const resources = [
|
|
213
|
+
...getSchemaResources(),
|
|
214
|
+
// Skill manifest for agent dispatch
|
|
215
|
+
{
|
|
216
|
+
uri: "4da://skills",
|
|
217
|
+
name: "Skill manifest",
|
|
218
|
+
description: "Registry of 4DA skills for Claude Code agent dispatch",
|
|
219
|
+
mimeType: "application/json",
|
|
220
|
+
},
|
|
221
|
+
// Category manifest for tool discovery
|
|
222
|
+
{
|
|
223
|
+
uri: "4da://categories",
|
|
224
|
+
name: "Tool categories",
|
|
225
|
+
description: "Tool groupings by category with tag metadata",
|
|
226
|
+
mimeType: "application/json",
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
return { resources };
|
|
230
|
+
});
|
|
231
|
+
// Read a resource (schema or skill manifest)
|
|
232
|
+
server.setRequestHandler("resources/read", async (request) => {
|
|
233
|
+
const uri = request.params.uri;
|
|
234
|
+
// Handle category manifest
|
|
235
|
+
if (uri === "4da://categories") {
|
|
236
|
+
return {
|
|
237
|
+
contents: [
|
|
238
|
+
{
|
|
239
|
+
uri,
|
|
240
|
+
mimeType: "application/json",
|
|
241
|
+
text: JSON.stringify(getCategoryManifest(), null, 2),
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
// Handle skill manifest
|
|
247
|
+
if (uri === "4da://skills") {
|
|
248
|
+
const skillsPath = join(homedir(), ".local", "share", "4da", "skills", "registry.json");
|
|
249
|
+
if (!existsSync(skillsPath)) {
|
|
250
|
+
throw new Error("Skill manifest not found. Run 4DA setup to create it.");
|
|
251
|
+
}
|
|
252
|
+
const skillsContent = readFileSync(skillsPath, "utf-8");
|
|
253
|
+
return {
|
|
254
|
+
contents: [
|
|
255
|
+
{
|
|
256
|
+
uri,
|
|
257
|
+
mimeType: "application/json",
|
|
258
|
+
text: skillsContent,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// Parse tool name from URI: 4da://schema/{tool_name}
|
|
264
|
+
const match = uri.match(/^4da:\/\/schema\/(.+)$/);
|
|
265
|
+
if (!match) {
|
|
266
|
+
throw new Error(`Invalid resource URI: ${uri}`);
|
|
267
|
+
}
|
|
268
|
+
const toolName = match[1];
|
|
269
|
+
if (!hasToolSchema(toolName)) {
|
|
270
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
248
271
|
}
|
|
249
|
-
const
|
|
272
|
+
const schemaFile = getSchemaFilename(toolName);
|
|
273
|
+
if (!schemaFile) {
|
|
274
|
+
throw new Error(`No schema file for tool: ${toolName}`);
|
|
275
|
+
}
|
|
276
|
+
// Read schema from file
|
|
277
|
+
const schemaPath = join(__dirname, "schemas", schemaFile);
|
|
278
|
+
const schemaContent = readFileSync(schemaPath, "utf-8");
|
|
250
279
|
return {
|
|
251
280
|
contents: [
|
|
252
281
|
{
|
|
253
282
|
uri,
|
|
254
283
|
mimeType: "application/json",
|
|
255
|
-
text:
|
|
284
|
+
text: schemaContent,
|
|
256
285
|
},
|
|
257
286
|
],
|
|
258
287
|
};
|
|
259
|
-
}
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
},
|
|
283
|
-
],
|
|
284
|
-
};
|
|
285
|
-
});
|
|
286
|
-
/**
|
|
287
|
-
* Execute a tool
|
|
288
|
-
*/
|
|
289
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
290
|
-
const { name, arguments: args } = request.params;
|
|
291
|
-
try {
|
|
292
|
-
const database = getDatabase();
|
|
293
|
-
return await dispatchTool(name, database, args);
|
|
294
|
-
}
|
|
295
|
-
catch (error) {
|
|
296
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
297
|
-
return {
|
|
298
|
-
content: [
|
|
299
|
-
{
|
|
300
|
-
type: "text",
|
|
301
|
-
text: JSON.stringify({ error: errorMessage }, null, 2),
|
|
302
|
-
},
|
|
303
|
-
],
|
|
304
|
-
isError: true,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
});
|
|
288
|
+
});
|
|
289
|
+
// Execute a tool
|
|
290
|
+
server.setRequestHandler("tools/call", async (request) => {
|
|
291
|
+
const { name, arguments: args } = request.params;
|
|
292
|
+
try {
|
|
293
|
+
const database = getDatabase();
|
|
294
|
+
return await dispatchTool(name, database, args);
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
298
|
+
return {
|
|
299
|
+
content: [
|
|
300
|
+
{
|
|
301
|
+
type: "text",
|
|
302
|
+
text: JSON.stringify({ error: errorMessage }, null, 2),
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
isError: true,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
return server;
|
|
310
|
+
}
|
|
308
311
|
// =============================================================================
|
|
309
312
|
// Server Lifecycle
|
|
310
313
|
// =============================================================================
|
|
@@ -312,8 +315,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
312
315
|
* Start the MCP server
|
|
313
316
|
*
|
|
314
317
|
* Supports three modes:
|
|
315
|
-
* (default) stdio transport —
|
|
316
|
-
* --http Streamable HTTP transport
|
|
318
|
+
* (default) stdio transport — serves 2025-era and 2026-07-28 hosts
|
|
319
|
+
* --http Streamable HTTP transport — stateless, both eras
|
|
317
320
|
* --setup Configure editors for 4DA MCP
|
|
318
321
|
*/
|
|
319
322
|
async function main() {
|
|
@@ -338,13 +341,23 @@ async function main() {
|
|
|
338
341
|
Options:
|
|
339
342
|
--http Use Streamable HTTP instead of stdio
|
|
340
343
|
--port <number> HTTP port (default: 4840)
|
|
341
|
-
--host <address> HTTP bind address (default: 127.0.0.1)
|
|
344
|
+
--host <address> HTTP bind address (default: 127.0.0.1). A non-loopback
|
|
345
|
+
address exposes every tool to the network and is
|
|
346
|
+
refused unless MCP_AUTH_SECRET is set; authentication
|
|
347
|
+
is then mandatory for every request.
|
|
342
348
|
--setup Detect editors and write MCP config
|
|
343
349
|
--doctor Validate database, bindings, and LLM providers
|
|
344
350
|
|
|
345
351
|
Environment:
|
|
346
352
|
FOURDA_DB_PATH Path to 4DA's SQLite database (auto-detected if omitted)
|
|
347
353
|
FOURDA_OFFLINE Set to "true" to disable all network calls (OSV.dev, HN)
|
|
354
|
+
MCP_AUTH_SECRET Shared secret for verifying relay-issued Bearer tokens
|
|
355
|
+
(HMAC-SHA256). Falls back to JWT_SECRET. Without it, no
|
|
356
|
+
token is accepted.
|
|
357
|
+
MCP_AUTH_REQUIRED Set to "true" to require auth on a loopback --http bind
|
|
358
|
+
(always required on a non-loopback bind)
|
|
359
|
+
MCP_ALLOWED_HOSTS Extra comma-separated hostnames accepted in the Host /
|
|
360
|
+
Origin headers (needed when binding to 0.0.0.0)
|
|
348
361
|
|
|
349
362
|
Works standalone (scans your project on startup) or with the full
|
|
350
363
|
4DA desktop app for content scoring, source monitoring, and more.
|
|
@@ -365,10 +378,35 @@ async function main() {
|
|
|
365
378
|
}
|
|
366
379
|
// HTTP transport mode
|
|
367
380
|
if (args.includes("--http")) {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
381
|
+
const flagValue = (flag) => {
|
|
382
|
+
const i = args.indexOf(flag);
|
|
383
|
+
if (i === -1)
|
|
384
|
+
return undefined;
|
|
385
|
+
const value = args[i + 1];
|
|
386
|
+
// A flag with no value (or followed by another flag) is a typo, not a
|
|
387
|
+
// request to bind everywhere — refuse rather than guess.
|
|
388
|
+
if (value === undefined || value.startsWith("-")) {
|
|
389
|
+
console.error(`[4DA] ${flag} requires a value`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
return value;
|
|
393
|
+
};
|
|
394
|
+
const rawPort = flagValue("--port");
|
|
395
|
+
const port = rawPort === undefined ? 4840 : Number.parseInt(rawPort, 10);
|
|
396
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
397
|
+
console.error(`[4DA] Invalid --port value: ${rawPort}`);
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
const host = flagValue("--host") ?? "127.0.0.1";
|
|
401
|
+
try {
|
|
402
|
+
await startHttpServer(buildServer, { port, host });
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
// Bind-policy refusals carry an actionable message; print it plainly
|
|
406
|
+
// rather than as an unhandled stack trace.
|
|
407
|
+
console.error(`[4DA] ${error instanceof Error ? error.message : String(error)}`);
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
372
410
|
return;
|
|
373
411
|
}
|
|
374
412
|
// -------------------------------------------------------------------------
|
|
@@ -392,9 +430,14 @@ async function main() {
|
|
|
392
430
|
console.error(` Or run: npx @4da/mcp-server --doctor for diagnostics`);
|
|
393
431
|
console.error(``);
|
|
394
432
|
}
|
|
395
|
-
// Default: stdio
|
|
396
|
-
|
|
397
|
-
|
|
433
|
+
// Default: stdio serving. `serveStdio` owns the era decision per connection —
|
|
434
|
+
// a 2025-era `initialize` opening is served exactly as before; a 2026-07-28
|
|
435
|
+
// `server/discover` opening gets the stateless modern protocol.
|
|
436
|
+
serveStdio(buildServer, {
|
|
437
|
+
onerror: (error) => {
|
|
438
|
+
console.error(`[4DA] stdio serving error: ${error.message}`);
|
|
439
|
+
},
|
|
440
|
+
});
|
|
398
441
|
// Handle graceful shutdown
|
|
399
442
|
process.on("SIGINT", () => {
|
|
400
443
|
console.error("[4DA] Received SIGINT — shutting down gracefully");
|
|
@@ -409,7 +452,7 @@ async function main() {
|
|
|
409
452
|
process.exit(0);
|
|
410
453
|
});
|
|
411
454
|
const toolCount = getSlimToolList().length;
|
|
412
|
-
console.error(`4DA MCP Server
|
|
455
|
+
console.error(`4DA MCP Server v${SERVER_VERSION} started — ${toolCount} tools, stdio transport`);
|
|
413
456
|
console.error(" Use --http for Streamable HTTP, --setup to configure editors, --doctor to check health");
|
|
414
457
|
}
|
|
415
458
|
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.
|
|
@@ -13,13 +14,12 @@ export type ToolExecutor = (db: FourDADatabase, params: any) => unknown | Promis
|
|
|
13
14
|
/**
|
|
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.
|
|
17
|
+
*
|
|
18
|
+
* Authenticated HTTP callers are role-checked here — the single choke point
|
|
19
|
+
* every tool call passes through. Stdio callers are unaffected (no auth
|
|
20
|
+
* context; the host already owns the process).
|
|
16
21
|
*/
|
|
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
|
-
}>;
|
|
22
|
+
export declare function dispatchTool(name: string, db: FourDADatabase, args: Record<string, unknown> | undefined): Promise<CallToolResult>;
|
|
23
23
|
/** Check if a tool exists in the dispatch map */
|
|
24
24
|
export declare function hasDispatchTool(name: string): boolean;
|
|
25
25
|
/** Get count of registered tool executors */
|
package/dist/tool-dispatch.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Map-based dispatch for the 14 active tools.
|
|
6
6
|
* Adding a new tool = add to this map + schema-registry + barrel export.
|
|
7
7
|
*/
|
|
8
|
+
import { assertToolPermission } from "./auth-context.js";
|
|
8
9
|
import { executeGetRelevantContent, executeGetContext, executeRecordFeedback, executeGetActionableSignals, executeKnowledgeGaps, executeDecisionMemory, executeCheckDecisionAlignment, executeAgentMemory, executeDeveloperDna, executeWhatShouldIKnow, executeVulnerabilityScan, executeEcosystemPulse, executeDependencyHealth, executeUpgradePlanner, } from "./tools/index.js";
|
|
9
10
|
import { getLiveIntelligence } from "./live-singleton.js";
|
|
10
11
|
/**
|
|
@@ -46,12 +47,17 @@ const FRESHNESS_TOOLS = new Set([
|
|
|
46
47
|
/**
|
|
47
48
|
* Dispatch a tool call by name. Awaits uniformly (no-op for sync executors).
|
|
48
49
|
* Returns MCP-formatted response with JSON-serialized result.
|
|
50
|
+
*
|
|
51
|
+
* Authenticated HTTP callers are role-checked here — the single choke point
|
|
52
|
+
* every tool call passes through. Stdio callers are unaffected (no auth
|
|
53
|
+
* context; the host already owns the process).
|
|
49
54
|
*/
|
|
50
55
|
export async function dispatchTool(name, db, args) {
|
|
51
56
|
const executor = DISPATCH_MAP[name];
|
|
52
57
|
if (!executor) {
|
|
53
58
|
throw new Error(`Unknown tool: ${name}`);
|
|
54
59
|
}
|
|
60
|
+
assertToolPermission(name);
|
|
55
61
|
const result = await executor(db, (args || {}));
|
|
56
62
|
const payload = FRESHNESS_TOOLS.has(name) ? attachFreshness(db, result) : result;
|
|
57
63
|
return {
|
|
@@ -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.1",
|
|
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,10 @@
|
|
|
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",
|
|
86
|
+
"hono": "^4.12.34"
|
|
74
87
|
},
|
|
75
88
|
"devDependencies": {
|
|
76
89
|
"@types/better-sqlite3": "^7.6.11",
|
|
@@ -80,16 +93,20 @@
|
|
|
80
93
|
"vitest": "^4.1.5"
|
|
81
94
|
},
|
|
82
95
|
"engines": {
|
|
83
|
-
"node": ">=
|
|
96
|
+
"node": ">=20.0.0"
|
|
84
97
|
},
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
"
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
"pnpm": {
|
|
99
|
+
"onlyBuiltDependencies": [
|
|
100
|
+
"better-sqlite3",
|
|
101
|
+
"esbuild"
|
|
102
|
+
],
|
|
103
|
+
"overrides": {
|
|
104
|
+
"fast-uri": ">=3.1.2",
|
|
105
|
+
"ip-address": "^10.1.1",
|
|
106
|
+
"vite": ">=7.3.5 <8",
|
|
107
|
+
"esbuild": ">=0.28.1",
|
|
108
|
+
"hono": ">=4.12.34 <5",
|
|
109
|
+
"@hono/node-server": ">=2.0.5 <3"
|
|
110
|
+
}
|
|
94
111
|
}
|
|
95
|
-
}
|
|
112
|
+
}
|