@zivis/mcp 0.1.10 → 0.1.12
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/dist/http-probe/capture.d.ts +51 -0
- package/dist/http-probe/capture.js +96 -0
- package/dist/http-probe/compare.d.ts +43 -0
- package/dist/http-probe/compare.js +117 -0
- package/dist/http-probe/index.d.ts +2 -0
- package/dist/http-probe/index.js +2 -0
- package/dist/pattern-packs/zivis-public-0.2.0/manifest.json +1 -1
- package/dist/redact.d.ts +30 -0
- package/dist/redact.js +164 -0
- package/dist/scanners/index.d.ts +3 -0
- package/dist/scanners/index.js +2 -0
- package/dist/scanners/normalize.d.ts +6 -0
- package/dist/scanners/normalize.js +181 -0
- package/dist/scanners/run.d.ts +27 -0
- package/dist/scanners/run.js +142 -0
- package/dist/scanners/types.d.ts +40 -0
- package/dist/scanners/types.js +1 -0
- package/dist/server.js +0 -5
- package/dist/tools/create-diagram.d.ts +3 -73
- package/dist/tools/create-diagram.js +8 -100
- package/dist/tools/get-diagram.d.ts +1 -1
- package/dist/tools/get-diagram.js +2 -52
- package/dist/tools/list-diagrams.d.ts +1 -1
- package/dist/tools/list-diagrams.js +2 -3
- package/dist/tools/manage-diagram.d.ts +1 -64
- package/dist/tools/manage-diagram.js +2 -211
- package/dist/tools/update-mermaid-source.d.ts +1 -1
- package/dist/tools/update-mermaid-source.js +1 -3
- package/package.json +5 -2
- package/dist/tools/generate-diagram.d.ts +0 -30
- package/dist/tools/generate-diagram.js +0 -161
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { redactString } from "../redact.js";
|
|
2
|
+
function redact(text) {
|
|
3
|
+
if (text === undefined || text === null || text === "")
|
|
4
|
+
return undefined;
|
|
5
|
+
return redactString(String(text)).text;
|
|
6
|
+
}
|
|
7
|
+
function normalizeSeverity(raw) {
|
|
8
|
+
const s = String(raw ?? "").toLowerCase();
|
|
9
|
+
if (s.startsWith("crit"))
|
|
10
|
+
return "critical";
|
|
11
|
+
if (s === "high" || s === "error")
|
|
12
|
+
return "high";
|
|
13
|
+
if (s === "medium" || s === "moderate" || s === "warning" || s === "warn")
|
|
14
|
+
return "medium";
|
|
15
|
+
if (s === "low")
|
|
16
|
+
return "low";
|
|
17
|
+
if (s === "info" || s === "informational" || s === "unknown" || s === "none" || s === "") {
|
|
18
|
+
return s === "" || s === "unknown" || s === "none" ? "unknown" : "info";
|
|
19
|
+
}
|
|
20
|
+
return "unknown";
|
|
21
|
+
}
|
|
22
|
+
function asArray(v) {
|
|
23
|
+
return Array.isArray(v) ? v : [];
|
|
24
|
+
}
|
|
25
|
+
function lead(base) {
|
|
26
|
+
return { lane: "lead", ...base };
|
|
27
|
+
}
|
|
28
|
+
export function normalizeGitleaks(parsed) {
|
|
29
|
+
const findings = asArray(parsed);
|
|
30
|
+
return findings.map((f) => {
|
|
31
|
+
const tags = Array.isArray(f.Tags) ? f.Tags : [];
|
|
32
|
+
const sevTag = tags.find((t) => ["critical", "high", "medium", "low"].includes(String(t).toLowerCase()));
|
|
33
|
+
return lead({
|
|
34
|
+
source: "gitleaks",
|
|
35
|
+
rule_id: f.RuleID ?? "gitleaks-secret",
|
|
36
|
+
title: f.Description ?? f.RuleID ?? "Potential secret",
|
|
37
|
+
severity: sevTag ? normalizeSeverity(sevTag) : "high",
|
|
38
|
+
location: { file: f.File, start_line: f.StartLine, end_line: f.EndLine },
|
|
39
|
+
excerpt: redact(f.Match),
|
|
40
|
+
metadata: tags.length ? { tags } : undefined,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function normalizeTrivy(parsed) {
|
|
45
|
+
const results = asArray(parsed?.Results);
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const r of results) {
|
|
48
|
+
for (const v of asArray(r.Vulnerabilities)) {
|
|
49
|
+
out.push(lead({
|
|
50
|
+
source: "trivy",
|
|
51
|
+
rule_id: v.VulnerabilityID ?? "trivy-vuln",
|
|
52
|
+
title: v.Title ?? `${v.PkgName ?? "dependency"} ${v.VulnerabilityID ?? ""}`.trim(),
|
|
53
|
+
severity: normalizeSeverity(v.Severity),
|
|
54
|
+
location: { file: r.Target },
|
|
55
|
+
package: v.PkgName,
|
|
56
|
+
cwe: Array.isArray(v.CweIDs) && v.CweIDs.length ? v.CweIDs : undefined,
|
|
57
|
+
references: dedupeRefs(v.PrimaryURL, v.References),
|
|
58
|
+
metadata: {
|
|
59
|
+
installed_version: v.InstalledVersion,
|
|
60
|
+
fixed_version: v.FixedVersion,
|
|
61
|
+
},
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
for (const s of asArray(r.Secrets)) {
|
|
65
|
+
out.push(lead({
|
|
66
|
+
source: "trivy",
|
|
67
|
+
rule_id: s.RuleID ?? "trivy-secret",
|
|
68
|
+
title: s.Title ?? "Potential secret",
|
|
69
|
+
severity: normalizeSeverity(s.Severity ?? "high"),
|
|
70
|
+
location: { file: r.Target, start_line: s.StartLine, end_line: s.EndLine },
|
|
71
|
+
excerpt: redact(s.Match),
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
for (const m of asArray(r.Misconfigurations)) {
|
|
75
|
+
out.push(lead({
|
|
76
|
+
source: "trivy",
|
|
77
|
+
rule_id: m.ID ?? "trivy-misconfig",
|
|
78
|
+
title: m.Title ?? m.ID ?? "Misconfiguration",
|
|
79
|
+
severity: normalizeSeverity(m.Severity),
|
|
80
|
+
location: { file: r.Target },
|
|
81
|
+
references: dedupeRefs(m.PrimaryURL, m.References),
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
export function normalizeSemgrep(parsed) {
|
|
88
|
+
const results = asArray(parsed?.results);
|
|
89
|
+
return results.map((r) => {
|
|
90
|
+
const meta = r.extra?.metadata ?? {};
|
|
91
|
+
return lead({
|
|
92
|
+
source: "semgrep",
|
|
93
|
+
rule_id: r.check_id ?? "semgrep-rule",
|
|
94
|
+
title: r.extra?.message ?? r.check_id ?? "Semgrep match",
|
|
95
|
+
severity: normalizeSeverity(r.extra?.severity),
|
|
96
|
+
location: { file: r.path, start_line: r.start?.line, end_line: r.end?.line },
|
|
97
|
+
cwe: toStringArray(meta.cwe),
|
|
98
|
+
owasp: toStringArray(meta.owasp),
|
|
99
|
+
references: Array.isArray(meta.references) ? meta.references : undefined,
|
|
100
|
+
excerpt: redact(r.extra?.lines),
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
export function normalizeOsvScanner(parsed) {
|
|
105
|
+
const results = asArray(parsed?.results);
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const src of results) {
|
|
108
|
+
const file = src.source?.path;
|
|
109
|
+
for (const pkg of asArray(src.packages)) {
|
|
110
|
+
const pkgName = pkg.package?.name;
|
|
111
|
+
const groupSeverity = new Map();
|
|
112
|
+
for (const g of asArray(pkg.groups)) {
|
|
113
|
+
for (const id of asArray(g.ids)) {
|
|
114
|
+
if (g.max_severity)
|
|
115
|
+
groupSeverity.set(id, g.max_severity);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const v of asArray(pkg.vulnerabilities)) {
|
|
119
|
+
out.push(lead({
|
|
120
|
+
source: "osv-scanner",
|
|
121
|
+
rule_id: v.id ?? "osv-vuln",
|
|
122
|
+
title: v.summary ?? `${pkgName ?? "dependency"} ${v.id ?? ""}`.trim(),
|
|
123
|
+
severity: cvssScoreToSeverity(groupSeverity.get(v.id ?? "")),
|
|
124
|
+
location: { file },
|
|
125
|
+
package: pkgName,
|
|
126
|
+
references: asArray(v.references)
|
|
127
|
+
.map((r) => r.url)
|
|
128
|
+
.filter((u) => typeof u === "string"),
|
|
129
|
+
metadata: { version: pkg.package?.version, ecosystem: pkg.package?.ecosystem },
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
function toStringArray(v) {
|
|
137
|
+
if (v === undefined || v === null)
|
|
138
|
+
return undefined;
|
|
139
|
+
const arr = Array.isArray(v) ? v : [v];
|
|
140
|
+
const out = arr.map((x) => String(x)).filter(Boolean);
|
|
141
|
+
return out.length ? out : undefined;
|
|
142
|
+
}
|
|
143
|
+
function dedupeRefs(primary, refs) {
|
|
144
|
+
const set = new Set();
|
|
145
|
+
if (primary)
|
|
146
|
+
set.add(primary);
|
|
147
|
+
for (const r of Array.isArray(refs) ? refs : [])
|
|
148
|
+
if (r)
|
|
149
|
+
set.add(r);
|
|
150
|
+
return set.size ? [...set] : undefined;
|
|
151
|
+
}
|
|
152
|
+
function cvssScoreToSeverity(score) {
|
|
153
|
+
if (!score)
|
|
154
|
+
return "unknown";
|
|
155
|
+
const n = Number.parseFloat(score);
|
|
156
|
+
if (Number.isNaN(n))
|
|
157
|
+
return "unknown";
|
|
158
|
+
if (n >= 9.0)
|
|
159
|
+
return "critical";
|
|
160
|
+
if (n >= 7.0)
|
|
161
|
+
return "high";
|
|
162
|
+
if (n >= 4.0)
|
|
163
|
+
return "medium";
|
|
164
|
+
if (n > 0)
|
|
165
|
+
return "low";
|
|
166
|
+
return "unknown";
|
|
167
|
+
}
|
|
168
|
+
export function normalizeScannerOutput(source, parsed) {
|
|
169
|
+
switch (source) {
|
|
170
|
+
case "gitleaks":
|
|
171
|
+
return normalizeGitleaks(parsed);
|
|
172
|
+
case "trivy":
|
|
173
|
+
return normalizeTrivy(parsed);
|
|
174
|
+
case "semgrep":
|
|
175
|
+
return normalizeSemgrep(parsed);
|
|
176
|
+
case "osv-scanner":
|
|
177
|
+
return normalizeOsvScanner(parsed);
|
|
178
|
+
default:
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LocalScanReport, ScannerId, ScannerRunResult } from "./types.js";
|
|
2
|
+
export declare const SUPPORTED_SCANNERS: readonly ScannerId[];
|
|
3
|
+
export declare const SCANNER_BINARIES: Record<ScannerId, string>;
|
|
4
|
+
export interface ScannerCommand {
|
|
5
|
+
bin: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function scannerCommand(source: ScannerId, dir: string, outPath: string): ScannerCommand;
|
|
9
|
+
export interface ScannerExecEnv {
|
|
10
|
+
isAvailable(bin: string): Promise<boolean>;
|
|
11
|
+
execute(cmd: ScannerCommand, opts: {
|
|
12
|
+
timeoutMs: number;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
reportText: string | null;
|
|
15
|
+
error?: string;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
export declare const defaultExecEnv: ScannerExecEnv;
|
|
19
|
+
export interface RunLocalScannersOptions {
|
|
20
|
+
sources?: ScannerId[];
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
env?: ScannerExecEnv;
|
|
23
|
+
tmpDir?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function runOneScanner(source: ScannerId, dir: string, opts?: RunLocalScannersOptions): Promise<ScannerRunResult>;
|
|
26
|
+
export declare function runLocalScanners(dir: string, opts?: RunLocalScannersOptions): Promise<LocalScanReport>;
|
|
27
|
+
export declare function aggregate(results: ScannerRunResult[]): LocalScanReport;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as crypto from "node:crypto";
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
|
+
import { normalizeScannerOutput } from "./normalize.js";
|
|
7
|
+
export const SUPPORTED_SCANNERS = ["gitleaks", "trivy", "semgrep", "osv-scanner"];
|
|
8
|
+
export const SCANNER_BINARIES = {
|
|
9
|
+
gitleaks: "gitleaks",
|
|
10
|
+
trivy: "trivy",
|
|
11
|
+
semgrep: "semgrep",
|
|
12
|
+
"osv-scanner": "osv-scanner",
|
|
13
|
+
};
|
|
14
|
+
export function scannerCommand(source, dir, outPath) {
|
|
15
|
+
switch (source) {
|
|
16
|
+
case "gitleaks":
|
|
17
|
+
return {
|
|
18
|
+
bin: "gitleaks",
|
|
19
|
+
args: ["detect", "--source", dir, "--no-banner", "--report-format", "json", "--report-path", outPath, "--exit-code", "0"],
|
|
20
|
+
};
|
|
21
|
+
case "trivy":
|
|
22
|
+
return {
|
|
23
|
+
bin: "trivy",
|
|
24
|
+
args: ["fs", "--quiet", "--format", "json", "--output", outPath, "--scanners", "vuln,secret,misconfig", dir],
|
|
25
|
+
};
|
|
26
|
+
case "semgrep":
|
|
27
|
+
return {
|
|
28
|
+
bin: "semgrep",
|
|
29
|
+
args: ["scan", "--json", "--output", outPath, "--quiet", "--config", process.env.ZIVIS_SEMGREP_CONFIG || "auto", dir],
|
|
30
|
+
};
|
|
31
|
+
case "osv-scanner":
|
|
32
|
+
return {
|
|
33
|
+
bin: "osv-scanner",
|
|
34
|
+
args: ["--format", "json", "--output", outPath, "--recursive", dir],
|
|
35
|
+
};
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(`unknown scanner: ${source}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const execFileAsync = (bin, args, timeoutMs) => new Promise((resolve) => {
|
|
41
|
+
execFile(bin, args, { timeout: timeoutMs, maxBuffer: 64 * 1024 * 1024 }, (err) => {
|
|
42
|
+
const code = err && typeof err.code === "number" ? (err.code) : err ? 1 : 0;
|
|
43
|
+
resolve({ code, stderr: err ? String(err.message ?? "") : "" });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
export const defaultExecEnv = {
|
|
47
|
+
async isAvailable(bin) {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
execFile(process.platform === "win32" ? "where" : "command", process.platform === "win32" ? [bin] : ["-v", bin], { shell: true, timeout: 5000 }, (err) => {
|
|
50
|
+
resolve(!err);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
async execute(cmd, opts) {
|
|
55
|
+
const outIdx = cmd.args.findIndex((a) => a === "--output" || a === "--report-path");
|
|
56
|
+
const outPath = outIdx >= 0 ? cmd.args[outIdx + 1] : undefined;
|
|
57
|
+
if (!outPath)
|
|
58
|
+
return { reportText: null, error: "no output path in command" };
|
|
59
|
+
try {
|
|
60
|
+
await execFileAsync(cmd.bin, cmd.args, opts.timeoutMs);
|
|
61
|
+
const text = await fs.readFile(outPath, "utf8").catch(() => null);
|
|
62
|
+
return { reportText: text };
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return { reportText: null, error: err instanceof Error ? err.message : String(err) };
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (outPath)
|
|
69
|
+
await fs.rm(outPath, { force: true }).catch(() => { });
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
export async function runOneScanner(source, dir, opts = {}) {
|
|
74
|
+
const env = opts.env ?? defaultExecEnv;
|
|
75
|
+
const bin = SCANNER_BINARIES[source];
|
|
76
|
+
const started = Date.now();
|
|
77
|
+
if (!(await env.isAvailable(bin))) {
|
|
78
|
+
return { source, status: "not_installed", leads: [] };
|
|
79
|
+
}
|
|
80
|
+
const tmpDir = opts.tmpDir ?? os.tmpdir();
|
|
81
|
+
const outPath = path.join(tmpDir, `zivis-${source}-${crypto.randomUUID()}.json`);
|
|
82
|
+
const cmd = scannerCommand(source, dir, outPath);
|
|
83
|
+
try {
|
|
84
|
+
const { reportText, error } = await env.execute(cmd, { timeoutMs: opts.timeoutMs ?? 120_000 });
|
|
85
|
+
if (error && reportText === null) {
|
|
86
|
+
return { source, status: "error", leads: [], error, duration_ms: Date.now() - started };
|
|
87
|
+
}
|
|
88
|
+
if (reportText === null || reportText.trim() === "") {
|
|
89
|
+
return { source, status: "ok", leads: [], duration_ms: Date.now() - started };
|
|
90
|
+
}
|
|
91
|
+
let parsed;
|
|
92
|
+
try {
|
|
93
|
+
parsed = JSON.parse(reportText);
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
return { source, status: "error", leads: [], error: `unparseable ${source} JSON: ${e instanceof Error ? e.message : String(e)}`, duration_ms: Date.now() - started };
|
|
97
|
+
}
|
|
98
|
+
const leads = normalizeScannerOutput(source, parsed);
|
|
99
|
+
return { source, status: "ok", leads, duration_ms: Date.now() - started };
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
return { source, status: "error", leads: [], error: err instanceof Error ? err.message : String(err), duration_ms: Date.now() - started };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export async function runLocalScanners(dir, opts = {}) {
|
|
106
|
+
const sources = opts.sources ?? [...SUPPORTED_SCANNERS];
|
|
107
|
+
const results = [];
|
|
108
|
+
for (const source of sources) {
|
|
109
|
+
results.push(await runOneScanner(source, dir, opts));
|
|
110
|
+
}
|
|
111
|
+
return aggregate(results);
|
|
112
|
+
}
|
|
113
|
+
export function aggregate(results) {
|
|
114
|
+
const leads = [];
|
|
115
|
+
const bySource = {};
|
|
116
|
+
const bySeverity = {};
|
|
117
|
+
const scannersRun = [];
|
|
118
|
+
const scannersMissing = [];
|
|
119
|
+
for (const r of results) {
|
|
120
|
+
if (r.status === "not_installed")
|
|
121
|
+
scannersMissing.push(r.source);
|
|
122
|
+
else
|
|
123
|
+
scannersRun.push(r.source);
|
|
124
|
+
for (const l of r.leads) {
|
|
125
|
+
leads.push(l);
|
|
126
|
+
bySource[l.source] = (bySource[l.source] ?? 0) + 1;
|
|
127
|
+
bySeverity[l.severity] = (bySeverity[l.severity] ?? 0) + 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
ran_at: new Date().toISOString(),
|
|
132
|
+
results,
|
|
133
|
+
leads,
|
|
134
|
+
summary: {
|
|
135
|
+
total_leads: leads.length,
|
|
136
|
+
by_source: bySource,
|
|
137
|
+
by_severity: bySeverity,
|
|
138
|
+
scanners_run: scannersRun,
|
|
139
|
+
scanners_missing: scannersMissing,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type ScannerId = "gitleaks" | "trivy" | "semgrep" | "osv-scanner";
|
|
2
|
+
export type LeadSeverity = "critical" | "high" | "medium" | "low" | "info" | "unknown";
|
|
3
|
+
export interface LeadLocation {
|
|
4
|
+
file?: string;
|
|
5
|
+
start_line?: number;
|
|
6
|
+
end_line?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface NormalizedLead {
|
|
9
|
+
lane: "lead";
|
|
10
|
+
source: ScannerId;
|
|
11
|
+
rule_id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
severity: LeadSeverity;
|
|
14
|
+
location?: LeadLocation;
|
|
15
|
+
package?: string;
|
|
16
|
+
cwe?: string[];
|
|
17
|
+
owasp?: string[];
|
|
18
|
+
references?: string[];
|
|
19
|
+
excerpt?: string;
|
|
20
|
+
metadata?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export interface ScannerRunResult {
|
|
23
|
+
source: ScannerId;
|
|
24
|
+
status: "ok" | "not_installed" | "error";
|
|
25
|
+
leads: NormalizedLead[];
|
|
26
|
+
error?: string;
|
|
27
|
+
duration_ms?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface LocalScanReport {
|
|
30
|
+
ran_at: string;
|
|
31
|
+
results: ScannerRunResult[];
|
|
32
|
+
leads: NormalizedLead[];
|
|
33
|
+
summary: {
|
|
34
|
+
total_leads: number;
|
|
35
|
+
by_source: Record<string, number>;
|
|
36
|
+
by_severity: Record<string, number>;
|
|
37
|
+
scanners_run: ScannerId[];
|
|
38
|
+
scanners_missing: ScannerId[];
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/server.js
CHANGED
|
@@ -73,7 +73,6 @@ import { UPDATE_ENDPOINT_NAME, UPDATE_ENDPOINT_DESCRIPTION, UPDATE_ENDPOINT_SCHE
|
|
|
73
73
|
import { MANAGE_APPLICATION_NAME, MANAGE_APPLICATION_DESCRIPTION, MANAGE_APPLICATION_SCHEMA, createManageApplicationHandler, } from "./tools/manage-application.js";
|
|
74
74
|
import { MANAGE_ENDPOINT_LIFECYCLE_NAME, MANAGE_ENDPOINT_LIFECYCLE_DESCRIPTION, MANAGE_ENDPOINT_LIFECYCLE_SCHEMA, createManageEndpointLifecycleHandler, } from "./tools/manage-endpoint-lifecycle.js";
|
|
75
75
|
import { DISCOVER_LOCAL_INFRA_NAME, DISCOVER_LOCAL_INFRA_DESCRIPTION, DISCOVER_LOCAL_INFRA_SCHEMA, createDiscoverLocalInfraHandler, } from "./tools/discover-local-infra.js";
|
|
76
|
-
import { GENERATE_DIAGRAM_NAME, GENERATE_DIAGRAM_DESCRIPTION, GENERATE_DIAGRAM_SCHEMA, createGenerateDiagramHandler, } from "./tools/generate-diagram.js";
|
|
77
76
|
import { LIST_DIAGRAMS_NAME, LIST_DIAGRAMS_DESCRIPTION, LIST_DIAGRAMS_SCHEMA, createListDiagramsHandler, } from "./tools/list-diagrams.js";
|
|
78
77
|
import { GET_DIAGRAM_NAME, GET_DIAGRAM_DESCRIPTION, GET_DIAGRAM_SCHEMA, createGetDiagramHandler, } from "./tools/get-diagram.js";
|
|
79
78
|
import { CREATE_DIAGRAM_NAME, CREATE_DIAGRAM_DESCRIPTION, CREATE_DIAGRAM_SCHEMA, createCreateDiagramHandler, } from "./tools/create-diagram.js";
|
|
@@ -215,10 +214,6 @@ export async function startServer(incoming = DEFAULT_CONFIG) {
|
|
|
215
214
|
description: DISCOVER_LOCAL_INFRA_DESCRIPTION,
|
|
216
215
|
inputSchema: DISCOVER_LOCAL_INFRA_SCHEMA,
|
|
217
216
|
}, createDiscoverLocalInfraHandler());
|
|
218
|
-
server.registerTool(GENERATE_DIAGRAM_NAME, {
|
|
219
|
-
description: GENERATE_DIAGRAM_DESCRIPTION,
|
|
220
|
-
inputSchema: GENERATE_DIAGRAM_SCHEMA,
|
|
221
|
-
}, createGenerateDiagramHandler(apiClient));
|
|
222
217
|
server.registerTool(LIST_DIAGRAMS_NAME, {
|
|
223
218
|
description: LIST_DIAGRAMS_DESCRIPTION,
|
|
224
219
|
inputSchema: LIST_DIAGRAMS_SCHEMA,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { ApiClient } from "../api-client.js";
|
|
3
3
|
export declare const CREATE_DIAGRAM_NAME = "zivis_create_diagram";
|
|
4
|
-
export declare const CREATE_DIAGRAM_DESCRIPTION = "Create a new
|
|
4
|
+
export declare const CREATE_DIAGRAM_DESCRIPTION = "Create a new diagram from Mermaid source.\n\nUse this to record system architecture diagrams, data flow diagrams, attack chains, sequence diagrams, etc. from code analysis. Content is Mermaid syntax (flowchart, sequenceDiagram, etc.) \u2014 see zivis_update_mermaid_source for editing an existing diagram's source.";
|
|
5
5
|
export declare const CREATE_DIAGRAM_SCHEMA: {
|
|
6
6
|
name: z.ZodString;
|
|
7
7
|
description: z.ZodOptional<z.ZodString>;
|
|
@@ -15,84 +15,14 @@ export declare const CREATE_DIAGRAM_SCHEMA: {
|
|
|
15
15
|
trust_boundary: "trust_boundary";
|
|
16
16
|
network: "network";
|
|
17
17
|
}>;
|
|
18
|
-
|
|
19
|
-
temp_id: z.ZodOptional<z.ZodString>;
|
|
20
|
-
name: z.ZodString;
|
|
21
|
-
description: z.ZodOptional<z.ZodString>;
|
|
22
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
23
|
-
icon: z.ZodOptional<z.ZodString>;
|
|
24
|
-
color: z.ZodOptional<z.ZodString>;
|
|
25
|
-
position_x: z.ZodOptional<z.ZodNumber>;
|
|
26
|
-
position_y: z.ZodOptional<z.ZodNumber>;
|
|
27
|
-
width: z.ZodOptional<z.ZodNumber>;
|
|
28
|
-
height: z.ZodOptional<z.ZodNumber>;
|
|
29
|
-
step_order: z.ZodOptional<z.ZodNumber>;
|
|
30
|
-
boundary_temp_id: z.ZodOptional<z.ZodString>;
|
|
31
|
-
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
32
|
-
}, z.core.$strip>>>;
|
|
33
|
-
connections: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34
|
-
source_temp_id: z.ZodOptional<z.ZodString>;
|
|
35
|
-
target_temp_id: z.ZodOptional<z.ZodString>;
|
|
36
|
-
source_node_id: z.ZodOptional<z.ZodString>;
|
|
37
|
-
target_node_id: z.ZodOptional<z.ZodString>;
|
|
38
|
-
label: z.ZodOptional<z.ZodString>;
|
|
39
|
-
description: z.ZodOptional<z.ZodString>;
|
|
40
|
-
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
41
|
-
bidirectional: z.ZodOptional<z.ZodBoolean>;
|
|
42
|
-
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
43
|
-
}, z.core.$strip>>>;
|
|
44
|
-
boundaries: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
45
|
-
temp_id: z.ZodOptional<z.ZodString>;
|
|
46
|
-
label: z.ZodString;
|
|
47
|
-
description: z.ZodOptional<z.ZodString>;
|
|
48
|
-
color: z.ZodOptional<z.ZodString>;
|
|
49
|
-
position_x: z.ZodOptional<z.ZodNumber>;
|
|
50
|
-
position_y: z.ZodOptional<z.ZodNumber>;
|
|
51
|
-
width: z.ZodOptional<z.ZodNumber>;
|
|
52
|
-
height: z.ZodOptional<z.ZodNumber>;
|
|
53
|
-
}, z.core.$strip>>>;
|
|
18
|
+
content: z.ZodString;
|
|
54
19
|
linked_threat_model_id: z.ZodOptional<z.ZodString>;
|
|
55
20
|
};
|
|
56
21
|
export declare function createCreateDiagramHandler(apiClient: ApiClient): (params: {
|
|
57
22
|
name: string;
|
|
58
23
|
description?: string;
|
|
59
24
|
diagram_type: string;
|
|
60
|
-
|
|
61
|
-
temp_id?: string;
|
|
62
|
-
name: string;
|
|
63
|
-
description?: string;
|
|
64
|
-
tags?: string[];
|
|
65
|
-
icon?: string;
|
|
66
|
-
color?: string;
|
|
67
|
-
position_x?: number;
|
|
68
|
-
position_y?: number;
|
|
69
|
-
width?: number;
|
|
70
|
-
height?: number;
|
|
71
|
-
step_order?: number;
|
|
72
|
-
boundary_temp_id?: string;
|
|
73
|
-
metadata?: Record<string, unknown>;
|
|
74
|
-
}>;
|
|
75
|
-
connections?: Array<{
|
|
76
|
-
source_temp_id?: string;
|
|
77
|
-
target_temp_id?: string;
|
|
78
|
-
source_node_id?: string;
|
|
79
|
-
target_node_id?: string;
|
|
80
|
-
label?: string;
|
|
81
|
-
description?: string;
|
|
82
|
-
tags?: string[];
|
|
83
|
-
bidirectional?: boolean;
|
|
84
|
-
metadata?: Record<string, unknown>;
|
|
85
|
-
}>;
|
|
86
|
-
boundaries?: Array<{
|
|
87
|
-
temp_id?: string;
|
|
88
|
-
label: string;
|
|
89
|
-
description?: string;
|
|
90
|
-
color?: string;
|
|
91
|
-
position_x?: number;
|
|
92
|
-
position_y?: number;
|
|
93
|
-
width?: number;
|
|
94
|
-
height?: number;
|
|
95
|
-
}>;
|
|
25
|
+
content: string;
|
|
96
26
|
linked_threat_model_id?: string;
|
|
97
27
|
}) => Promise<{
|
|
98
28
|
content: {
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { sanitizeResponse } from "../sanitize.js";
|
|
3
3
|
export const CREATE_DIAGRAM_NAME = "zivis_create_diagram";
|
|
4
|
-
export const CREATE_DIAGRAM_DESCRIPTION = `Create a new
|
|
4
|
+
export const CREATE_DIAGRAM_DESCRIPTION = `Create a new diagram from Mermaid source.
|
|
5
5
|
|
|
6
|
-
Use this to
|
|
7
|
-
|
|
8
|
-
**Temp IDs:** When creating nodes and connections together, assign each node a temp_id (e.g., "node_0", "node_1") and reference them in connections via source_temp_id / target_temp_id. Similarly, assign boundaries a temp_id and reference them in nodes via boundary_temp_id.
|
|
9
|
-
|
|
10
|
-
**Positioning:** Nodes default to (0,0). For readable layouts, space nodes apart (e.g., 250px horizontal, 150px vertical gaps). Default node size is 200x100px. Boundaries should be sized to contain their child nodes with padding.
|
|
11
|
-
|
|
12
|
-
**Tags:** Use tags to annotate nodes with metadata (e.g., ["database", "postgres", "port:5432", "pii"]). These are searchable and displayed in the UI.
|
|
13
|
-
|
|
14
|
-
**Colors:** Use hex colors (e.g., "#336791" for databases, "#DC382D" for caches, "#009639" for proxies).`;
|
|
6
|
+
Use this to record system architecture diagrams, data flow diagrams, attack chains, sequence diagrams, etc. from code analysis. Content is Mermaid syntax (flowchart, sequenceDiagram, etc.) — see zivis_update_mermaid_source for editing an existing diagram's source.`;
|
|
15
7
|
export const CREATE_DIAGRAM_SCHEMA = {
|
|
16
8
|
name: z
|
|
17
9
|
.string()
|
|
@@ -23,51 +15,9 @@ export const CREATE_DIAGRAM_SCHEMA = {
|
|
|
23
15
|
diagram_type: z
|
|
24
16
|
.enum(["architecture", "data_flow", "attack_chain", "sequence", "trust_boundary", "network", "deployment", "custom"])
|
|
25
17
|
.describe("Type of diagram"),
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
name: z.string().describe("Node name (e.g., 'PostgreSQL', 'API Gateway')"),
|
|
30
|
-
description: z.string().optional().describe("Node description"),
|
|
31
|
-
tags: z.array(z.string()).optional().describe("Tags (e.g., ['database', 'port:5432'])"),
|
|
32
|
-
icon: z.string().optional().describe("Icon name (e.g., 'database', 'server', 'shield', 'globe')"),
|
|
33
|
-
color: z.string().optional().describe("Hex color (e.g., '#336791')"),
|
|
34
|
-
position_x: z.number().optional().describe("X position on canvas (default 0)"),
|
|
35
|
-
position_y: z.number().optional().describe("Y position on canvas (default 0)"),
|
|
36
|
-
width: z.number().optional().describe("Node width (default 200)"),
|
|
37
|
-
height: z.number().optional().describe("Node height (default 100)"),
|
|
38
|
-
step_order: z.number().optional().describe("Step order for attack chain diagrams"),
|
|
39
|
-
boundary_temp_id: z.string().optional().describe("Temp ID of boundary this node belongs to"),
|
|
40
|
-
metadata: z.record(z.string(), z.unknown()).optional().describe("Custom metadata"),
|
|
41
|
-
}))
|
|
42
|
-
.optional()
|
|
43
|
-
.describe("Nodes to create"),
|
|
44
|
-
connections: z
|
|
45
|
-
.array(z.object({
|
|
46
|
-
source_temp_id: z.string().optional().describe("Source node temp ID"),
|
|
47
|
-
target_temp_id: z.string().optional().describe("Target node temp ID"),
|
|
48
|
-
source_node_id: z.string().optional().describe("Source node UUID (if referencing existing node)"),
|
|
49
|
-
target_node_id: z.string().optional().describe("Target node UUID (if referencing existing node)"),
|
|
50
|
-
label: z.string().optional().describe("Connection label (e.g., 'HTTPS', 'depends on')"),
|
|
51
|
-
description: z.string().optional().describe("Connection description"),
|
|
52
|
-
tags: z.array(z.string()).optional().describe("Connection tags"),
|
|
53
|
-
bidirectional: z.boolean().optional().describe("Whether connection goes both ways (default false)"),
|
|
54
|
-
metadata: z.record(z.string(), z.unknown()).optional().describe("Custom metadata"),
|
|
55
|
-
}))
|
|
56
|
-
.optional()
|
|
57
|
-
.describe("Connections between nodes"),
|
|
58
|
-
boundaries: z
|
|
59
|
-
.array(z.object({
|
|
60
|
-
temp_id: z.string().optional().describe("Temporary ID for referencing in nodes (e.g., 'boundary_0')"),
|
|
61
|
-
label: z.string().describe("Boundary label (e.g., 'DMZ', 'Internal Network')"),
|
|
62
|
-
description: z.string().optional().describe("Boundary description"),
|
|
63
|
-
color: z.string().optional().describe("Hex color"),
|
|
64
|
-
position_x: z.number().optional().describe("X position (default 0)"),
|
|
65
|
-
position_y: z.number().optional().describe("Y position (default 0)"),
|
|
66
|
-
width: z.number().optional().describe("Boundary width (default 400)"),
|
|
67
|
-
height: z.number().optional().describe("Boundary height (default 300)"),
|
|
68
|
-
}))
|
|
69
|
-
.optional()
|
|
70
|
-
.describe("Boundary groups (trust boundaries, network segments)"),
|
|
18
|
+
content: z
|
|
19
|
+
.string()
|
|
20
|
+
.describe("Mermaid diagram source (e.g., 'flowchart TD\\n A[Client] --> B[Server]')"),
|
|
71
21
|
linked_threat_model_id: z
|
|
72
22
|
.string()
|
|
73
23
|
.optional()
|
|
@@ -76,51 +26,13 @@ export const CREATE_DIAGRAM_SCHEMA = {
|
|
|
76
26
|
export function createCreateDiagramHandler(apiClient) {
|
|
77
27
|
return async (params) => {
|
|
78
28
|
try {
|
|
79
|
-
const apiNodes = params.nodes?.map((n) => ({
|
|
80
|
-
tempId: n.temp_id,
|
|
81
|
-
name: n.name,
|
|
82
|
-
description: n.description,
|
|
83
|
-
tags: n.tags,
|
|
84
|
-
icon: n.icon,
|
|
85
|
-
color: n.color,
|
|
86
|
-
positionX: n.position_x,
|
|
87
|
-
positionY: n.position_y,
|
|
88
|
-
width: n.width,
|
|
89
|
-
height: n.height,
|
|
90
|
-
stepOrder: n.step_order,
|
|
91
|
-
boundaryTempId: n.boundary_temp_id,
|
|
92
|
-
metadata: n.metadata,
|
|
93
|
-
}));
|
|
94
|
-
const apiConnections = params.connections?.map((c) => ({
|
|
95
|
-
sourceTempId: c.source_temp_id,
|
|
96
|
-
targetTempId: c.target_temp_id,
|
|
97
|
-
sourceNodeId: c.source_node_id,
|
|
98
|
-
targetNodeId: c.target_node_id,
|
|
99
|
-
label: c.label,
|
|
100
|
-
description: c.description,
|
|
101
|
-
tags: c.tags,
|
|
102
|
-
bidirectional: c.bidirectional,
|
|
103
|
-
metadata: c.metadata,
|
|
104
|
-
}));
|
|
105
|
-
const apiBoundaries = params.boundaries?.map((b) => ({
|
|
106
|
-
tempId: b.temp_id,
|
|
107
|
-
label: b.label,
|
|
108
|
-
description: b.description,
|
|
109
|
-
color: b.color,
|
|
110
|
-
positionX: b.position_x,
|
|
111
|
-
positionY: b.position_y,
|
|
112
|
-
width: b.width,
|
|
113
|
-
height: b.height,
|
|
114
|
-
}));
|
|
115
29
|
const data = await apiClient.post("/api/diagrams", {
|
|
116
30
|
name: params.name,
|
|
117
31
|
description: params.description,
|
|
118
32
|
diagramType: params.diagram_type,
|
|
119
|
-
contentType: "
|
|
33
|
+
contentType: "mermaid",
|
|
34
|
+
content: params.content,
|
|
120
35
|
sourceType: "manual",
|
|
121
|
-
nodes: apiNodes,
|
|
122
|
-
connections: apiConnections,
|
|
123
|
-
boundaries: apiBoundaries,
|
|
124
36
|
});
|
|
125
37
|
const sanitized = sanitizeResponse(data);
|
|
126
38
|
const diagramId = sanitized.id;
|
|
@@ -135,16 +47,12 @@ export function createCreateDiagramHandler(apiClient) {
|
|
|
135
47
|
catch {
|
|
136
48
|
}
|
|
137
49
|
}
|
|
138
|
-
const nodeCount = Array.isArray(sanitized.nodes) ? sanitized.nodes.length : 0;
|
|
139
|
-
const connCount = Array.isArray(sanitized.connections) ? sanitized.connections.length : 0;
|
|
140
|
-
const boundaryCount = Array.isArray(sanitized.boundaries) ? sanitized.boundaries.length : 0;
|
|
141
50
|
const result = {
|
|
142
51
|
diagram_id: diagramId,
|
|
143
52
|
name: sanitized.name,
|
|
144
53
|
diagram_type: sanitized.diagramType,
|
|
145
|
-
stats: { nodes: nodeCount, connections: connCount, boundaries: boundaryCount },
|
|
146
54
|
linked_threat_model_id: linkedThreatModelId,
|
|
147
|
-
_instruction: "Diagram created. Use zivis_get_diagram to see full content, or zivis_manage_diagram to modify.",
|
|
55
|
+
_instruction: "Diagram created. Use zivis_get_diagram to see full content, or zivis_manage_diagram to modify metadata/links, or zivis_update_mermaid_source to change the diagram source.",
|
|
148
56
|
};
|
|
149
57
|
return {
|
|
150
58
|
content: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { ApiClient } from "../api-client.js";
|
|
3
3
|
export declare const GET_DIAGRAM_NAME = "zivis_get_diagram";
|
|
4
|
-
export declare const GET_DIAGRAM_DESCRIPTION = "Get the full content of a diagram including
|
|
4
|
+
export declare const GET_DIAGRAM_DESCRIPTION = "Get the full content of a diagram, including its Mermaid source.\n\nUse this before modifying a diagram \u2014 you need the current Mermaid source for zivis_update_mermaid_source.";
|
|
5
5
|
export declare const GET_DIAGRAM_SCHEMA: {
|
|
6
6
|
diagram_id: z.ZodString;
|
|
7
7
|
};
|