@kryptosai/mcp-observatory 0.19.0 → 0.20.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 +39 -0
- package/dist/src/ci-issue.d.ts +22 -0
- package/dist/src/ci-issue.js +102 -0
- package/dist/src/ci-issue.js.map +1 -0
- package/dist/src/ci.d.ts +4 -0
- package/dist/src/ci.js +7 -0
- package/dist/src/ci.js.map +1 -1
- package/dist/src/cli.js +6 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/ci-report.d.ts +12 -0
- package/dist/src/commands/ci-report.js +102 -0
- package/dist/src/commands/ci-report.js.map +1 -0
- package/dist/src/commands/history.d.ts +2 -0
- package/dist/src/commands/history.js +52 -0
- package/dist/src/commands/history.js.map +1 -0
- package/dist/src/commands/lock.d.ts +2 -0
- package/dist/src/commands/lock.js +133 -0
- package/dist/src/commands/lock.js.map +1 -0
- package/dist/src/commands/scan.js +19 -3
- package/dist/src/commands/scan.js.map +1 -1
- package/dist/src/commands/test.js +9 -0
- package/dist/src/commands/test.js.map +1 -1
- package/dist/src/commands/watch.js +3 -0
- package/dist/src/commands/watch.js.map +1 -1
- package/dist/src/commit-status.d.ts +6 -0
- package/dist/src/commit-status.js +79 -0
- package/dist/src/commit-status.js.map +1 -0
- package/dist/src/history.d.ts +27 -0
- package/dist/src/history.js +80 -0
- package/dist/src/history.js.map +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/lockfile.d.ts +48 -0
- package/dist/src/lockfile.js +150 -0
- package/dist/src/lockfile.js.map +1 -0
- package/dist/src/reporters/pr-comment-matrix.d.ts +6 -0
- package/dist/src/reporters/pr-comment-matrix.js +102 -0
- package/dist/src/reporters/pr-comment-matrix.js.map +1 -0
- package/dist/src/reporters/pr-comment.d.ts +15 -2
- package/dist/src/reporters/pr-comment.js +18 -14
- package/dist/src/reporters/pr-comment.js.map +1 -1
- package/dist/src/score.js +9 -5
- package/dist/src/score.js.map +1 -1
- package/dist/src/server.d.ts +1 -0
- package/dist/src/server.js +103 -1
- package/dist/src/server.js.map +1 -1
- package/dist/src/telemetry.d.ts +15 -0
- package/dist/src/telemetry.js +1 -0
- package/dist/src/telemetry.js.map +1 -1
- package/dist/src/types.d.ts +61 -0
- package/package.json +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
// ── Path helpers ────────────────────────────────────────────────────────────
|
|
4
|
+
export function defaultLockPath(cwd) {
|
|
5
|
+
return path.join(cwd ?? process.cwd(), ".mcp-observatory", "lock.json");
|
|
6
|
+
}
|
|
7
|
+
// ── Read / Write ────────────────────────────────────────────────────────────
|
|
8
|
+
export async function readLockFile(lockPath) {
|
|
9
|
+
const filePath = lockPath ?? defaultLockPath();
|
|
10
|
+
const raw = await readFile(filePath, "utf8");
|
|
11
|
+
return JSON.parse(raw);
|
|
12
|
+
}
|
|
13
|
+
export async function writeLockFile(lockFile, lockPath) {
|
|
14
|
+
const filePath = lockPath ?? defaultLockPath();
|
|
15
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
16
|
+
await writeFile(filePath, JSON.stringify(lockFile, null, 2) + "\n", "utf8");
|
|
17
|
+
return filePath;
|
|
18
|
+
}
|
|
19
|
+
// ── Build Lock Entry from RunArtifact ───────────────────────────────────────
|
|
20
|
+
export function buildServerLockEntry(artifact) {
|
|
21
|
+
const toolsCheck = artifact.checks.find((ch) => ch.id === "tools");
|
|
22
|
+
const promptsCheck = artifact.checks.find((ch) => ch.id === "prompts");
|
|
23
|
+
const resourcesCheck = artifact.checks.find((ch) => ch.id === "resources");
|
|
24
|
+
// Extract tools
|
|
25
|
+
const tools = [];
|
|
26
|
+
if (toolsCheck && toolsCheck.evidence.length > 0) {
|
|
27
|
+
const ev = toolsCheck.evidence[0];
|
|
28
|
+
const schemas = ev.schemas;
|
|
29
|
+
const identifiers = ev.identifiers ?? [];
|
|
30
|
+
if (schemas && Object.keys(schemas).length > 0) {
|
|
31
|
+
for (const name of Object.keys(schemas)) {
|
|
32
|
+
tools.push({ name, inputSchema: schemas[name] });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Fall back to identifiers only
|
|
37
|
+
for (const name of identifiers) {
|
|
38
|
+
tools.push({ name, inputSchema: {} });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Extract prompts
|
|
43
|
+
const prompts = [];
|
|
44
|
+
if (promptsCheck && promptsCheck.evidence.length > 0) {
|
|
45
|
+
const ev = promptsCheck.evidence[0];
|
|
46
|
+
const identifiers = ev.identifiers ?? [];
|
|
47
|
+
for (const name of identifiers) {
|
|
48
|
+
prompts.push({ name });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Extract resources
|
|
52
|
+
const resources = [];
|
|
53
|
+
if (resourcesCheck && resourcesCheck.evidence.length > 0) {
|
|
54
|
+
const ev = resourcesCheck.evidence[0];
|
|
55
|
+
const identifiers = ev.identifiers ?? [];
|
|
56
|
+
for (const name of identifiers) {
|
|
57
|
+
resources.push({ name, uri: name });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
targetId: artifact.target.targetId,
|
|
62
|
+
lockedAt: new Date().toISOString(),
|
|
63
|
+
serverName: artifact.target.serverName,
|
|
64
|
+
serverVersion: artifact.target.serverVersion,
|
|
65
|
+
tools,
|
|
66
|
+
prompts,
|
|
67
|
+
resources,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// ── Verify against lock ─────────────────────────────────────────────────────
|
|
71
|
+
export function verifyAgainstLock(lockEntry, artifact) {
|
|
72
|
+
const current = buildServerLockEntry(artifact);
|
|
73
|
+
const drift = [];
|
|
74
|
+
const targetId = lockEntry.targetId;
|
|
75
|
+
// Compare tools
|
|
76
|
+
const lockedToolNames = new Set(lockEntry.tools.map((t) => t.name));
|
|
77
|
+
const currentToolNames = new Set(current.tools.map((t) => t.name));
|
|
78
|
+
for (const name of currentToolNames) {
|
|
79
|
+
if (!lockedToolNames.has(name)) {
|
|
80
|
+
drift.push({ targetId, category: "tools", name, change: "added" });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const name of lockedToolNames) {
|
|
84
|
+
if (!currentToolNames.has(name)) {
|
|
85
|
+
drift.push({ targetId, category: "tools", name, change: "removed" });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Compare tool schemas for tools present in both
|
|
89
|
+
const lockedToolMap = new Map(lockEntry.tools.map((t) => [t.name, t]));
|
|
90
|
+
for (const tool of current.tools) {
|
|
91
|
+
const locked = lockedToolMap.get(tool.name);
|
|
92
|
+
if (locked) {
|
|
93
|
+
const lockedSchema = JSON.stringify(locked.inputSchema);
|
|
94
|
+
const currentSchema = JSON.stringify(tool.inputSchema);
|
|
95
|
+
if (lockedSchema !== currentSchema) {
|
|
96
|
+
drift.push({
|
|
97
|
+
targetId,
|
|
98
|
+
category: "tools",
|
|
99
|
+
name: tool.name,
|
|
100
|
+
change: "schema changed",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Compare prompts
|
|
106
|
+
const lockedPromptNames = new Set(lockEntry.prompts.map((p) => p.name));
|
|
107
|
+
const currentPromptNames = new Set(current.prompts.map((p) => p.name));
|
|
108
|
+
for (const name of currentPromptNames) {
|
|
109
|
+
if (!lockedPromptNames.has(name)) {
|
|
110
|
+
drift.push({ targetId, category: "prompts", name, change: "added" });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const name of lockedPromptNames) {
|
|
114
|
+
if (!currentPromptNames.has(name)) {
|
|
115
|
+
drift.push({ targetId, category: "prompts", name, change: "removed" });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Compare resources
|
|
119
|
+
const lockedResourceNames = new Set(lockEntry.resources.map((r) => r.name));
|
|
120
|
+
const currentResourceNames = new Set(current.resources.map((r) => r.name));
|
|
121
|
+
for (const name of currentResourceNames) {
|
|
122
|
+
if (!lockedResourceNames.has(name)) {
|
|
123
|
+
drift.push({ targetId, category: "resources", name, change: "added" });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const name of lockedResourceNames) {
|
|
127
|
+
if (!currentResourceNames.has(name)) {
|
|
128
|
+
drift.push({ targetId, category: "resources", name, change: "removed" });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { targetId, passed: drift.length === 0, drift };
|
|
132
|
+
}
|
|
133
|
+
// ── Merge lock files ────────────────────────────────────────────────────────
|
|
134
|
+
export function mergeLockFile(existing, newEntries) {
|
|
135
|
+
const serverMap = new Map();
|
|
136
|
+
// Start with existing entries
|
|
137
|
+
for (const entry of existing.servers) {
|
|
138
|
+
serverMap.set(entry.targetId, entry);
|
|
139
|
+
}
|
|
140
|
+
// Overwrite / add new entries
|
|
141
|
+
for (const entry of newEntries) {
|
|
142
|
+
serverMap.set(entry.targetId, entry);
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
version: 1,
|
|
146
|
+
lockedAt: new Date().toISOString(),
|
|
147
|
+
servers: Array.from(serverMap.values()),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=lockfile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lockfile.js","sourceRoot":"","sources":["../../src/lockfile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { findChecksByStatus } from "./common.js";
|
|
2
|
+
import { extractSecurityFindings, extractQualityFindings, countCapabilities, blockquoteList, prCommentFooter, } from "./pr-comment.js";
|
|
3
|
+
function analyzeRow(row) {
|
|
4
|
+
const security = extractSecurityFindings(row.artifact.checks);
|
|
5
|
+
const quality = extractQualityFindings(row.artifact.checks);
|
|
6
|
+
const failingChecks = findChecksByStatus(row.artifact.checks, "fail")
|
|
7
|
+
.filter(c => c.id !== "security" && c.id !== "security-lite");
|
|
8
|
+
const highMedSecurity = security.filter(f => f.severity === "high" || f.severity === "medium");
|
|
9
|
+
const issueCount = highMedSecurity.length + quality.length + failingChecks.length;
|
|
10
|
+
const caps = countCapabilities(row.artifact.checks);
|
|
11
|
+
return { row, security, quality, failingChecks, highMedSecurity, issueCount, toolCount: caps.tools };
|
|
12
|
+
}
|
|
13
|
+
function trendArrow(trend) {
|
|
14
|
+
if (!trend || trend.direction === "new")
|
|
15
|
+
return "";
|
|
16
|
+
switch (trend.direction) {
|
|
17
|
+
case "up":
|
|
18
|
+
return " ↗";
|
|
19
|
+
case "down":
|
|
20
|
+
return " ↘";
|
|
21
|
+
case "stable":
|
|
22
|
+
return " →";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function healthCell(artifact, trend) {
|
|
26
|
+
const hs = artifact.healthScore;
|
|
27
|
+
const base = hs ? `**${hs.grade}** (${hs.overall})` : `**${artifact.gate}**`;
|
|
28
|
+
return base + trendArrow(trend);
|
|
29
|
+
}
|
|
30
|
+
function issuesCell(analysis) {
|
|
31
|
+
const { highMedSecurity, quality, failingChecks, issueCount } = analysis;
|
|
32
|
+
if (issueCount === 0)
|
|
33
|
+
return "✅";
|
|
34
|
+
const hasSecurityOrFailing = highMedSecurity.length > 0 || failingChecks.length > 0;
|
|
35
|
+
const hasQuality = quality.length > 0;
|
|
36
|
+
if (hasSecurityOrFailing && hasQuality) {
|
|
37
|
+
return `🔴 ${issueCount} issues`;
|
|
38
|
+
}
|
|
39
|
+
if (hasSecurityOrFailing) {
|
|
40
|
+
const count = highMedSecurity.length + failingChecks.length;
|
|
41
|
+
return `🔴 ${count} security`;
|
|
42
|
+
}
|
|
43
|
+
// quality only
|
|
44
|
+
return `⚠️ ${quality.length} quality`;
|
|
45
|
+
}
|
|
46
|
+
function renderDetailsBlock(analysis) {
|
|
47
|
+
const items = [];
|
|
48
|
+
for (const f of analysis.highMedSecurity) {
|
|
49
|
+
items.push(`\`${f.severity}\` ${f.message}`);
|
|
50
|
+
}
|
|
51
|
+
for (const c of analysis.failingChecks) {
|
|
52
|
+
items.push(`\`fail\` **${c.id}**: ${c.message}`);
|
|
53
|
+
}
|
|
54
|
+
for (const f of analysis.quality) {
|
|
55
|
+
items.push(`\`${f.severity}\` ${f.message}`);
|
|
56
|
+
}
|
|
57
|
+
if (items.length === 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
const targetId = analysis.row.artifact.target.targetId;
|
|
60
|
+
const hasSecurityOrFailing = analysis.highMedSecurity.length > 0 || analysis.failingChecks.length > 0;
|
|
61
|
+
const icon = hasSecurityOrFailing ? "🔴" : "⚠️";
|
|
62
|
+
const lines = [];
|
|
63
|
+
lines.push(`<details><summary>${icon} ${targetId} — ${items.length} issue${items.length === 1 ? "" : "s"}</summary>`);
|
|
64
|
+
lines.push("");
|
|
65
|
+
lines.push(blockquoteList(items));
|
|
66
|
+
lines.push("");
|
|
67
|
+
lines.push("</details>");
|
|
68
|
+
return lines.join("\n");
|
|
69
|
+
}
|
|
70
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
71
|
+
export function renderMatrixComment(rows) {
|
|
72
|
+
if (rows.length === 0) {
|
|
73
|
+
return ["## 🔭 MCP Observatory — No servers scanned", "", "No servers were scanned in this run.", prCommentFooter()].join("\n");
|
|
74
|
+
}
|
|
75
|
+
const sections = [];
|
|
76
|
+
const analyses = rows.map(analyzeRow);
|
|
77
|
+
// Header
|
|
78
|
+
sections.push(`## 🔭 MCP Observatory — ${rows.length} server${rows.length === 1 ? "" : "s"} scanned`);
|
|
79
|
+
sections.push("");
|
|
80
|
+
// Table header
|
|
81
|
+
sections.push("| Server | Health | Tools | Issues |");
|
|
82
|
+
sections.push("| --- | --- | --- | --- |");
|
|
83
|
+
// Table rows
|
|
84
|
+
for (const analysis of analyses) {
|
|
85
|
+
const server = analysis.row.artifact.target.targetId;
|
|
86
|
+
const health = healthCell(analysis.row.artifact, analysis.row.trend);
|
|
87
|
+
const tools = String(analysis.toolCount);
|
|
88
|
+
const issues = issuesCell(analysis);
|
|
89
|
+
sections.push(`| ${server} | ${health} | ${tools} | ${issues} |`);
|
|
90
|
+
}
|
|
91
|
+
// Details blocks for rows with issues
|
|
92
|
+
const detailBlocks = analyses
|
|
93
|
+
.map(renderDetailsBlock)
|
|
94
|
+
.filter((b) => b !== undefined);
|
|
95
|
+
if (detailBlocks.length > 0) {
|
|
96
|
+
sections.push("");
|
|
97
|
+
sections.push(detailBlocks.join("\n\n"));
|
|
98
|
+
}
|
|
99
|
+
sections.push(prCommentFooter());
|
|
100
|
+
return sections.join("\n");
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=pr-comment-matrix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-comment-matrix.js","sourceRoot":"","sources":["../../../src/reporters/pr-comment-matrix.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,15 @@
|
|
|
1
|
-
import type { DiffArtifact, RunArtifact } from "../types.js";
|
|
2
|
-
export
|
|
1
|
+
import type { CheckResult, DiffArtifact, RunArtifact, TrendInfo } from "../types.js";
|
|
2
|
+
export interface ParsedFinding {
|
|
3
|
+
severity: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function extractSecurityFindings(checks: CheckResult[]): ParsedFinding[];
|
|
7
|
+
export declare function extractQualityFindings(checks: CheckResult[]): ParsedFinding[];
|
|
8
|
+
export declare function countCapabilities(checks: CheckResult[]): {
|
|
9
|
+
tools: number;
|
|
10
|
+
prompts: number;
|
|
11
|
+
resources: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function blockquoteList(items: string[], max?: number): string;
|
|
14
|
+
export declare function prCommentFooter(): string;
|
|
15
|
+
export declare function renderPrComment(artifact: RunArtifact | DiffArtifact, trend?: TrendInfo): string;
|
|
@@ -2,8 +2,8 @@ import { findChecksByStatus } from "./common.js";
|
|
|
2
2
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
3
3
|
const MAX_ITEMS_PER_SECTION = 5;
|
|
4
4
|
const REPO_URL = "https://github.com/KryptosAI/mcp-observatory";
|
|
5
|
-
// ── Extraction helpers
|
|
6
|
-
function extractSecurityFindings(checks) {
|
|
5
|
+
// ── Extraction helpers (exported for matrix comment reuse) ──────────────────
|
|
6
|
+
export function extractSecurityFindings(checks) {
|
|
7
7
|
const securityChecks = checks.filter(c => c.id === "security" || c.id === "security-lite");
|
|
8
8
|
const findings = [];
|
|
9
9
|
for (const check of securityChecks) {
|
|
@@ -18,7 +18,7 @@ function extractSecurityFindings(checks) {
|
|
|
18
18
|
}
|
|
19
19
|
return findings;
|
|
20
20
|
}
|
|
21
|
-
function extractQualityFindings(checks) {
|
|
21
|
+
export function extractQualityFindings(checks) {
|
|
22
22
|
const qualityChecks = checks.filter(c => c.id === "schema-quality");
|
|
23
23
|
const findings = [];
|
|
24
24
|
for (const check of qualityChecks) {
|
|
@@ -33,7 +33,7 @@ function extractQualityFindings(checks) {
|
|
|
33
33
|
}
|
|
34
34
|
return findings;
|
|
35
35
|
}
|
|
36
|
-
function countCapabilities(checks) {
|
|
36
|
+
export function countCapabilities(checks) {
|
|
37
37
|
const get = (id) => {
|
|
38
38
|
const check = checks.find(c => c.id === id);
|
|
39
39
|
return check?.evidence[0]?.itemCount ?? 0;
|
|
@@ -47,7 +47,7 @@ function conformanceSummary(checks) {
|
|
|
47
47
|
return check.message;
|
|
48
48
|
}
|
|
49
49
|
// ── Formatting helpers ──────────────────────────────────────────────────────
|
|
50
|
-
function blockquoteList(items, max = MAX_ITEMS_PER_SECTION) {
|
|
50
|
+
export function blockquoteList(items, max = MAX_ITEMS_PER_SECTION) {
|
|
51
51
|
const shown = items.slice(0, max);
|
|
52
52
|
const lines = shown.map(item => `> ${item}`);
|
|
53
53
|
const remaining = items.length - max;
|
|
@@ -56,7 +56,7 @@ function blockquoteList(items, max = MAX_ITEMS_PER_SECTION) {
|
|
|
56
56
|
}
|
|
57
57
|
return lines.join("\n");
|
|
58
58
|
}
|
|
59
|
-
function
|
|
59
|
+
export function prCommentFooter() {
|
|
60
60
|
return [
|
|
61
61
|
"",
|
|
62
62
|
"---",
|
|
@@ -64,7 +64,7 @@ function footer() {
|
|
|
64
64
|
].join("\n");
|
|
65
65
|
}
|
|
66
66
|
// ── Run artifact rendering ──────────────────────────────────────────────────
|
|
67
|
-
function renderRunComment(artifact) {
|
|
67
|
+
function renderRunComment(artifact, trend) {
|
|
68
68
|
const sections = [];
|
|
69
69
|
const security = extractSecurityFindings(artifact.checks);
|
|
70
70
|
const quality = extractQualityFindings(artifact.checks);
|
|
@@ -116,10 +116,14 @@ function renderRunComment(artifact) {
|
|
|
116
116
|
}
|
|
117
117
|
// Summary stats
|
|
118
118
|
const caps = countCapabilities(artifact.checks);
|
|
119
|
+
const healthPart = artifact.healthScore
|
|
120
|
+
? `Health: **${artifact.healthScore.grade}** (${artifact.healthScore.overall})`
|
|
121
|
+
: `Gate: **${artifact.gate}**`;
|
|
122
|
+
const trendPart = trend && trend.direction !== "new" && trend.previous
|
|
123
|
+
? ` ${trend.direction === "up" ? "↗" : trend.direction === "down" ? "↘" : "→"} was ${trend.previous.grade} (${trend.previous.healthScore})`
|
|
124
|
+
: "";
|
|
119
125
|
const statsLine = [
|
|
120
|
-
|
|
121
|
-
? `Health: **${artifact.healthScore.grade}** (${artifact.healthScore.overall})`
|
|
122
|
-
: `Gate: **${artifact.gate}**`,
|
|
126
|
+
healthPart + trendPart,
|
|
123
127
|
`${caps.tools} tools`,
|
|
124
128
|
`${caps.prompts} prompts`,
|
|
125
129
|
`${caps.resources} resources`,
|
|
@@ -127,7 +131,7 @@ function renderRunComment(artifact) {
|
|
|
127
131
|
sections.push("");
|
|
128
132
|
sections.push(`### 📊 Summary`);
|
|
129
133
|
sections.push(`> ${statsLine}`);
|
|
130
|
-
sections.push(
|
|
134
|
+
sections.push(prCommentFooter());
|
|
131
135
|
return sections.join("\n");
|
|
132
136
|
}
|
|
133
137
|
// ── Diff artifact rendering ─────────────────────────────────────────────────
|
|
@@ -186,7 +190,7 @@ function renderDiffComment(artifact) {
|
|
|
186
190
|
sections.push("");
|
|
187
191
|
sections.push(`### 📊 Summary`);
|
|
188
192
|
sections.push(`> ${statsLine}`);
|
|
189
|
-
sections.push(
|
|
193
|
+
sections.push(prCommentFooter());
|
|
190
194
|
return sections.join("\n");
|
|
191
195
|
}
|
|
192
196
|
function flattenDrift(drift) {
|
|
@@ -199,9 +203,9 @@ function flattenDrift(drift) {
|
|
|
199
203
|
return lines;
|
|
200
204
|
}
|
|
201
205
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
202
|
-
export function renderPrComment(artifact) {
|
|
206
|
+
export function renderPrComment(artifact, trend) {
|
|
203
207
|
return artifact.artifactType === "run"
|
|
204
|
-
? renderRunComment(artifact)
|
|
208
|
+
? renderRunComment(artifact, trend)
|
|
205
209
|
: renderDiffComment(artifact);
|
|
206
210
|
}
|
|
207
211
|
//# sourceMappingURL=pr-comment.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pr-comment.js","sourceRoot":"","sources":["../../../src/reporters/pr-comment.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AASjD,+EAA+E;AAE/E,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAChC,MAAM,QAAQ,GAAG,8CAA8C,CAAC;AAEhE,+EAA+E;AAE/E,
|
|
1
|
+
{"version":3,"file":"pr-comment.js","sourceRoot":"","sources":["../../../src/reporters/pr-comment.ts"],"names":[],"mappings":"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"}
|
package/dist/src/score.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
// IMPORTANT: Scoring logic is duplicated in api/src/worker.ts for the Cloudflare Worker
|
|
2
|
+
// deployment (which can't import from src/). Keep both files in sync when making changes.
|
|
1
3
|
export const DEFAULT_WEIGHTS = {
|
|
2
|
-
protocolCompliance: 0.30,
|
|
3
|
-
schemaQuality: 0.20,
|
|
4
|
-
security: 0.20,
|
|
5
|
-
reliability: 0.20,
|
|
6
|
-
performance: 0.10,
|
|
4
|
+
protocolCompliance: 0.30, // Highest — spec compliance is foundational for interop
|
|
5
|
+
schemaQuality: 0.20, // Good schemas enable AI agents to use tools correctly
|
|
6
|
+
security: 0.20, // Parity with quality — both critical for production use
|
|
7
|
+
reliability: 0.20, // Tools/prompts/resources actually responding as expected
|
|
8
|
+
performance: 0.10, // Lowest — latency matters less than correctness
|
|
7
9
|
};
|
|
8
10
|
const STATUS_SCORES = {
|
|
9
11
|
pass: 100,
|
|
@@ -60,6 +62,8 @@ function scorePerformance(weight, metrics) {
|
|
|
60
62
|
const sorted = [...latencies].sort((a, b) => a - b);
|
|
61
63
|
const p95Index = Math.min(Math.ceil(sorted.length * 0.95) - 1, sorted.length - 1);
|
|
62
64
|
const p95 = sorted[p95Index] ?? 0;
|
|
65
|
+
// p95 latency thresholds for performance scoring
|
|
66
|
+
// <500ms = excellent (100), <1s = good (80), <2s = acceptable (60), <5s = slow (40), >5s = poor (20)
|
|
63
67
|
let score;
|
|
64
68
|
if (p95 < 500)
|
|
65
69
|
score = 100;
|
package/dist/src/score.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/score.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/score.ts"],"names":[],"mappings":"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"}
|
package/dist/src/server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
export declare function validateArgs(args: string[]): void;
|
|
2
3
|
export declare function validateCommand(command: string): void;
|
|
3
4
|
export declare function validatePath(filePath: string, allowedRoot: string): string;
|
|
4
5
|
export declare function startServer(): Promise<void>;
|
package/dist/src/server.js
CHANGED
|
@@ -15,6 +15,9 @@ import { defaultRunsDirectory, readArtifact, writeRunArtifact } from "./storage.
|
|
|
15
15
|
import { compareResponses } from "./verify.js";
|
|
16
16
|
import { loadTelemetryConfig, recordEvent, buildEvent } from "./telemetry.js";
|
|
17
17
|
import { TOOL_VERSION } from "./version.js";
|
|
18
|
+
import { readLockFile, verifyAgainstLock } from "./lockfile.js";
|
|
19
|
+
import { readHistory, getTrend, renderTrendLabel } from "./history.js";
|
|
20
|
+
import { buildCiReport } from "./commands/ci-report.js";
|
|
18
21
|
// ── Security: Command Allowlist ────────────────────────────────────────────
|
|
19
22
|
// MCP server mode is invoked by an LLM, not an operator. Arbitrary command
|
|
20
23
|
// execution would let a prompt-injection attack run anything on the host.
|
|
@@ -32,7 +35,7 @@ const ALLOWED_COMMANDS = new Set([
|
|
|
32
35
|
// ── Security: Argument Validation ──────────────────────────────────────────
|
|
33
36
|
// Reject args containing shell metacharacters that could enable injection.
|
|
34
37
|
const DANGEROUS_ARG_PATTERN = /[;|`]|\$\(|&&|\|\|/;
|
|
35
|
-
function validateArgs(args) {
|
|
38
|
+
export function validateArgs(args) {
|
|
36
39
|
for (const arg of args) {
|
|
37
40
|
if (DANGEROUS_ARG_PATTERN.test(arg)) {
|
|
38
41
|
throw new Error(`Argument "${arg}" contains shell metacharacters and was rejected. ` +
|
|
@@ -550,6 +553,105 @@ export async function startServer() {
|
|
|
550
553
|
return { content: [{ type: "text", text: `Error watching: ${msg}` }], isError: true };
|
|
551
554
|
}
|
|
552
555
|
});
|
|
556
|
+
server.tool("lock_verify", "Verify that live MCP servers still match a previously saved lock file. Detects schema drift, added/removed tools, and breaking changes.", {
|
|
557
|
+
config: z.string().optional().describe("Path to MCP config file."),
|
|
558
|
+
}, async ({ config }) => {
|
|
559
|
+
const startMs = Date.now();
|
|
560
|
+
try {
|
|
561
|
+
const lockFile = await readLockFile();
|
|
562
|
+
const targets = await scanForTargets(config);
|
|
563
|
+
const results = [];
|
|
564
|
+
let anyFailed = false;
|
|
565
|
+
for (const t of targets) {
|
|
566
|
+
const lockEntry = lockFile.servers.find(s => s.targetId === t.config.targetId);
|
|
567
|
+
if (!lockEntry)
|
|
568
|
+
continue;
|
|
569
|
+
const artifact = await runTarget(t.config);
|
|
570
|
+
const result = verifyAgainstLock(lockEntry, artifact);
|
|
571
|
+
if (result.passed) {
|
|
572
|
+
results.push(`✓ ${t.config.targetId}: no drift`);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
anyFailed = true;
|
|
576
|
+
results.push(`✗ ${t.config.targetId}: ${result.drift.length} changes`);
|
|
577
|
+
for (const d of result.drift) {
|
|
578
|
+
results.push(` - ${d.category}: ${d.name} — ${d.change}`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (results.length === 0) {
|
|
583
|
+
results.push("No servers in lock file match discovered targets.");
|
|
584
|
+
}
|
|
585
|
+
logRequest("lock_verify", startMs, anyFailed);
|
|
586
|
+
return { content: [{ type: "text", text: results.join("\n") }] };
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
590
|
+
logRequest("lock_verify", startMs, true);
|
|
591
|
+
return { content: [{ type: "text", text: `Lock verify failed: ${msg}` }], isError: true };
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
server.tool("get_history", "Get health score trends for MCP servers from run history.", {
|
|
595
|
+
target: z.string().optional().describe("Filter to a specific target ID."),
|
|
596
|
+
}, async ({ target }) => {
|
|
597
|
+
const startMs = Date.now();
|
|
598
|
+
try {
|
|
599
|
+
const history = await readHistory();
|
|
600
|
+
let targetIds = [...new Set(history.entries.map(e => e.targetId))];
|
|
601
|
+
if (target)
|
|
602
|
+
targetIds = targetIds.filter(id => id === target);
|
|
603
|
+
if (targetIds.length === 0) {
|
|
604
|
+
logRequest("get_history", startMs);
|
|
605
|
+
return { content: [{ type: "text", text: "No history found. Run a scan or test first." }] };
|
|
606
|
+
}
|
|
607
|
+
const lines = [];
|
|
608
|
+
for (const id of targetIds) {
|
|
609
|
+
const trend = getTrend(id, history);
|
|
610
|
+
if (!trend)
|
|
611
|
+
continue;
|
|
612
|
+
const { current } = trend;
|
|
613
|
+
const label = renderTrendLabel(trend);
|
|
614
|
+
lines.push(`${id}: ${current.grade} (${current.healthScore}) ${label}`);
|
|
615
|
+
}
|
|
616
|
+
logRequest("get_history", startMs);
|
|
617
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
618
|
+
}
|
|
619
|
+
catch (error) {
|
|
620
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
621
|
+
logRequest("get_history", startMs, true);
|
|
622
|
+
return { content: [{ type: "text", text: `History failed: ${msg}` }], isError: true };
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
server.tool("ci_report", "Generate a CI regression report from run artifacts.", {
|
|
626
|
+
artifactsDir: z.string().optional().describe("Directory containing run artifacts. Defaults to .mcp-observatory/runs/"),
|
|
627
|
+
}, async ({ artifactsDir }) => {
|
|
628
|
+
const startMs = Date.now();
|
|
629
|
+
try {
|
|
630
|
+
const { readdir, readFile } = await import("node:fs/promises");
|
|
631
|
+
const dir = artifactsDir ?? path.join(process.cwd(), ".mcp-observatory", "runs");
|
|
632
|
+
const files = await readdir(dir);
|
|
633
|
+
const artifacts = [];
|
|
634
|
+
for (const f of files) {
|
|
635
|
+
if (!f.endsWith(".json"))
|
|
636
|
+
continue;
|
|
637
|
+
try {
|
|
638
|
+
const raw = await readFile(path.join(dir, f), "utf8");
|
|
639
|
+
const parsed = JSON.parse(raw);
|
|
640
|
+
if (parsed["artifactType"] === "run")
|
|
641
|
+
artifacts.push(parsed);
|
|
642
|
+
}
|
|
643
|
+
catch { /* skip invalid */ }
|
|
644
|
+
}
|
|
645
|
+
const report = buildCiReport(artifacts);
|
|
646
|
+
logRequest("ci_report", startMs);
|
|
647
|
+
return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
|
|
648
|
+
}
|
|
649
|
+
catch (error) {
|
|
650
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
651
|
+
logRequest("ci_report", startMs, true);
|
|
652
|
+
return { content: [{ type: "text", text: `CI report failed: ${msg}` }], isError: true };
|
|
653
|
+
}
|
|
654
|
+
});
|
|
553
655
|
const transport = new StdioServerTransport();
|
|
554
656
|
await server.connect(transport);
|
|
555
657
|
}
|