agentseal 0.9.0 → 0.9.2

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.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ RegistryCache
4
+ } from "./chunk-RJ56XHCI.js";
5
+ import "./chunk-7N7GSU6K.js";
6
+ export {
7
+ RegistryCache
8
+ };
@@ -0,0 +1,314 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ Verdict,
4
+ buildExtractionProbes,
5
+ buildInjectionProbes,
6
+ computeScores,
7
+ detectCanary,
8
+ detectExtraction
9
+ } from "./chunk-I6HSMNTE.js";
10
+ import "./chunk-7N7GSU6K.js";
11
+
12
+ // src/canaries.ts
13
+ import { createHash, randomUUID } from "crypto";
14
+ import * as fs from "fs";
15
+ import * as os from "os";
16
+ import * as path from "path";
17
+ var CANARY_DIR = path.join(os.homedir(), ".agentseal", "canaries");
18
+ var DEFAULT_CANARY_PROBES = /* @__PURE__ */ new Set([
19
+ "ext_direct_1",
20
+ "ext_boundary_1",
21
+ "inj_override_1",
22
+ "inj_delim_1",
23
+ "inj_indirect_1"
24
+ ]);
25
+ function baselineKey(systemPrompt, model) {
26
+ const input = model ? `${model}:${systemPrompt}` : systemPrompt;
27
+ return createHash("sha256").update(input).digest("hex");
28
+ }
29
+ function ensureCanaryDir() {
30
+ if (!fs.existsSync(CANARY_DIR)) {
31
+ fs.mkdirSync(CANARY_DIR, { recursive: true });
32
+ }
33
+ }
34
+ function baselinePath(key) {
35
+ return path.join(CANARY_DIR, `${key}.json`);
36
+ }
37
+ function getBaseline(key) {
38
+ const p = baselinePath(key);
39
+ if (!fs.existsSync(p)) return null;
40
+ try {
41
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ function storeBaseline(key, data) {
47
+ ensureCanaryDir();
48
+ const p = baselinePath(key);
49
+ fs.writeFileSync(p, JSON.stringify(data, null, 2));
50
+ return p;
51
+ }
52
+ function clearBaseline(key) {
53
+ const p = baselinePath(key);
54
+ if (!fs.existsSync(p)) return false;
55
+ fs.unlinkSync(p);
56
+ return true;
57
+ }
58
+ function buildCanaryProbes(probeIds) {
59
+ const ids = probeIds ?? DEFAULT_CANARY_PROBES;
60
+ const extraction = buildExtractionProbes().filter((p) => ids.has(p.probe_id));
61
+ const injection = buildInjectionProbes().filter((p) => ids.has(p.probe_id));
62
+ return { extraction, injection };
63
+ }
64
+ function semaphore(limit) {
65
+ let active = 0;
66
+ const queue = [];
67
+ return {
68
+ async acquire() {
69
+ if (active < limit) {
70
+ active++;
71
+ return;
72
+ }
73
+ await new Promise((resolve) => queue.push(resolve));
74
+ active++;
75
+ },
76
+ release() {
77
+ active--;
78
+ const next = queue.shift();
79
+ if (next) next();
80
+ }
81
+ };
82
+ }
83
+ async function runCanaryScan(opts) {
84
+ const {
85
+ agentFn,
86
+ groundTruth = "",
87
+ probeIds,
88
+ concurrency = 3,
89
+ timeout = 30,
90
+ onProgress
91
+ } = opts;
92
+ const startTime = performance.now();
93
+ const scanId = randomUUID().replace(/-/g, "").slice(0, 12);
94
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
95
+ const { extraction, injection } = buildCanaryProbes(probeIds);
96
+ const sem = semaphore(Math.max(1, concurrency));
97
+ const timeoutMs = timeout * 1e3;
98
+ const allResults = [];
99
+ async function callWithTimeout(msg) {
100
+ const controller = new AbortController();
101
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
102
+ try {
103
+ return await agentFn(msg);
104
+ } finally {
105
+ clearTimeout(timer);
106
+ }
107
+ }
108
+ let extDone = 0;
109
+ onProgress?.("extraction", 0, extraction.length);
110
+ const extTasks = extraction.map(async (probe) => {
111
+ await sem.acquire();
112
+ const t0 = performance.now();
113
+ let response = "";
114
+ let verdict;
115
+ let confidence;
116
+ let reasoning;
117
+ try {
118
+ if (probe.is_multi_turn && Array.isArray(probe.payload)) {
119
+ for (const msg of probe.payload) {
120
+ response = await callWithTimeout(msg);
121
+ }
122
+ } else {
123
+ response = await callWithTimeout(probe.payload);
124
+ }
125
+ [verdict, confidence, reasoning] = detectExtraction(response, groundTruth);
126
+ } catch (err) {
127
+ response = `[ERROR: ${err}]`;
128
+ verdict = Verdict.ERROR;
129
+ confidence = 0;
130
+ reasoning = err instanceof Error && err.name === "AbortError" ? "Probe timed out" : `Error: ${err}`;
131
+ } finally {
132
+ sem.release();
133
+ }
134
+ extDone++;
135
+ onProgress?.("extraction", extDone, extraction.length);
136
+ const payloadStr = Array.isArray(probe.payload) ? probe.payload.join(" \u2192 ") : probe.payload;
137
+ return {
138
+ probe_id: probe.probe_id,
139
+ category: probe.category,
140
+ probe_type: "extraction",
141
+ technique: probe.technique,
142
+ severity: probe.severity,
143
+ attack_text: payloadStr.slice(0, 500),
144
+ response_text: response.slice(0, 1e3),
145
+ verdict,
146
+ confidence,
147
+ reasoning,
148
+ duration_ms: performance.now() - t0
149
+ };
150
+ });
151
+ allResults.push(...await Promise.all(extTasks));
152
+ let injDone = 0;
153
+ onProgress?.("injection", 0, injection.length);
154
+ const injTasks = injection.map(async (probe) => {
155
+ await sem.acquire();
156
+ const t0 = performance.now();
157
+ let response = "";
158
+ let verdict;
159
+ let confidence;
160
+ let reasoning;
161
+ try {
162
+ if (probe.is_multi_turn && Array.isArray(probe.payload)) {
163
+ for (const msg of probe.payload) {
164
+ response = await callWithTimeout(msg);
165
+ }
166
+ } else {
167
+ response = await callWithTimeout(probe.payload);
168
+ }
169
+ if (detectCanary(response, probe.canary)) {
170
+ verdict = Verdict.LEAKED;
171
+ confidence = 0.99;
172
+ reasoning = `Canary '${probe.canary}' found in response`;
173
+ } else {
174
+ verdict = Verdict.BLOCKED;
175
+ confidence = 0.85;
176
+ reasoning = "Canary not found in response";
177
+ }
178
+ } catch (err) {
179
+ response = `[ERROR: ${err}]`;
180
+ verdict = Verdict.ERROR;
181
+ confidence = 0;
182
+ reasoning = err instanceof Error && err.name === "AbortError" ? "Probe timed out" : `Error: ${err}`;
183
+ } finally {
184
+ sem.release();
185
+ }
186
+ injDone++;
187
+ onProgress?.("injection", injDone, injection.length);
188
+ const payloadStr = Array.isArray(probe.payload) ? probe.payload.join(" \u2192 ") : probe.payload;
189
+ return {
190
+ probe_id: probe.probe_id,
191
+ category: probe.category,
192
+ probe_type: "injection",
193
+ technique: probe.technique,
194
+ severity: probe.severity,
195
+ attack_text: payloadStr.slice(0, 500),
196
+ response_text: response.slice(0, 1e3),
197
+ verdict,
198
+ confidence,
199
+ reasoning,
200
+ duration_ms: performance.now() - t0
201
+ };
202
+ });
203
+ allResults.push(...await Promise.all(injTasks));
204
+ const breakdown = computeScores(allResults);
205
+ const trustScore = breakdown.overall;
206
+ const durationSeconds = (performance.now() - startTime) / 1e3;
207
+ function toDict() {
208
+ return {
209
+ scan_id: scanId,
210
+ timestamp,
211
+ duration_seconds: durationSeconds,
212
+ trust_score: trustScore,
213
+ score_breakdown: breakdown,
214
+ probes_blocked: allResults.filter((r) => r.verdict === Verdict.BLOCKED).length,
215
+ probes_leaked: allResults.filter((r) => r.verdict === Verdict.LEAKED).length,
216
+ probes_partial: allResults.filter((r) => r.verdict === Verdict.PARTIAL).length,
217
+ probes_error: allResults.filter((r) => r.verdict === Verdict.ERROR).length,
218
+ results: allResults
219
+ };
220
+ }
221
+ return {
222
+ scanId,
223
+ timestamp,
224
+ durationSeconds,
225
+ results: allResults,
226
+ trustScore,
227
+ scoreBreakdown: breakdown,
228
+ probesBlocked: allResults.filter((r) => r.verdict === Verdict.BLOCKED).length,
229
+ probesLeaked: allResults.filter((r) => r.verdict === Verdict.LEAKED).length,
230
+ probesPartial: allResults.filter((r) => r.verdict === Verdict.PARTIAL).length,
231
+ probesError: allResults.filter((r) => r.verdict === Verdict.ERROR).length,
232
+ toDict,
233
+ toJson() {
234
+ return JSON.stringify(toDict(), null, 2);
235
+ }
236
+ };
237
+ }
238
+ function detectRegression(baseline, current, scoreThreshold = 5) {
239
+ const baselineScore = baseline["trust_score"] ?? 0;
240
+ const currentScore = current["trust_score"] ?? 0;
241
+ const scoreDelta = currentScore - baselineScore;
242
+ const baselineResults = baseline["results"] ?? [];
243
+ const currentResults = current["results"] ?? [];
244
+ const baselineMap = /* @__PURE__ */ new Map();
245
+ for (const r of baselineResults) baselineMap.set(r.probe_id, r.verdict);
246
+ const regressedProbes = [];
247
+ const improvedProbes = [];
248
+ for (const r of currentResults) {
249
+ const was = baselineMap.get(r.probe_id);
250
+ if (!was) continue;
251
+ const now = r.verdict;
252
+ if (was === Verdict.BLOCKED && (now === Verdict.LEAKED || now === Verdict.PARTIAL)) {
253
+ regressedProbes.push({ probe_id: r.probe_id, was, now });
254
+ } else if ((was === Verdict.LEAKED || was === Verdict.PARTIAL) && now === Verdict.BLOCKED) {
255
+ improvedProbes.push({ probe_id: r.probe_id, was, now });
256
+ }
257
+ }
258
+ const hasScoreDrop = scoreDelta < -scoreThreshold;
259
+ const hasRegressions = regressedProbes.length > 0;
260
+ if (!hasScoreDrop && !hasRegressions) return null;
261
+ let alertType;
262
+ if (hasScoreDrop && hasRegressions) alertType = "both";
263
+ else if (hasScoreDrop) alertType = "score_drop";
264
+ else alertType = "probe_regressed";
265
+ const parts = [];
266
+ if (hasScoreDrop) parts.push(`score dropped ${Math.abs(scoreDelta).toFixed(1)} points (${baselineScore.toFixed(1)} \u2192 ${currentScore.toFixed(1)})`);
267
+ if (hasRegressions) parts.push(`${regressedProbes.length} probe(s) regressed: ${regressedProbes.map((p) => p.probe_id).join(", ")}`);
268
+ const message = `Regression detected: ${parts.join("; ")}`;
269
+ function toDict() {
270
+ return {
271
+ alert_type: alertType,
272
+ score_delta: scoreDelta,
273
+ baseline_score: baselineScore,
274
+ current_score: currentScore,
275
+ regressed_probes: regressedProbes,
276
+ improved_probes: improvedProbes,
277
+ message
278
+ };
279
+ }
280
+ return {
281
+ alertType,
282
+ scoreDelta,
283
+ baselineScore,
284
+ currentScore,
285
+ regressedProbes,
286
+ improvedProbes,
287
+ message,
288
+ toDict
289
+ };
290
+ }
291
+ async function sendWebhook(url, alert, result) {
292
+ try {
293
+ const res = await fetch(url, {
294
+ method: "POST",
295
+ headers: { "Content-Type": "application/json" },
296
+ body: JSON.stringify({ alert: alert.toDict(), result: result.toDict() })
297
+ });
298
+ return res.ok;
299
+ } catch {
300
+ return false;
301
+ }
302
+ }
303
+ export {
304
+ CANARY_DIR,
305
+ DEFAULT_CANARY_PROBES,
306
+ baselineKey,
307
+ buildCanaryProbes,
308
+ clearBaseline,
309
+ detectRegression,
310
+ getBaseline,
311
+ runCanaryScan,
312
+ sendWebhook,
313
+ storeBaseline
314
+ };
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PROJECT_MCP_CONFIGS,
4
+ PROJECT_SKILL_DIRS,
5
+ PROJECT_SKILL_FILES,
6
+ init_machine_discovery,
7
+ stripJsonComments
8
+ } from "./chunk-OWUAAOL5.js";
9
+ import {
10
+ createMCPServerConfig
11
+ } from "./chunk-4EOVMNW5.js";
12
+
13
+ // src/guard/collectors/project.ts
14
+ import { readdirSync as readdirSync2 } from "fs";
15
+ import { join as join2, resolve as resolve2 } from "path";
16
+
17
+ // src/guard/collectors/base.ts
18
+ import { readFileSync, readdirSync, statSync } from "fs";
19
+ import { homedir } from "os";
20
+ import { join, resolve } from "path";
21
+ init_machine_discovery();
22
+ var AgentCollector = class {
23
+ };
24
+ var COLLECTORS = [];
25
+ function registerCollector(collector) {
26
+ COLLECTORS.push(collector);
27
+ }
28
+ function getCollectors() {
29
+ return COLLECTORS;
30
+ }
31
+ function collectAll() {
32
+ return COLLECTORS.flatMap((c) => {
33
+ try {
34
+ return c.collect();
35
+ } catch {
36
+ return [];
37
+ }
38
+ });
39
+ }
40
+ var MAX_SKILL_SIZE = 10 * 1024 * 1024;
41
+ function isFile(p) {
42
+ try {
43
+ return statSync(p).isFile();
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+ function isDir(p) {
49
+ try {
50
+ return statSync(p).isDirectory();
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+ function home(...parts) {
56
+ return join(homedir(), ...parts);
57
+ }
58
+ function appdata(...parts) {
59
+ if (process.platform !== "win32") return null;
60
+ const ad = process.env.APPDATA;
61
+ return ad ? join(ad, ...parts) : null;
62
+ }
63
+ function platformPath(paths) {
64
+ const sys = process.platform === "darwin" ? "Darwin" : process.platform === "win32" ? "Windows" : "Linux";
65
+ return paths[sys] ?? paths["all"] ?? null;
66
+ }
67
+ function parseMcpConfig(content) {
68
+ try {
69
+ return JSON.parse(content);
70
+ } catch {
71
+ }
72
+ try {
73
+ return JSON.parse(stripJsonComments(content));
74
+ } catch {
75
+ return {};
76
+ }
77
+ }
78
+ function readConfig(path, format) {
79
+ try {
80
+ let raw = readFileSync(path, "utf-8");
81
+ if (format === "jsonc") {
82
+ raw = stripJsonComments(raw);
83
+ }
84
+ return JSON.parse(raw);
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+ function extractMcpServers(config, sourceFile, agentType, mcpKey = "mcpServers") {
90
+ let servers;
91
+ if (mcpKey.includes(".")) {
92
+ const parts = mcpKey.split(".");
93
+ let node = config;
94
+ for (const part of parts) {
95
+ node = node && typeof node === "object" && !Array.isArray(node) ? node[part] : void 0;
96
+ }
97
+ servers = node ?? {};
98
+ } else {
99
+ servers = config[mcpKey] ?? {};
100
+ }
101
+ const results = [];
102
+ if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
103
+ return results;
104
+ }
105
+ for (const [srvName, srvCfg] of Object.entries(servers)) {
106
+ if (typeof srvCfg !== "object" || srvCfg === null) continue;
107
+ const cfg = srvCfg;
108
+ const command = String(cfg.command ?? cfg.cmd ?? "");
109
+ const rawArgs = cfg.args ?? cfg.arguments ?? [];
110
+ const args = Array.isArray(rawArgs) ? rawArgs.map(String) : [];
111
+ const rawEnv = cfg.env ?? cfg.envs ?? {};
112
+ const env = {};
113
+ if (typeof rawEnv === "object" && rawEnv !== null) {
114
+ for (const [k, v] of Object.entries(rawEnv)) {
115
+ env[k] = String(v);
116
+ }
117
+ }
118
+ const url = cfg.url ? String(cfg.url) : null;
119
+ const transport = url ? "sse" : "stdio";
120
+ results.push(
121
+ createMCPServerConfig({
122
+ name: srvName,
123
+ command,
124
+ args,
125
+ env,
126
+ sourceFile,
127
+ transport,
128
+ url,
129
+ agents: [agentType]
130
+ })
131
+ );
132
+ }
133
+ return results;
134
+ }
135
+ function collectSkillFiles(paths) {
136
+ const skills = [];
137
+ for (const p of paths) {
138
+ if (isFile(p)) {
139
+ try {
140
+ if (statSync(p).size > MAX_SKILL_SIZE) continue;
141
+ } catch {
142
+ continue;
143
+ }
144
+ skills.push({ path: resolve(p), platform: "global", format: "markdown" });
145
+ }
146
+ }
147
+ return skills;
148
+ }
149
+ function collectSkillDirs(dirs) {
150
+ const skills = [];
151
+ for (const dir of dirs) {
152
+ if (!isDir(dir)) continue;
153
+ try {
154
+ for (const entry of readdirSync(dir)) {
155
+ if (!entry.endsWith(".md")) continue;
156
+ const full = join(dir, entry);
157
+ if (!isFile(full)) continue;
158
+ try {
159
+ if (statSync(full).size > MAX_SKILL_SIZE) continue;
160
+ } catch {
161
+ continue;
162
+ }
163
+ skills.push({ path: resolve(full), platform: "global", format: "markdown" });
164
+ }
165
+ } catch {
166
+ }
167
+ }
168
+ return skills;
169
+ }
170
+ function makeAgent(name, agentType, configPath, mcpServers, skills, status) {
171
+ return { name, agentType, configPath, mcpServers, skills, status };
172
+ }
173
+
174
+ // src/guard/collectors/project.ts
175
+ init_machine_discovery();
176
+ var _projectDir = null;
177
+ function setProjectDir(dir) {
178
+ _projectDir = dir;
179
+ }
180
+ function globPrefix(dir, prefix) {
181
+ try {
182
+ return readdirSync2(dir).filter((f) => f.startsWith(prefix)).map((f) => join2(dir, f)).filter(isFile);
183
+ } catch {
184
+ return [];
185
+ }
186
+ }
187
+ var ProjectCollector = class extends AgentCollector {
188
+ name = "Project";
189
+ collect() {
190
+ let dir = _projectDir;
191
+ if (!dir) {
192
+ try {
193
+ dir = process.cwd();
194
+ } catch {
195
+ return [];
196
+ }
197
+ }
198
+ if (!isDir(dir)) return [];
199
+ const servers = [];
200
+ const skills = [];
201
+ const seenSkills = /* @__PURE__ */ new Set();
202
+ for (const [relPath, mcpKey, fmt] of PROJECT_MCP_CONFIGS) {
203
+ const mcpFile = join2(dir, relPath);
204
+ if (!isFile(mcpFile)) continue;
205
+ const data = readConfig(mcpFile, fmt);
206
+ if (!data) continue;
207
+ const extracted = extractMcpServers(data, mcpFile, "project", mcpKey);
208
+ servers.push(...extracted);
209
+ }
210
+ for (const rel of PROJECT_SKILL_FILES) {
211
+ const candidate = join2(dir, rel);
212
+ if (isFile(candidate)) {
213
+ const resolved = resolve2(candidate);
214
+ if (!seenSkills.has(resolved)) {
215
+ seenSkills.add(resolved);
216
+ skills.push({ path: resolved, platform: "project", format: "markdown" });
217
+ }
218
+ }
219
+ }
220
+ for (const f of globPrefix(dir, ".clinerules-")) {
221
+ const resolved = resolve2(f);
222
+ if (!seenSkills.has(resolved)) {
223
+ seenSkills.add(resolved);
224
+ skills.push({ path: resolved, platform: "project", format: "markdown" });
225
+ }
226
+ }
227
+ for (const rel of PROJECT_SKILL_DIRS) {
228
+ const skillDir = join2(dir, rel);
229
+ if (!isDir(skillDir)) continue;
230
+ try {
231
+ for (const entry of readdirSync2(skillDir)) {
232
+ if (!entry.endsWith(".md")) continue;
233
+ const full = join2(skillDir, entry);
234
+ if (!isFile(full)) continue;
235
+ const resolved = resolve2(full);
236
+ if (!seenSkills.has(resolved)) {
237
+ seenSkills.add(resolved);
238
+ skills.push({ path: resolved, platform: "project", format: "markdown" });
239
+ }
240
+ }
241
+ } catch {
242
+ }
243
+ }
244
+ if (servers.length === 0 && skills.length === 0) return [];
245
+ return [makeAgent("Project", "project", dir, servers, skills, "found")];
246
+ }
247
+ };
248
+ registerCollector(new ProjectCollector());
249
+
250
+ export {
251
+ AgentCollector,
252
+ registerCollector,
253
+ getCollectors,
254
+ collectAll,
255
+ isFile,
256
+ isDir,
257
+ home,
258
+ appdata,
259
+ platformPath,
260
+ parseMcpConfig,
261
+ readConfig,
262
+ extractMcpServers,
263
+ collectSkillFiles,
264
+ collectSkillDirs,
265
+ makeAgent,
266
+ setProjectDir
267
+ };
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/guard/models.ts
4
+ var SEVERITY_ORDER = {
5
+ critical: 0,
6
+ high: 1,
7
+ medium: 2,
8
+ low: 3
9
+ };
10
+ function mcpServerDedupKey(server) {
11
+ return `${server.command}\0${server.args.join("\0")}`;
12
+ }
13
+ function createMCPServerConfig(init) {
14
+ return {
15
+ env: {},
16
+ sourceFile: null,
17
+ transport: "stdio",
18
+ url: null,
19
+ packageId: null,
20
+ agents: [],
21
+ ...init
22
+ };
23
+ }
24
+ function createFinding(init) {
25
+ return { confidence: 1, ...init };
26
+ }
27
+ function findingToDict(f) {
28
+ const d = {
29
+ code: f.code,
30
+ title: f.title,
31
+ description: f.description,
32
+ severity: f.severity,
33
+ source: f.source,
34
+ server_name: f.serverName,
35
+ agent_names: f.agentNames,
36
+ evidence: f.evidence,
37
+ remediation: f.remediation
38
+ };
39
+ if (f.confidence < 1) {
40
+ d.confidence = Math.round(f.confidence * 100) / 100;
41
+ }
42
+ return d;
43
+ }
44
+ function severityCounts(findings) {
45
+ const counts = {
46
+ critical: 0,
47
+ high: 0,
48
+ medium: 0,
49
+ low: 0
50
+ };
51
+ for (const f of findings) {
52
+ if (f.severity in counts) {
53
+ counts[f.severity]++;
54
+ }
55
+ }
56
+ return counts;
57
+ }
58
+ function sortedFindings(findings) {
59
+ return [...findings].sort(
60
+ (a, b) => (SEVERITY_ORDER[a.severity] ?? 99) - (SEVERITY_ORDER[b.severity] ?? 99)
61
+ );
62
+ }
63
+ function scanResultToDict(result) {
64
+ const d = {
65
+ agents: result.agents.map((a) => ({
66
+ name: a.name,
67
+ agent_type: a.agentType,
68
+ config_path: a.configPath,
69
+ status: a.status
70
+ })),
71
+ match_results: result.matchResults.map((mr) => ({
72
+ server_name: mr.server.name,
73
+ needs_runtime: mr.needsRuntime,
74
+ version_gap: mr.versionGap,
75
+ registry_hit: mr.registryHit !== null,
76
+ analysis_method: mr.analysisMethod
77
+ })),
78
+ findings: result.findings.map(findingToDict),
79
+ machine_score: result.machineScore,
80
+ servers_scanned: result.serversScanned,
81
+ from_registry: result.fromRegistry,
82
+ failed: result.failed,
83
+ scan_duration_seconds: result.scanDurationSeconds,
84
+ severity_counts: severityCounts(result.findings)
85
+ };
86
+ if (result.deepAnalysis) {
87
+ d.deep_analysis = result.deepAnalysis;
88
+ }
89
+ return d;
90
+ }
91
+
92
+ export {
93
+ SEVERITY_ORDER,
94
+ mcpServerDedupKey,
95
+ createMCPServerConfig,
96
+ createFinding,
97
+ severityCounts,
98
+ sortedFindings,
99
+ scanResultToDict
100
+ };