@4da/mcp-server 5.0.2 → 5.0.4
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 -3
- package/dist/auth-context.d.ts +0 -1
- package/dist/auth-context.js +0 -1
- package/dist/auth.d.ts +0 -1
- package/dist/auth.js +0 -1
- package/dist/db.d.ts +17 -2
- package/dist/db.js +104 -67
- package/dist/doctor.d.ts +0 -1
- package/dist/doctor.js +17 -15
- package/dist/embeddings.d.ts +0 -1
- package/dist/embeddings.js +0 -1
- package/dist/http-transport.d.ts +0 -1
- package/dist/http-transport.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +17 -5
- package/dist/live/cache.d.ts +0 -1
- package/dist/live/cache.js +0 -1
- package/dist/live/crates-registry.d.ts +0 -1
- package/dist/live/crates-registry.js +5 -8
- package/dist/live/cvss.d.ts +0 -1
- package/dist/live/cvss.js +0 -1
- package/dist/live/go-registry.d.ts +0 -1
- package/dist/live/go-registry.js +4 -9
- package/dist/live/hn-fetcher.d.ts +0 -1
- package/dist/live/hn-fetcher.js +23 -11
- package/dist/live/http-utils.d.ts +0 -1
- package/dist/live/http-utils.js +0 -1
- package/dist/live/index.d.ts +0 -1
- package/dist/live/index.js +0 -1
- package/dist/live/npm-registry.d.ts +0 -1
- package/dist/live/npm-registry.js +4 -8
- package/dist/live/osv-scanner.d.ts +20 -2
- package/dist/live/osv-scanner.js +82 -9
- package/dist/live/platform.d.ts +0 -1
- package/dist/live/platform.js +0 -1
- package/dist/live/pypi-registry.d.ts +0 -1
- package/dist/live/pypi-registry.js +7 -10
- package/dist/live/rate-limiter.d.ts +0 -1
- package/dist/live/rate-limiter.js +0 -1
- package/dist/live/semver-utils.d.ts +11 -1
- package/dist/live/semver-utils.js +14 -1
- package/dist/live/types.d.ts +0 -1
- package/dist/live/types.js +0 -1
- package/dist/live/version-resolver.d.ts +0 -1
- package/dist/live/version-resolver.js +0 -1
- package/dist/live-singleton.d.ts +0 -1
- package/dist/live-singleton.js +0 -1
- package/dist/project-scanner.d.ts +0 -1
- package/dist/project-scanner.js +0 -1
- package/dist/schema-registry.d.ts +17 -8
- package/dist/schema-registry.js +54 -9
- package/dist/schemas/developer-dna.json +1 -1
- package/dist/schemas/get-context.json +2 -2
- package/dist/schemas/get-relevant-content.json +1 -1
- package/dist/schemas/record-feedback.json +1 -1
- package/dist/setup.d.ts +0 -1
- package/dist/setup.js +0 -1
- package/dist/tool-dispatch.d.ts +0 -1
- package/dist/tool-dispatch.js +21 -2
- package/dist/tools/agent-memory.d.ts +0 -1
- package/dist/tools/agent-memory.js +0 -1
- package/dist/tools/decision-enforcement.d.ts +0 -1
- package/dist/tools/decision-enforcement.js +0 -1
- package/dist/tools/decision-memory.d.ts +0 -1
- package/dist/tools/decision-memory.js +0 -1
- package/dist/tools/decision-recall.d.ts +0 -1
- package/dist/tools/decision-recall.js +0 -1
- package/dist/tools/dependency-health.d.ts +0 -1
- package/dist/tools/dependency-health.js +31 -5
- package/dist/tools/developer-dna.d.ts +3 -3
- package/dist/tools/developer-dna.js +63 -47
- package/dist/tools/ecosystem-pulse.d.ts +7 -2
- package/dist/tools/ecosystem-pulse.js +39 -3
- package/dist/tools/get-actionable-signals.d.ts +0 -1
- package/dist/tools/get-actionable-signals.js +0 -1
- package/dist/tools/get-context.d.ts +0 -1
- package/dist/tools/get-context.js +2 -3
- package/dist/tools/get-relevant-content.d.ts +0 -1
- package/dist/tools/get-relevant-content.js +1 -3
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/tools/knowledge-gaps.d.ts +0 -1
- package/dist/tools/knowledge-gaps.js +79 -17
- package/dist/tools/recall.d.ts +0 -1
- package/dist/tools/recall.js +0 -1
- package/dist/tools/record-feedback.d.ts +1 -2
- package/dist/tools/record-feedback.js +7 -8
- package/dist/tools/upgrade-planner.d.ts +0 -1
- package/dist/tools/upgrade-planner.js +15 -3
- package/dist/tools/vulnerability-scan.d.ts +0 -1
- package/dist/tools/vulnerability-scan.js +0 -1
- package/dist/tools/what-should-i-know.d.ts +0 -1
- package/dist/tools/what-should-i-know.js +6 -3
- package/dist/types.d.ts +2 -3
- package/dist/types.js +0 -1
- package/package.json +37 -11
package/dist/live/index.d.ts
CHANGED
package/dist/live/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Privacy: sends only package names — public manifest data.
|
|
9
9
|
*/
|
|
10
10
|
import { fetchJson, fetchWithTimeout } from "./http-utils.js";
|
|
11
|
-
import { computeSemverDistance,
|
|
11
|
+
import { computeSemverDistance, maxStableSemver } from "./semver-utils.js";
|
|
12
12
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
13
13
|
const NPM_DOWNLOADS_URL = "https://api.npmjs.org/downloads/point/last-week";
|
|
14
14
|
const NPM_TIMEOUT_MS = 8_000;
|
|
@@ -127,13 +127,10 @@ export class NpmRegistry {
|
|
|
127
127
|
return result;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
+
// Semver-max, not last-published: maintenance releases of older lines are
|
|
131
|
+
// published after newer lines (see maxStableSemver).
|
|
130
132
|
function findLatestStable(versions) {
|
|
131
|
-
|
|
132
|
-
if (!isPreRelease(versions[i])) {
|
|
133
|
-
return versions[i];
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return null;
|
|
133
|
+
return maxStableSemver(versions);
|
|
137
134
|
}
|
|
138
135
|
function errorResult(name, currentVersion, isDev, fetchError) {
|
|
139
136
|
return {
|
|
@@ -152,4 +149,3 @@ function errorResult(name, currentVersion, isDev, fetchError) {
|
|
|
152
149
|
fetchError,
|
|
153
150
|
};
|
|
154
151
|
}
|
|
155
|
-
//# sourceMappingURL=npm-registry.js.map
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { LiveCache } from "./cache.js";
|
|
10
10
|
import type { RateLimiter } from "./rate-limiter.js";
|
|
11
|
-
import type { ResolvedDependency, VulnerabilityScanResult } from "./types.js";
|
|
11
|
+
import type { ResolvedDependency, OsvVulnerability, VulnerabilityScanResult } from "./types.js";
|
|
12
12
|
export declare class OsvScanner {
|
|
13
13
|
private cache;
|
|
14
14
|
private rateLimiter;
|
|
@@ -28,4 +28,22 @@ export declare function extractCvssScore(severity: Array<{
|
|
|
28
28
|
type: string;
|
|
29
29
|
score: string;
|
|
30
30
|
}> | undefined): number | null;
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Pick the fix version for the OSV range that actually CONTAINS the installed
|
|
33
|
+
* version — never another release line's fix. The old code returned the first
|
|
34
|
+
* range's first `fixed` event, so a multi-range advisory (e.g. undici fixed at
|
|
35
|
+
* 6.x on the 6 line and 7.x on the 7 line) recommended a DOWNGRADE to 6.x for
|
|
36
|
+
* an installed 7.x.
|
|
37
|
+
*
|
|
38
|
+
* Selection order:
|
|
39
|
+
* 1. The `fixed` of the interval containing installed (introduced <= installed
|
|
40
|
+
* < fixed).
|
|
41
|
+
* 2. If that interval has no fix (or none contains installed), the smallest
|
|
42
|
+
* published fix strictly ABOVE installed across all ranges.
|
|
43
|
+
* 3. If the installed version is unparseable, the highest published fix (it
|
|
44
|
+
* resolves the advisory regardless of release line).
|
|
45
|
+
* 4. Otherwise null — omitting the recommendation beats emitting a downgrade.
|
|
46
|
+
*
|
|
47
|
+
* Hard guard: the returned version is never below the installed version.
|
|
48
|
+
*/
|
|
49
|
+
export declare function extractFixedVersion(affected: OsvVulnerability["affected"] | undefined, packageName: string, ecosystem: string, installedVersion: string | null): string | null;
|
package/dist/live/osv-scanner.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { fetchWithTimeout, fetchJson } from "./http-utils.js";
|
|
11
11
|
import { cvssBaseScore } from "./cvss.js";
|
|
12
|
+
import { compareSemver, maxSemver, parseSemver } from "./semver-utils.js";
|
|
12
13
|
const OSV_BATCH_URL = "https://api.osv.dev/v1/querybatch";
|
|
13
14
|
const OSV_VULN_URL = "https://api.osv.dev/v1/vulns/";
|
|
14
15
|
const OSV_TIMEOUT_MS = 15_000;
|
|
@@ -174,7 +175,7 @@ export class OsvScanner {
|
|
|
174
175
|
}
|
|
175
176
|
function mapVulnerability(vuln, dep) {
|
|
176
177
|
const { severity, cvssScore } = deriveSeverity(vuln);
|
|
177
|
-
const fixedVersion = extractFixedVersion(vuln.affected, dep.name, dep.ecosystem);
|
|
178
|
+
const fixedVersion = extractFixedVersion(vuln.affected, dep.name, dep.ecosystem, dep.version);
|
|
178
179
|
return {
|
|
179
180
|
package: dep.name,
|
|
180
181
|
currentVersion: dep.version || "unknown",
|
|
@@ -260,19 +261,91 @@ function cvssToSeverity(score) {
|
|
|
260
261
|
return "low";
|
|
261
262
|
return "unknown";
|
|
262
263
|
}
|
|
263
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Pick the fix version for the OSV range that actually CONTAINS the installed
|
|
266
|
+
* version — never another release line's fix. The old code returned the first
|
|
267
|
+
* range's first `fixed` event, so a multi-range advisory (e.g. undici fixed at
|
|
268
|
+
* 6.x on the 6 line and 7.x on the 7 line) recommended a DOWNGRADE to 6.x for
|
|
269
|
+
* an installed 7.x.
|
|
270
|
+
*
|
|
271
|
+
* Selection order:
|
|
272
|
+
* 1. The `fixed` of the interval containing installed (introduced <= installed
|
|
273
|
+
* < fixed).
|
|
274
|
+
* 2. If that interval has no fix (or none contains installed), the smallest
|
|
275
|
+
* published fix strictly ABOVE installed across all ranges.
|
|
276
|
+
* 3. If the installed version is unparseable, the highest published fix (it
|
|
277
|
+
* resolves the advisory regardless of release line).
|
|
278
|
+
* 4. Otherwise null — omitting the recommendation beats emitting a downgrade.
|
|
279
|
+
*
|
|
280
|
+
* Hard guard: the returned version is never below the installed version.
|
|
281
|
+
*/
|
|
282
|
+
export function extractFixedVersion(affected, packageName, ecosystem, installedVersion) {
|
|
264
283
|
if (!affected)
|
|
265
284
|
return null;
|
|
285
|
+
const intervals = collectVulnerableIntervals(affected, packageName, ecosystem);
|
|
286
|
+
const fixes = intervals
|
|
287
|
+
.map((i) => i.fixed)
|
|
288
|
+
.filter((f) => f !== null);
|
|
289
|
+
if (fixes.length === 0)
|
|
290
|
+
return null;
|
|
291
|
+
if (installedVersion === null || parseSemver(installedVersion) === null) {
|
|
292
|
+
// Can't place the installed version on a release line — recommend the
|
|
293
|
+
// highest published fix so the upgrade resolves the advisory either way.
|
|
294
|
+
return maxSemver(fixes);
|
|
295
|
+
}
|
|
296
|
+
// The interval whose release line the installed version sits on. `compareSemver`
|
|
297
|
+
// returns 0 for unparseable bounds (e.g. introduced "0" = since inception),
|
|
298
|
+
// which keeps those intervals eligible on the introduced side only.
|
|
299
|
+
const containing = intervals.find((i) => compareSemver(installedVersion, i.introduced) >= 0 &&
|
|
300
|
+
(i.fixed === null || compareSemver(installedVersion, i.fixed) < 0));
|
|
301
|
+
if (containing?.fixed &&
|
|
302
|
+
compareSemver(containing.fixed, installedVersion) > 0) {
|
|
303
|
+
return containing.fixed;
|
|
304
|
+
}
|
|
305
|
+
// No fix on the installed line (open interval, or installed already past
|
|
306
|
+
// every containing range): smallest fix strictly above installed, or nothing.
|
|
307
|
+
const above = fixes.filter((f) => compareSemver(f, installedVersion) > 0);
|
|
308
|
+
if (above.length === 0)
|
|
309
|
+
return null;
|
|
310
|
+
return above.reduce((min, v) => (compareSemver(v, min) < 0 ? v : min), above[0]);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Flatten the matching package's version ranges into [introduced, fixed)
|
|
314
|
+
* intervals. OSV sorts events within a range; each `introduced` opens an
|
|
315
|
+
* interval and the next `fixed` closes it. GIT ranges hold commit hashes,
|
|
316
|
+
* not versions, and are skipped.
|
|
317
|
+
*/
|
|
318
|
+
function collectVulnerableIntervals(affected, packageName, ecosystem) {
|
|
319
|
+
const intervals = [];
|
|
266
320
|
for (const a of affected) {
|
|
267
|
-
if (a.package.name
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
321
|
+
if (a.package.name !== packageName || a.package.ecosystem !== ecosystem)
|
|
322
|
+
continue;
|
|
323
|
+
for (const range of a.ranges || []) {
|
|
324
|
+
if (range.type === "GIT")
|
|
325
|
+
continue;
|
|
326
|
+
let open = null;
|
|
327
|
+
for (const event of range.events || []) {
|
|
328
|
+
if (event.introduced !== undefined) {
|
|
329
|
+
if (open)
|
|
330
|
+
intervals.push(open);
|
|
331
|
+
open = { introduced: event.introduced, fixed: null };
|
|
332
|
+
}
|
|
333
|
+
else if (event.fixed !== undefined) {
|
|
334
|
+
if (open) {
|
|
335
|
+
open.fixed = event.fixed;
|
|
336
|
+
intervals.push(open);
|
|
337
|
+
open = null;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
// `fixed` with no preceding `introduced` — treat as vulnerable
|
|
341
|
+
// since inception ("0" = OSV's since-the-beginning sentinel).
|
|
342
|
+
intervals.push({ introduced: "0", fixed: event.fixed });
|
|
343
|
+
}
|
|
272
344
|
}
|
|
273
345
|
}
|
|
346
|
+
if (open)
|
|
347
|
+
intervals.push(open);
|
|
274
348
|
}
|
|
275
349
|
}
|
|
276
|
-
return
|
|
350
|
+
return intervals;
|
|
277
351
|
}
|
|
278
|
-
//# sourceMappingURL=osv-scanner.js.map
|
package/dist/live/platform.d.ts
CHANGED
|
@@ -29,4 +29,3 @@ export declare function hostTarget(): HostTarget;
|
|
|
29
29
|
export declare function targetActiveOnHost(target: string | null | undefined, host?: HostTarget): boolean;
|
|
30
30
|
/** Evaluate a cfg predicate body (already unwrapped from `cfg( ... )`). */
|
|
31
31
|
export declare function evalCfg(expr: string, host?: HostTarget): boolean;
|
|
32
|
-
//# sourceMappingURL=platform.d.ts.map
|
package/dist/live/platform.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Privacy: sends only package names -- public manifest data.
|
|
9
9
|
*/
|
|
10
10
|
import { fetchJson } from "./http-utils.js";
|
|
11
|
-
import { computeSemverDistance, isPreRelease } from "./semver-utils.js";
|
|
11
|
+
import { computeSemverDistance, isPreRelease, maxStableSemver } from "./semver-utils.js";
|
|
12
12
|
const PYPI_BASE_URL = "https://pypi.org/pypi";
|
|
13
13
|
const PYPI_TIMEOUT_MS = 8_000;
|
|
14
14
|
const PYPI_CACHE_TTL = 86_400; // 24 hours
|
|
@@ -83,17 +83,15 @@ export class PyPIRegistry {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
function findLatestStable(versions, releases) {
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
// Semver-max among eligible versions, not last-listed (see maxStableSemver).
|
|
87
|
+
const eligible = versions.filter((v) => {
|
|
88
88
|
if (isPreRelease(v))
|
|
89
|
-
|
|
89
|
+
return false;
|
|
90
90
|
// Skip if all files for this version are yanked
|
|
91
91
|
const files = releases[v];
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
return null;
|
|
92
|
+
return !(files && files.length > 0 && files.every((f) => f.yanked));
|
|
93
|
+
});
|
|
94
|
+
return maxStableSemver(eligible);
|
|
97
95
|
}
|
|
98
96
|
function getLastPublished(files) {
|
|
99
97
|
if (!files || files.length === 0)
|
|
@@ -139,4 +137,3 @@ function errorResult(name, currentVersion, isDev, fetchError) {
|
|
|
139
137
|
fetchError,
|
|
140
138
|
};
|
|
141
139
|
}
|
|
142
|
-
//# sourceMappingURL=pypi-registry.js.map
|
|
@@ -10,4 +10,14 @@ export declare function isPreRelease(version: string): boolean;
|
|
|
10
10
|
export declare function compareSemver(a: string, b: string): number;
|
|
11
11
|
/** Highest version (by MAJOR.MINOR.PATCH) from a list, or null if empty. */
|
|
12
12
|
export declare function maxSemver(versions: string[]): string | null;
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Highest NON-prerelease version by semver from a list.
|
|
15
|
+
*
|
|
16
|
+
* Registries list versions in publish order, and maintenance releases for an
|
|
17
|
+
* older line land AFTER newer lines (React published 19.0.8 after 19.2.x; rsa
|
|
18
|
+
* published 0.9.10 after 0.10.0-rc.*). Taking the "last stable entry" therefore
|
|
19
|
+
* reported an older line as the latest stable — which surfaced as a bogus
|
|
20
|
+
* stable version in dependency_health and a literal DOWNGRADE recommendation
|
|
21
|
+
* in upgrade_planner. Order-independent max fixes the class.
|
|
22
|
+
*/
|
|
23
|
+
export declare function maxStableSemver(versions: string[]): string | null;
|
|
@@ -49,4 +49,17 @@ export function maxSemver(versions) {
|
|
|
49
49
|
return null;
|
|
50
50
|
return versions.reduce((max, v) => (compareSemver(v, max) > 0 ? v : max), versions[0]);
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Highest NON-prerelease version by semver from a list.
|
|
54
|
+
*
|
|
55
|
+
* Registries list versions in publish order, and maintenance releases for an
|
|
56
|
+
* older line land AFTER newer lines (React published 19.0.8 after 19.2.x; rsa
|
|
57
|
+
* published 0.9.10 after 0.10.0-rc.*). Taking the "last stable entry" therefore
|
|
58
|
+
* reported an older line as the latest stable — which surfaced as a bogus
|
|
59
|
+
* stable version in dependency_health and a literal DOWNGRADE recommendation
|
|
60
|
+
* in upgrade_planner. Order-independent max fixes the class.
|
|
61
|
+
*/
|
|
62
|
+
export function maxStableSemver(versions) {
|
|
63
|
+
const stable = versions.filter((v) => parseSemver(v) !== null && !isPreRelease(v));
|
|
64
|
+
return maxSemver(stable);
|
|
65
|
+
}
|
package/dist/live/types.d.ts
CHANGED
package/dist/live/types.js
CHANGED
|
@@ -23,4 +23,3 @@ export declare function resolveVersions(cwd: string, deps: string[], devDeps: st
|
|
|
23
23
|
* Direct dependency health and upgrade planning continue to use resolveVersions.
|
|
24
24
|
*/
|
|
25
25
|
export declare function resolveAuditVersions(cwd: string, deps: string[], devDeps: string[], language: string, targets?: Record<string, string>): ResolvedDependency[];
|
|
26
|
-
//# sourceMappingURL=version-resolver.d.ts.map
|
package/dist/live-singleton.d.ts
CHANGED
package/dist/live-singleton.js
CHANGED
package/dist/project-scanner.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema Registry for Dynamic Context Discovery
|
|
3
3
|
*
|
|
4
|
-
* Reduces tool listing from ~4500 tokens to ~
|
|
5
|
-
* - Returning
|
|
4
|
+
* Reduces tool listing from ~4500 tokens to ~1300 tokens by:
|
|
5
|
+
* - Returning one-liner summaries in list_tools
|
|
6
|
+
* - Serving the REAL inputSchema only for tools with REQUIRED parameters
|
|
7
|
+
* (AD-032): a client cannot construct a valid call to those without the
|
|
8
|
+
* schema, and most clients never read MCP Resources — so a slim
|
|
9
|
+
* `{type:"object"}` made required-param tools effectively uncallable.
|
|
10
|
+
* All-optional tools stay slim (`{}` is a valid call) with the full
|
|
11
|
+
* schema still available as a Resource.
|
|
6
12
|
* - Storing full schemas as MCP Resources (lazy-loaded)
|
|
7
13
|
*
|
|
8
14
|
* Also provides category/tag metadata for tool discovery and filtering.
|
|
@@ -39,16 +45,20 @@ export interface ToolRegistryEntry {
|
|
|
39
45
|
* 14 tools total: 9 standalone + 5 full-mode.
|
|
40
46
|
*/
|
|
41
47
|
export declare const TOOL_REGISTRY: Record<string, ToolRegistryEntry>;
|
|
48
|
+
/** An inputSchema as tools/list serves it — the MCP Tool contract requires
|
|
49
|
+
* the literal `type: "object"` at the root. */
|
|
50
|
+
export type ToolInputSchema = {
|
|
51
|
+
type: "object";
|
|
52
|
+
} & Record<string, unknown>;
|
|
42
53
|
/**
|
|
43
|
-
* Get
|
|
44
|
-
*
|
|
54
|
+
* Get the tool list for the tools/list response: slim summaries throughout,
|
|
55
|
+
* real inputSchema for required-param tools, `{type:"object"}` otherwise
|
|
56
|
+
* (full schema via resources).
|
|
45
57
|
*/
|
|
46
58
|
export declare function getSlimToolList(standaloneOnly?: boolean): Array<{
|
|
47
59
|
name: string;
|
|
48
60
|
description: string;
|
|
49
|
-
inputSchema:
|
|
50
|
-
type: "object";
|
|
51
|
-
};
|
|
61
|
+
inputSchema: ToolInputSchema;
|
|
52
62
|
annotations: ToolAnnotations;
|
|
53
63
|
}>;
|
|
54
64
|
/**
|
|
@@ -77,4 +87,3 @@ export declare function getCategoryManifest(): {
|
|
|
77
87
|
};
|
|
78
88
|
/** Find tools matching any of the given tags */
|
|
79
89
|
export declare function getToolsByTags(tags: string[]): string[];
|
|
80
|
-
//# sourceMappingURL=schema-registry.d.ts.map
|
package/dist/schema-registry.js
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Schema Registry for Dynamic Context Discovery
|
|
4
4
|
*
|
|
5
|
-
* Reduces tool listing from ~4500 tokens to ~
|
|
6
|
-
* - Returning
|
|
5
|
+
* Reduces tool listing from ~4500 tokens to ~1300 tokens by:
|
|
6
|
+
* - Returning one-liner summaries in list_tools
|
|
7
|
+
* - Serving the REAL inputSchema only for tools with REQUIRED parameters
|
|
8
|
+
* (AD-032): a client cannot construct a valid call to those without the
|
|
9
|
+
* schema, and most clients never read MCP Resources — so a slim
|
|
10
|
+
* `{type:"object"}` made required-param tools effectively uncallable.
|
|
11
|
+
* All-optional tools stay slim (`{}` is a valid call) with the full
|
|
12
|
+
* schema still available as a Resource.
|
|
7
13
|
* - Storing full schemas as MCP Resources (lazy-loaded)
|
|
8
14
|
*
|
|
9
15
|
* Also provides category/tag metadata for tool discovery and filtering.
|
|
@@ -11,6 +17,9 @@
|
|
|
11
17
|
* Full schemas available at: 4da://schema/{tool_name}
|
|
12
18
|
* Category manifest at: 4da://categories
|
|
13
19
|
*/
|
|
20
|
+
import { readFileSync } from "node:fs";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { dirname, join } from "node:path";
|
|
14
23
|
/**
|
|
15
24
|
* Slim tool registry — one-liner descriptions + category/tag metadata.
|
|
16
25
|
* Full schemas are stored in schemas/*.json and exposed as MCP Resources.
|
|
@@ -61,7 +70,7 @@ export const TOOL_REGISTRY = {
|
|
|
61
70
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
62
71
|
},
|
|
63
72
|
get_context: {
|
|
64
|
-
summary: "What 4DA knows about the user: role, tech stack, interests, and
|
|
73
|
+
summary: "What 4DA knows about the user: role, tech stack, interests, exclusions, and ACE-detected context. Call FIRST when you need to know what the user works on before answering or recommending.",
|
|
65
74
|
schemaFile: "get-context.json",
|
|
66
75
|
category: "intelligence",
|
|
67
76
|
tags: ["context", "interests", "tech-stack", "profile"],
|
|
@@ -93,10 +102,10 @@ export const TOOL_REGISTRY = {
|
|
|
93
102
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
94
103
|
},
|
|
95
104
|
record_feedback: {
|
|
96
|
-
summary: "Record click/save/dismiss on a content item
|
|
105
|
+
summary: "Record click/save/dismiss on a content item as explicit interaction history. Call AFTER the user reacts to a surfaced item (opens, saves, or dismisses it).",
|
|
97
106
|
schemaFile: "record-feedback.json",
|
|
98
107
|
category: "intelligence",
|
|
99
|
-
tags: ["feedback", "
|
|
108
|
+
tags: ["feedback", "history", "save", "dismiss"],
|
|
100
109
|
standalone: false,
|
|
101
110
|
annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: false, idempotentHint: false },
|
|
102
111
|
},
|
|
@@ -136,9 +145,46 @@ export const TOOL_REGISTRY = {
|
|
|
136
145
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
137
146
|
},
|
|
138
147
|
};
|
|
148
|
+
/** Schemas live beside this module in both layouts: src/schemas (vitest) and
|
|
149
|
+
* dist/schemas (compiled — populated by the copy-schemas build step). */
|
|
150
|
+
const SCHEMAS_DIR = join(dirname(fileURLToPath(import.meta.url)), "schemas");
|
|
151
|
+
/** Lazy cache: tool name → its real inputSchema when the schema declares
|
|
152
|
+
* required parameters, else null (tool stays slim). */
|
|
153
|
+
const requiredParamSchemas = new Map();
|
|
139
154
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
155
|
+
* The tools/list discoverability line (AD-032): a tool whose schema declares
|
|
156
|
+
* REQUIRED parameters serves its real inputSchema — `{type:"object"}` hid the
|
|
157
|
+
* required params from every client that never reads MCP Resources, making
|
|
158
|
+
* those tools fail on first call (GPT adversarial audit, finding 7). Tools
|
|
159
|
+
* whose parameters are all optional keep the slim schema: `{}` is a valid
|
|
160
|
+
* call, and the full schema stays one Resource read away. Fail-open: an
|
|
161
|
+
* unreadable or malformed schema file falls back to slim rather than
|
|
162
|
+
* breaking tools/list.
|
|
163
|
+
*/
|
|
164
|
+
function inputSchemaIfRequired(name, schemaFile) {
|
|
165
|
+
if (!requiredParamSchemas.has(name)) {
|
|
166
|
+
let loaded = null;
|
|
167
|
+
try {
|
|
168
|
+
const parsed = JSON.parse(readFileSync(join(SCHEMAS_DIR, schemaFile), "utf-8"));
|
|
169
|
+
const schema = parsed.inputSchema;
|
|
170
|
+
if (schema &&
|
|
171
|
+
schema.type === "object" &&
|
|
172
|
+
Array.isArray(schema.required) &&
|
|
173
|
+
schema.required.length > 0) {
|
|
174
|
+
loaded = schema;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
loaded = null;
|
|
179
|
+
}
|
|
180
|
+
requiredParamSchemas.set(name, loaded);
|
|
181
|
+
}
|
|
182
|
+
return requiredParamSchemas.get(name) ?? null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get the tool list for the tools/list response: slim summaries throughout,
|
|
186
|
+
* real inputSchema for required-param tools, `{type:"object"}` otherwise
|
|
187
|
+
* (full schema via resources).
|
|
142
188
|
*/
|
|
143
189
|
export function getSlimToolList(standaloneOnly) {
|
|
144
190
|
return Object.entries(TOOL_REGISTRY)
|
|
@@ -146,7 +192,7 @@ export function getSlimToolList(standaloneOnly) {
|
|
|
146
192
|
.map(([name, info]) => ({
|
|
147
193
|
name,
|
|
148
194
|
description: info.summary,
|
|
149
|
-
inputSchema: { type: "object" },
|
|
195
|
+
inputSchema: inputSchemaIfRequired(name, info.schemaFile) ?? { type: "object" },
|
|
150
196
|
annotations: info.annotations,
|
|
151
197
|
}));
|
|
152
198
|
}
|
|
@@ -200,4 +246,3 @@ export function getToolsByTags(tags) {
|
|
|
200
246
|
.filter(([, entry]) => entry.tags.some((t) => tagSet.has(t.toLowerCase())))
|
|
201
247
|
.map(([name]) => name);
|
|
202
248
|
}
|
|
203
|
-
//# sourceMappingURL=schema-registry.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "developer_dna",
|
|
3
|
-
"description": "Export your Developer DNA — a comprehensive profile of your tech identity built from project scans,
|
|
3
|
+
"description": "Export your Developer DNA — a comprehensive profile of your tech identity built from project scans, explicit interests, and local interaction history. Includes primary stack, adjacent tech, top dependencies, engaged topics, blind spots, source engagement rates, and aggregate stats. Call when the user asks for a summary of their developer profile, identity, or tech fingerprint.",
|
|
4
4
|
"inputSchema": {
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "get_context",
|
|
3
|
-
"description": "Get the user's context - what 4DA knows about them.\n\nReturns information about:\n- Static Identity: User-declared role, tech stack, domains, interests, and exclusions\n- ACE Context (optional): Auto-detected technologies and active topics from recent activity\n- Learned Preferences (optional):
|
|
3
|
+
"description": "Get the user's context - what 4DA knows about them.\n\nReturns information about:\n- Static Identity: User-declared role, tech stack, domains, interests, and exclusions\n- ACE Context (optional): Auto-detected technologies and active topics from recent activity\n- Learned Preferences (optional): retained compatibility field; implicit preference learning no longer contributes to scoring\n\nCall FIRST when you need to understand what the user works on or cares about before answering or recommending.",
|
|
4
4
|
"inputSchema": {
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"include_learned": {
|
|
12
12
|
"type": "boolean",
|
|
13
|
-
"description": "Include learned
|
|
13
|
+
"description": "Include the retained learned-preferences compatibility field. Default: true"
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "get_relevant_content",
|
|
3
|
-
"description": "Get filtered relevant content from 4DA's personalized feed.\n\nReturns content items that match the user's interests, tech stack, and
|
|
3
|
+
"description": "Get filtered relevant content from 4DA's personalized feed.\n\nReturns content items that match the user's explicit interests, tech stack, and ACE-detected context.\nItems are scored based on:\n- Explicit interests declared by the user\n- Tech stack and domains\n- ACE-detected context (recent files, projects, git activity)\n\nIMPORTANT for autonomous use: each item carries an `evidence_class` describing how it is grounded. The relevance_score float is NOT comparable across classes — gate actions on evidence_class, not the number:\n- \"osv_verified\": a security advisory matching one of the user's INSTALLED dependencies — safe to act on\n- \"dependency_grounded\": matched one of the user's declared dependencies — trustworthy\n- \"semantic_only\": topical/embedding relevance with NO dependency edge — surface to the human, verify before acting\n- \"keyword_heuristic\": standalone-mode keyword score (no Rust pipeline) — weakest signal\n\nCall when the user asks what to read, what is relevant to them, or for content on a topic.",
|
|
4
4
|
"inputSchema": {
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "record_feedback",
|
|
3
|
-
"description": "Record user
|
|
3
|
+
"description": "Record explicit user interaction history for a content item.\n\nFeedback actions:\n- \"click\": User clicked/opened the item\n- \"save\": User saved/bookmarked the item\n- \"dismiss\": User dismissed the item\n- \"mark_irrelevant\": User marked item as not relevant\n\nThis records what happened; it does not train content preferences (interaction counts only graduate scoring's bootstrap gate). Call after the user reacts to a surfaced item (opens, saves, or dismisses it).",
|
|
4
4
|
"inputSchema": {
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
package/dist/setup.d.ts
CHANGED
package/dist/setup.js
CHANGED
package/dist/tool-dispatch.d.ts
CHANGED
|
@@ -24,4 +24,3 @@ export declare function dispatchTool(name: string, db: FourDADatabase, args: Rec
|
|
|
24
24
|
export declare function hasDispatchTool(name: string): boolean;
|
|
25
25
|
/** Get count of registered tool executors */
|
|
26
26
|
export declare function getDispatchToolCount(): number;
|
|
27
|
-
//# sourceMappingURL=tool-dispatch.d.ts.map
|
package/dist/tool-dispatch.js
CHANGED
|
@@ -58,7 +58,27 @@ export async function dispatchTool(name, db, args) {
|
|
|
58
58
|
throw new Error(`Unknown tool: ${name}`);
|
|
59
59
|
}
|
|
60
60
|
assertToolPermission(name);
|
|
61
|
-
|
|
61
|
+
// BUSY/LOCKED retry at the single choke point every tool passes through.
|
|
62
|
+
// The desktop engine writes to the same database every 30 minutes; a read
|
|
63
|
+
// that collides with its transaction deserves one backoff retry rather than
|
|
64
|
+
// an immediate error JSON. (db.queryWithRetry existed for this but had no
|
|
65
|
+
// production caller — it is sync and the executors are async, so the retry
|
|
66
|
+
// lives here.)
|
|
67
|
+
const toolArgs = (args || {});
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
result = await executor(db, toolArgs);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
const code = error.code;
|
|
74
|
+
if (code === "SQLITE_BUSY" || code === "SQLITE_LOCKED") {
|
|
75
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
76
|
+
result = await executor(db, toolArgs);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
62
82
|
const payload = FRESHNESS_TOOLS.has(name) ? attachFreshness(db, result) : result;
|
|
63
83
|
return {
|
|
64
84
|
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
@@ -94,4 +114,3 @@ export function hasDispatchTool(name) {
|
|
|
94
114
|
export function getDispatchToolCount() {
|
|
95
115
|
return Object.keys(DISPATCH_MAP).length;
|
|
96
116
|
}
|
|
97
|
-
//# sourceMappingURL=tool-dispatch.js.map
|
|
@@ -72,4 +72,3 @@ export declare const checkDecisionAlignmentTool: {
|
|
|
72
72
|
};
|
|
73
73
|
export declare function executeCheckDecisionAlignment(db: FourDADatabase, params: CheckDecisionAlignmentParams): AlignmentResult | Promise<AlignmentResult>;
|
|
74
74
|
export {};
|
|
75
|
-
//# sourceMappingURL=decision-enforcement.d.ts.map
|