@certscore/mcp 0.2.11 → 0.2.15
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 +131 -36
- package/dist/certscore-mcp.mjs +984 -243
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +122 -106
- package/dist/tools.d.ts +21 -58
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +520 -58
- package/package.json +4 -3
- package/server-light.json +19 -0
- package/server.json +10 -3
package/dist/tools.js
CHANGED
|
@@ -4,6 +4,18 @@ export const MAX_EVIDENCE_PACKET_CHARS = 250_000;
|
|
|
4
4
|
const EVIDENCE_STRING_CHARS = 4_000;
|
|
5
5
|
const EVIDENCE_ARRAY_ITEMS = 40;
|
|
6
6
|
const EVIDENCE_OBJECT_KEYS = 80;
|
|
7
|
+
const OBSERVATION_ONLY_DISCLAIMER = "Observation only: no-go, not-observed, and limited-coverage results are not proof of compliance.";
|
|
8
|
+
function toolResultSummary(payload) {
|
|
9
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
10
|
+
return "CertScore tool call completed. Read structuredContent for the result.";
|
|
11
|
+
}
|
|
12
|
+
const record = payload;
|
|
13
|
+
const type = typeof record.type === "string" ? record.type : "result";
|
|
14
|
+
const status = typeof record.status === "string" ? `; status=${record.status}` : "";
|
|
15
|
+
const scanId = typeof record.scanId === "string" ? `; scanId=${record.scanId}` : "";
|
|
16
|
+
const score = typeof record.score === "number" ? `; score=${record.score}` : "";
|
|
17
|
+
return `CertScore ${type}${status}${scanId}${score}. Full result is in structuredContent.`;
|
|
18
|
+
}
|
|
7
19
|
export function toToolResult(payload) {
|
|
8
20
|
const structuredContent = payload !== null && typeof payload === "object" && !Array.isArray(payload)
|
|
9
21
|
? payload
|
|
@@ -13,37 +25,167 @@ export function toToolResult(payload) {
|
|
|
13
25
|
content: [
|
|
14
26
|
{
|
|
15
27
|
type: "text",
|
|
16
|
-
text:
|
|
28
|
+
text: toolResultSummary(payload)
|
|
17
29
|
}
|
|
18
30
|
]
|
|
19
31
|
};
|
|
20
32
|
}
|
|
21
33
|
export function toToolError(error) {
|
|
22
|
-
const
|
|
23
|
-
?
|
|
24
|
-
|
|
34
|
+
const responseRecord = error instanceof CertScoreError && error.responseBody && typeof error.responseBody === "object" && !Array.isArray(error.responseBody)
|
|
35
|
+
? error.responseBody
|
|
36
|
+
: null;
|
|
37
|
+
const terminalError = responseRecord?.error && typeof responseRecord.error === "object" && !Array.isArray(responseRecord.error)
|
|
38
|
+
? responseRecord.error
|
|
39
|
+
: null;
|
|
40
|
+
const status = error instanceof CertScoreError ? error.status : undefined;
|
|
41
|
+
const retryable = typeof terminalError?.retryable === "boolean"
|
|
42
|
+
? terminalError.retryable
|
|
43
|
+
: status === 429 || (typeof status === "number" && status >= 500);
|
|
44
|
+
const retryAfterSeconds = typeof terminalError?.retryAfterSeconds === "number"
|
|
45
|
+
? terminalError.retryAfterSeconds
|
|
46
|
+
: error instanceof CertScoreError && "retryAfterSeconds" in error && typeof error.retryAfterSeconds === "number"
|
|
47
|
+
? error.retryAfterSeconds
|
|
48
|
+
: retryable
|
|
49
|
+
? 30
|
|
50
|
+
: null;
|
|
51
|
+
const code = error instanceof CertScoreError ? error.code : "internal_error";
|
|
52
|
+
const message = error instanceof Error ? error.message : "Unknown CertScore MCP error.";
|
|
53
|
+
const recommendedNextAction = typeof terminalError?.recommendedNextAction === "string"
|
|
54
|
+
? terminalError.recommendedNextAction
|
|
55
|
+
: retryable
|
|
56
|
+
? `Wait ${retryAfterSeconds ?? 30} seconds, then retry the same request. Stop and contact CertScore support if the error repeats.`
|
|
57
|
+
: "Correct the request using the error details, then retry only if the requested operation is still appropriate.";
|
|
58
|
+
const payload = {
|
|
59
|
+
error: {
|
|
60
|
+
code,
|
|
61
|
+
message,
|
|
62
|
+
retryable,
|
|
63
|
+
retryAfterSeconds,
|
|
64
|
+
recommendedNextAction,
|
|
65
|
+
...(error instanceof CertScoreError ? {
|
|
25
66
|
name: error.name,
|
|
26
|
-
message: error.message,
|
|
27
67
|
status: error.status,
|
|
28
|
-
code: error.code,
|
|
29
|
-
retryAfterSeconds: "retryAfterSeconds" in error ? error.retryAfterSeconds : undefined,
|
|
30
68
|
responseBody: truncateErrorResponseBody(error.responseBody)
|
|
31
|
-
}
|
|
69
|
+
} : {})
|
|
32
70
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
74
|
+
isError: true
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function toInvalidArgumentsToolError(errorMessage) {
|
|
78
|
+
const tool = errorMessage.match(/tool ([a-z_]+)/i)?.[1] ?? null;
|
|
79
|
+
const field = errorMessage.match(/\bat ([a-zA-Z0-9_.-]+)/)?.[1]
|
|
80
|
+
?? (errorMessage.includes("url") ? "url" : errorMessage.includes("scanId") ? "scanId" : null);
|
|
81
|
+
const missing = /required|expected string, received undefined/i.test(errorMessage);
|
|
82
|
+
const message = field
|
|
83
|
+
? missing
|
|
84
|
+
? `The ${field} field is required.`
|
|
85
|
+
: `The ${field} field is invalid.`
|
|
86
|
+
: `The arguments for ${tool ?? "this tool"} are invalid.`;
|
|
87
|
+
const recommendedNextAction = field === "url"
|
|
88
|
+
? "Provide a public URL or domain."
|
|
89
|
+
: field === "scanId"
|
|
90
|
+
? "Provide the stable scanId returned by certscore_scan_site."
|
|
91
|
+
: "Correct the named fields using the tool input schema, then retry.";
|
|
92
|
+
const error = {
|
|
93
|
+
code: "invalid_arguments",
|
|
94
|
+
message,
|
|
95
|
+
...(field ? { field } : {}),
|
|
96
|
+
retryable: false,
|
|
97
|
+
retryAfterSeconds: null,
|
|
98
|
+
recommendedNextAction,
|
|
99
|
+
mcpCode: -32602
|
|
100
|
+
};
|
|
101
|
+
const payload = tool === "certscore_scan_site"
|
|
102
|
+
? {
|
|
103
|
+
type: "certscore_tool_error",
|
|
104
|
+
status: "invalid_arguments",
|
|
105
|
+
error,
|
|
106
|
+
recommendedNextTool: null,
|
|
107
|
+
recommendedNextAction,
|
|
108
|
+
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER
|
|
109
|
+
}
|
|
110
|
+
: { error };
|
|
42
111
|
return {
|
|
43
112
|
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
113
|
+
...(tool === "certscore_scan_site" ? { structuredContent: payload } : {}),
|
|
44
114
|
isError: true
|
|
45
115
|
};
|
|
46
116
|
}
|
|
117
|
+
function terminalErrorForResult(value) {
|
|
118
|
+
const existing = value.error && typeof value.error === "object" && !Array.isArray(value.error)
|
|
119
|
+
? value.error
|
|
120
|
+
: null;
|
|
121
|
+
if (existing) {
|
|
122
|
+
const retryable = existing.retryable === true;
|
|
123
|
+
return {
|
|
124
|
+
code: typeof existing.code === "string" ? existing.code : String(value.status ?? "scan_failed"),
|
|
125
|
+
message: typeof existing.message === "string" ? existing.message : "The scan did not produce a canonical result.",
|
|
126
|
+
retryable,
|
|
127
|
+
retryAfterSeconds: typeof existing.retryAfterSeconds === "number" ? existing.retryAfterSeconds : retryable ? 30 : null,
|
|
128
|
+
recommendedNextAction: typeof existing.recommendedNextAction === "string"
|
|
129
|
+
? existing.recommendedNextAction
|
|
130
|
+
: retryable
|
|
131
|
+
? "Wait for the recommended delay, then retry certscore_scan_site with freshness=refresh."
|
|
132
|
+
: "Stop and review the scan limitations before deciding whether to change the URL."
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (value.status === "completed_limited" && value.noGo) {
|
|
136
|
+
const retryable = value.noGo.retryLikelyToHelp === true;
|
|
137
|
+
return {
|
|
138
|
+
code: typeof value.noGo.reasonCode === "string" ? value.noGo.reasonCode : "completed_limited",
|
|
139
|
+
message: typeof value.noGo.explanation === "string" ? value.noGo.explanation : "The scan completed with a no-go limitation.",
|
|
140
|
+
retryable,
|
|
141
|
+
retryAfterSeconds: retryable ? 30 : null,
|
|
142
|
+
recommendedNextAction: typeof value.noGo.recommendedNextAction === "string"
|
|
143
|
+
? value.noGo.recommendedNextAction
|
|
144
|
+
: "Review the retained limitation and change the URL or site state before retrying."
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const fallback = {
|
|
148
|
+
failed: {
|
|
149
|
+
code: "scanner_runtime_failure",
|
|
150
|
+
message: "The scan ended before a canonical result could be produced.",
|
|
151
|
+
retryable: true,
|
|
152
|
+
retryAfterSeconds: 30,
|
|
153
|
+
recommendedNextAction: "Wait 30 seconds, then retry certscore_scan_site with freshness=refresh. Stop if the failure repeats."
|
|
154
|
+
},
|
|
155
|
+
expired: {
|
|
156
|
+
code: "scan_expired",
|
|
157
|
+
message: "The scan expired before a canonical result was available.",
|
|
158
|
+
retryable: true,
|
|
159
|
+
retryAfterSeconds: 30,
|
|
160
|
+
recommendedNextAction: "Wait 30 seconds, then retry certscore_scan_site with freshness=refresh."
|
|
161
|
+
},
|
|
162
|
+
rate_limited: {
|
|
163
|
+
code: "rate_limited",
|
|
164
|
+
message: "The scan is rate limited.",
|
|
165
|
+
retryable: true,
|
|
166
|
+
retryAfterSeconds: typeof value.retryAfterSeconds === "number" ? value.retryAfterSeconds : 30,
|
|
167
|
+
recommendedNextAction: "Wait for the recommended delay, then retry the same certscore_scan_site request."
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
return fallback[String(value.status)] ?? null;
|
|
171
|
+
}
|
|
172
|
+
export function withMcpAgentGuidance(value) {
|
|
173
|
+
const status = String(value.status ?? "");
|
|
174
|
+
const active = status === "queued" || status === "running" || status === "finalizing";
|
|
175
|
+
const usable = status === "completed" || status === "completed_limited";
|
|
176
|
+
const error = terminalErrorForResult(value);
|
|
177
|
+
return {
|
|
178
|
+
...value,
|
|
179
|
+
error,
|
|
180
|
+
recommendedNextTool: active ? "certscore_get_scan_status" : usable ? "certscore_get_scan_bundle" : null,
|
|
181
|
+
recommendedNextAction: error?.recommendedNextAction ?? value.recommendedNextAction ?? (active
|
|
182
|
+
? `Poll certscore_get_scan_status with scanId ${value.scanId ?? value.jobId} after the recommended delay.`
|
|
183
|
+
: usable
|
|
184
|
+
? `Call certscore_get_scan_bundle with scanId ${value.scanId ?? value.jobId} for the canonical findings and limitations.`
|
|
185
|
+
: "Review the result and retained limitations."),
|
|
186
|
+
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER
|
|
187
|
+
};
|
|
188
|
+
}
|
|
47
189
|
export function boundEvidencePacket(payload, maxSerializedChars = MAX_EVIDENCE_PACKET_CHARS) {
|
|
48
190
|
const originalSerializedChars = measureSerializedChars(payload);
|
|
49
191
|
if (originalSerializedChars <= maxSerializedChars) {
|
|
@@ -87,6 +229,121 @@ export function boundEvidencePacket(payload, maxSerializedChars = MAX_EVIDENCE_P
|
|
|
87
229
|
function measureSerializedChars(value) {
|
|
88
230
|
return JSON.stringify(value)?.length ?? 0;
|
|
89
231
|
}
|
|
232
|
+
function measureSerializedBytes(value) {
|
|
233
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
234
|
+
}
|
|
235
|
+
function updateBundleActualBytes(bundle) {
|
|
236
|
+
bundle.mcpMetadata.actualBytes = 0;
|
|
237
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
238
|
+
const measured = measureSerializedBytes(bundle);
|
|
239
|
+
if (bundle.mcpMetadata.actualBytes === measured) {
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
bundle.mcpMetadata.actualBytes = measured;
|
|
243
|
+
}
|
|
244
|
+
return bundle.mcpMetadata.actualBytes;
|
|
245
|
+
}
|
|
246
|
+
function boundedText(value, maxChars) {
|
|
247
|
+
if (typeof value !== "string") {
|
|
248
|
+
return value;
|
|
249
|
+
}
|
|
250
|
+
return value.length > maxChars ? `${value.slice(0, maxChars)}…[truncated]` : value;
|
|
251
|
+
}
|
|
252
|
+
function compactBundleFinding(finding) {
|
|
253
|
+
const evidence = finding.evidence && typeof finding.evidence === "object" && !Array.isArray(finding.evidence)
|
|
254
|
+
? finding.evidence
|
|
255
|
+
: {};
|
|
256
|
+
return {
|
|
257
|
+
type: finding.type,
|
|
258
|
+
id: finding.id,
|
|
259
|
+
scanId: finding.scanId,
|
|
260
|
+
label: boundedText(finding.label, 140),
|
|
261
|
+
criticality: finding.criticality,
|
|
262
|
+
confidence: finding.confidence,
|
|
263
|
+
plainEnglish: boundedText(finding.plainEnglish, 280),
|
|
264
|
+
...(finding.resultDisposition ? { resultDisposition: finding.resultDisposition } : {}),
|
|
265
|
+
...(finding.noGo ? { noGo: finding.noGo } : {}),
|
|
266
|
+
reviewLenses: Array.isArray(finding.reviewLenses) ? finding.reviewLenses.slice(0, 2) : [],
|
|
267
|
+
evidence: {
|
|
268
|
+
basis: evidence.basis,
|
|
269
|
+
summary: boundedText(evidence.summary, 220),
|
|
270
|
+
...(evidence.phase !== undefined ? { phase: evidence.phase } : {}),
|
|
271
|
+
exampleCount: evidence.exampleCount,
|
|
272
|
+
examplesShown: evidence.examplesShown,
|
|
273
|
+
...(evidence.hasTimingAnchor !== undefined ? { hasTimingAnchor: evidence.hasTimingAnchor } : {}),
|
|
274
|
+
...(evidence.hasVendorAnchor !== undefined ? { hasVendorAnchor: evidence.hasVendorAnchor } : {}),
|
|
275
|
+
...(evidence.hasConsentContext !== undefined ? { hasConsentContext: evidence.hasConsentContext } : {}),
|
|
276
|
+
...(evidence.hasPolicyAnchor !== undefined ? { hasPolicyAnchor: evidence.hasPolicyAnchor } : {})
|
|
277
|
+
},
|
|
278
|
+
...(finding.nextStep !== undefined ? { nextStep: boundedText(finding.nextStep, 180) } : {})
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function compactPriorityEvidenceSummary(summary) {
|
|
282
|
+
const firstDigest = Array.isArray(summary.digests) ? summary.digests[0] : null;
|
|
283
|
+
const firstReference = summary.references && typeof summary.references === "object" && !Array.isArray(summary.references)
|
|
284
|
+
? Object.entries(summary.references).find(([, value]) => typeof value === "string")
|
|
285
|
+
: null;
|
|
286
|
+
return {
|
|
287
|
+
digests: firstDigest ? [{
|
|
288
|
+
findingId: firstDigest.findingId,
|
|
289
|
+
basis: firstDigest.basis ?? null,
|
|
290
|
+
summary: boundedText(firstDigest.summary, 180) ?? null,
|
|
291
|
+
phase: firstDigest.phase ?? null,
|
|
292
|
+
evidenceUrl: firstDigest.evidenceUrl ?? firstReference?.[1] ?? null
|
|
293
|
+
}] : [],
|
|
294
|
+
evidenceAvailable: summary.evidenceAvailable === true,
|
|
295
|
+
evidenceSafetyNotes: [],
|
|
296
|
+
references: firstDigest ? {} : firstReference ? { [firstReference[0]]: firstReference[1] } : {}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function bundleEvidenceSummary(evidence, findings, links, includeDiagnostics) {
|
|
300
|
+
const digests = findings.slice(0, 3).map((finding) => {
|
|
301
|
+
const findingEvidence = finding.evidence && typeof finding.evidence === "object" && !Array.isArray(finding.evidence)
|
|
302
|
+
? finding.evidence
|
|
303
|
+
: {};
|
|
304
|
+
return {
|
|
305
|
+
findingId: finding.id,
|
|
306
|
+
basis: findingEvidence.basis ?? null,
|
|
307
|
+
summary: boundedText(findingEvidence.summary, 500) ?? null,
|
|
308
|
+
phase: findingEvidence.phase ?? null,
|
|
309
|
+
evidenceUrl: findingEvidence.excerpt && typeof findingEvidence.excerpt === "object" && !Array.isArray(findingEvidence.excerpt)
|
|
310
|
+
? findingEvidence.excerpt.evidenceUrl ?? null
|
|
311
|
+
: typeof links.findings === "string"
|
|
312
|
+
? links.findings
|
|
313
|
+
: typeof links.report === "string"
|
|
314
|
+
? links.report
|
|
315
|
+
: null
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
digests,
|
|
320
|
+
evidenceAvailable: digests.length > 0 || Object.keys(evidence).length > 0,
|
|
321
|
+
evidenceSafetyNotes: Array.isArray(evidence.evidenceSafetyNotes)
|
|
322
|
+
? evidence.evidenceSafetyNotes.slice(0, 3).map((note) => boundedText(note, 300))
|
|
323
|
+
: [],
|
|
324
|
+
references: Object.fromEntries(Object.entries(links).filter(([key, value]) => ["findings", "pulse", "report", "preConsentCookiesTrackers"].includes(key) && typeof value === "string")),
|
|
325
|
+
...(includeDiagnostics ? {
|
|
326
|
+
projectionDiagnostics: compactEvidenceValue(evidence.projectionDiagnostics ?? null, {
|
|
327
|
+
arrayItems: 12,
|
|
328
|
+
depth: 4,
|
|
329
|
+
objectKeys: 24,
|
|
330
|
+
stringChars: 600
|
|
331
|
+
}),
|
|
332
|
+
coverageDiagnostics: compactEvidenceValue(evidence.coverageDiagnostics ?? null, {
|
|
333
|
+
arrayItems: 12,
|
|
334
|
+
depth: 4,
|
|
335
|
+
objectKeys: 24,
|
|
336
|
+
stringChars: 600
|
|
337
|
+
}),
|
|
338
|
+
policySurfaceCoverage: compactEvidenceValue(evidence.policySurfaceCoverage ?? null, {
|
|
339
|
+
arrayItems: 12,
|
|
340
|
+
depth: 4,
|
|
341
|
+
objectKeys: 24,
|
|
342
|
+
stringChars: 600
|
|
343
|
+
})
|
|
344
|
+
} : {})
|
|
345
|
+
};
|
|
346
|
+
}
|
|
90
347
|
function compactEvidenceValue(value, options) {
|
|
91
348
|
if (value === null || value === undefined) {
|
|
92
349
|
return value;
|
|
@@ -298,71 +555,276 @@ export function limitPreConsentRows(payload, options = {}) {
|
|
|
298
555
|
};
|
|
299
556
|
}
|
|
300
557
|
export function buildScanBundle(input) {
|
|
301
|
-
const
|
|
558
|
+
const detail = input.detail ?? "summary";
|
|
559
|
+
const maxFindings = Math.min(50, Math.max(1, input.maxFindings ?? (detail === "summary" ? 5 : 20)));
|
|
302
560
|
const maxPreConsentRows = Math.min(50, Math.max(1, input.maxPreConsentRows ?? 20));
|
|
303
|
-
const evidence = input.evidence;
|
|
304
|
-
const report = input.report;
|
|
305
|
-
const
|
|
306
|
-
const
|
|
561
|
+
const evidence = (input.evidence ?? {});
|
|
562
|
+
const report = (input.report ?? {});
|
|
563
|
+
const allFindings = Array.isArray(input.findings.findings) ? input.findings.findings : [];
|
|
564
|
+
const findings = detail === "summary"
|
|
565
|
+
? []
|
|
566
|
+
: allFindings.slice(0, maxFindings).map((finding) => detail === "full" ? finding : compactBundleFinding(finding));
|
|
567
|
+
const preConsentRows = Array.isArray(input.preConsentCookiesTrackers?.rows)
|
|
307
568
|
? input.preConsentCookiesTrackers.rows.slice(0, maxPreConsentRows)
|
|
308
569
|
: [];
|
|
309
570
|
const links = {
|
|
310
571
|
...(input.scan.links ?? {}),
|
|
311
572
|
...(report.links && typeof report.links === "object" && !Array.isArray(report.links) ? report.links : {})
|
|
312
573
|
};
|
|
313
|
-
|
|
574
|
+
const maxBytes = Math.min(200_000, Math.max(5_000, input.maxBytes ?? 50_000));
|
|
575
|
+
const contentUrls = Object.fromEntries(Object.entries({
|
|
576
|
+
report: input.scan.links?.report ?? links.report,
|
|
577
|
+
findings: links.findings,
|
|
578
|
+
evidence: links.pulse,
|
|
579
|
+
preConsentCookiesTrackers: links.preConsentCookiesTrackers
|
|
580
|
+
}).filter(([, value]) => typeof value === "string"));
|
|
581
|
+
const intentionallyOmitted = new Set();
|
|
582
|
+
if (detail === "summary" && allFindings.length > 0)
|
|
583
|
+
intentionallyOmitted.add("findings");
|
|
584
|
+
if ((detail === "summary" || detail === "findings") && (Object.keys(evidence).length > 0 || allFindings.length > 0))
|
|
585
|
+
intentionallyOmitted.add("evidence");
|
|
586
|
+
if (detail !== "full" && Object.keys(report).length > 0)
|
|
587
|
+
intentionallyOmitted.add("fullReport");
|
|
588
|
+
const guidedScan = withMcpAgentGuidance(input.scan);
|
|
589
|
+
const bundle = {
|
|
314
590
|
type: "certscore_scan_bundle",
|
|
591
|
+
detail,
|
|
315
592
|
scanId: input.scan.scanId,
|
|
593
|
+
domain: input.scan.domain,
|
|
594
|
+
url: input.scan.url ?? null,
|
|
316
595
|
status: input.scan.status,
|
|
596
|
+
score: input.scan.score ?? null,
|
|
597
|
+
scoreStatus: input.scan.scoreStatus ?? "final",
|
|
598
|
+
scoreVersion: input.scan.scoreVersion ?? null,
|
|
599
|
+
scoreUpdatedAt: input.scan.scoreUpdatedAt ?? null,
|
|
600
|
+
riskLevel: input.scan.riskLevel ?? null,
|
|
317
601
|
resultDisposition: input.scan.resultDisposition ?? null,
|
|
318
602
|
noGo: input.scan.noGo ?? null,
|
|
319
|
-
|
|
603
|
+
coverage: input.scan.coverage ?? null,
|
|
604
|
+
createdAt: input.scan.createdAt ?? null,
|
|
605
|
+
startedAt: input.scan.startedAt ?? null,
|
|
606
|
+
completedAt: input.scan.completedAt ?? null,
|
|
607
|
+
scanTimeSeconds: input.scan.scanTimeSeconds ?? null,
|
|
608
|
+
timing: {
|
|
609
|
+
createdAt: input.scan.createdAt ?? null,
|
|
610
|
+
startedAt: input.scan.startedAt ?? null,
|
|
611
|
+
completedAt: input.scan.completedAt ?? null,
|
|
612
|
+
scanTimeSeconds: input.scan.scanTimeSeconds ?? null
|
|
613
|
+
},
|
|
320
614
|
summary: {
|
|
321
|
-
|
|
615
|
+
headline: report.summary && typeof report.summary === "object" && !Array.isArray(report.summary)
|
|
616
|
+
? report.summary.headline ?? null
|
|
617
|
+
: null,
|
|
322
618
|
executiveSummary: report.executiveSummary ?? null,
|
|
323
619
|
counts: report.counts ?? null,
|
|
324
|
-
coverage: report.coverage ?? input.scan.coverage ?? null,
|
|
325
620
|
agentInterpretation: report.agentInterpretation ?? null
|
|
326
621
|
},
|
|
327
622
|
findings,
|
|
328
623
|
findingsMetadata: {
|
|
329
624
|
shown: findings.length,
|
|
330
|
-
total:
|
|
331
|
-
truncated:
|
|
625
|
+
total: allFindings.length,
|
|
626
|
+
truncated: allFindings.length > findings.length
|
|
332
627
|
},
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
coverageDiagnostics: compactEvidenceValue(evidence.coverageDiagnostics ?? null, {
|
|
343
|
-
arrayItems: 20,
|
|
344
|
-
depth: 5,
|
|
345
|
-
objectKeys: 40,
|
|
346
|
-
stringChars: 1_000
|
|
347
|
-
}),
|
|
348
|
-
policySurfaceCoverage: compactEvidenceValue(evidence.policySurfaceCoverage ?? null, {
|
|
349
|
-
arrayItems: 20,
|
|
350
|
-
depth: 5,
|
|
351
|
-
objectKeys: 40,
|
|
352
|
-
stringChars: 1_000
|
|
628
|
+
...(detail === "evidence" || detail === "full"
|
|
629
|
+
? { evidenceSummary: bundleEvidenceSummary(evidence, allFindings, links, detail === "full") }
|
|
630
|
+
: {}),
|
|
631
|
+
...(detail === "full" ? {
|
|
632
|
+
fullReport: compactEvidenceValue(report, {
|
|
633
|
+
arrayItems: 50,
|
|
634
|
+
depth: 8,
|
|
635
|
+
objectKeys: 100,
|
|
636
|
+
stringChars: 4_000
|
|
353
637
|
})
|
|
354
|
-
},
|
|
355
|
-
preConsentCookiesTrackers: {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
638
|
+
} : {}),
|
|
639
|
+
...(input.preConsentCookiesTrackers ? { preConsentCookiesTrackers: {
|
|
640
|
+
summary: input.preConsentCookiesTrackers.summary,
|
|
641
|
+
rows: preConsentRows,
|
|
642
|
+
shown: preConsentRows.length,
|
|
643
|
+
total: Array.isArray(input.preConsentCookiesTrackers.rows) ? input.preConsentCookiesTrackers.rows.length : 0,
|
|
644
|
+
truncated: Array.isArray(input.preConsentCookiesTrackers.rows) && input.preConsentCookiesTrackers.rows.length > preConsentRows.length
|
|
645
|
+
} } : {}),
|
|
362
646
|
links,
|
|
363
|
-
|
|
364
|
-
|
|
647
|
+
reportUrl: input.scan.links?.report ?? (typeof links.report === "string" ? links.report : null),
|
|
648
|
+
recommendedNextTool: null,
|
|
649
|
+
recommendedNextAction: guidedScan.error?.recommendedNextAction ?? (detail === "summary" && allFindings.length > 0
|
|
650
|
+
? "Review this overview, then request detail=findings for bounded finding objects or open the report URL."
|
|
651
|
+
: findings.length > 0
|
|
652
|
+
? "Review the returned findings and follow their evidence references. Use detail=evidence only when deeper retained context is needed."
|
|
653
|
+
: "Review coverage and limitations before interpreting the absence of findings."),
|
|
654
|
+
error: guidedScan.error,
|
|
655
|
+
mcpMetadata: {
|
|
656
|
+
detail,
|
|
657
|
+
heavyEvidenceIncluded: detail === "evidence" || detail === "full",
|
|
658
|
+
findingsTruncated: allFindings.length > findings.length,
|
|
659
|
+
requestedMaxBytes: maxBytes,
|
|
660
|
+
actualBytes: 0,
|
|
661
|
+
truncated: false,
|
|
662
|
+
truncationReason: null,
|
|
663
|
+
omittedSections: [...intentionallyOmitted],
|
|
664
|
+
nextRecommendedMaxBytes: null,
|
|
665
|
+
omittedContentAvailableViaUrl: intentionallyOmitted.size > 0 && Object.keys(contentUrls).length > 0,
|
|
666
|
+
contentUrls
|
|
667
|
+
},
|
|
668
|
+
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER,
|
|
669
|
+
disclaimer: input.scan.disclaimer ?? input.report?.disclaimer ?? OBSERVATION_ONLY_DISCLAIMER
|
|
670
|
+
};
|
|
671
|
+
const recommendationCandidates = [];
|
|
672
|
+
const markBudgetOmitted = (section, reason, requiredBytes = bundle.mcpMetadata.actualBytes) => {
|
|
673
|
+
bundle.mcpMetadata.truncated = true;
|
|
674
|
+
bundle.mcpMetadata.truncationReason ??= reason;
|
|
675
|
+
if (!bundle.mcpMetadata.omittedSections.includes(section)) {
|
|
676
|
+
bundle.mcpMetadata.omittedSections.push(section);
|
|
677
|
+
}
|
|
678
|
+
recommendationCandidates.push(requiredBytes);
|
|
679
|
+
bundle.mcpMetadata.omittedContentAvailableViaUrl = Object.keys(contentUrls).length > 0;
|
|
365
680
|
};
|
|
681
|
+
const refresh = () => updateBundleActualBytes(bundle);
|
|
682
|
+
refresh();
|
|
683
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.fullReport) {
|
|
684
|
+
markBudgetOmitted("fullReport", "full_report_omitted_to_byte_limit");
|
|
685
|
+
delete bundle.fullReport;
|
|
686
|
+
refresh();
|
|
687
|
+
}
|
|
688
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && detail === "full" && bundle.evidenceSummary) {
|
|
689
|
+
const compactSummary = bundleEvidenceSummary(evidence, allFindings, links, false);
|
|
690
|
+
markBudgetOmitted("evidenceDiagnostics", "evidence_diagnostics_omitted_to_byte_limit");
|
|
691
|
+
bundle.evidenceSummary = compactSummary;
|
|
692
|
+
refresh();
|
|
693
|
+
}
|
|
694
|
+
const inventoryRows = bundle.preConsentCookiesTrackers?.rows;
|
|
695
|
+
while (bundle.mcpMetadata.actualBytes > maxBytes && Array.isArray(inventoryRows) && inventoryRows.length > 0) {
|
|
696
|
+
markBudgetOmitted("preConsentCookiesTrackers", "evidence_inventory_reduced_to_byte_limit");
|
|
697
|
+
inventoryRows.pop();
|
|
698
|
+
bundle.preConsentCookiesTrackers.shown = inventoryRows.length;
|
|
699
|
+
bundle.preConsentCookiesTrackers.truncated = true;
|
|
700
|
+
refresh();
|
|
701
|
+
}
|
|
702
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.preConsentCookiesTrackers) {
|
|
703
|
+
markBudgetOmitted("preConsentCookiesTrackers", "evidence_inventory_omitted_to_byte_limit");
|
|
704
|
+
delete bundle.preConsentCookiesTrackers;
|
|
705
|
+
refresh();
|
|
706
|
+
}
|
|
707
|
+
while (bundle.mcpMetadata.actualBytes > maxBytes && bundle.findings.length > 1) {
|
|
708
|
+
markBudgetOmitted("additionalFindings", "findings_reduced_to_byte_limit");
|
|
709
|
+
bundle.findings.pop();
|
|
710
|
+
bundle.findingsMetadata.shown = bundle.findings.length;
|
|
711
|
+
bundle.findingsMetadata.truncated = true;
|
|
712
|
+
bundle.mcpMetadata.findingsTruncated = true;
|
|
713
|
+
refresh();
|
|
714
|
+
}
|
|
715
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes) {
|
|
716
|
+
markBudgetOmitted("summaryDetail", "summary_compacted_to_byte_limit");
|
|
717
|
+
bundle.summary = {
|
|
718
|
+
headline: boundedText(bundle.summary?.headline, 240) ?? null,
|
|
719
|
+
executiveSummary: null,
|
|
720
|
+
counts: bundle.summary?.counts ? { totalAutomatedFindingCount: bundle.findingsMetadata.total } : null,
|
|
721
|
+
agentInterpretation: null
|
|
722
|
+
};
|
|
723
|
+
refresh();
|
|
724
|
+
}
|
|
725
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes) {
|
|
726
|
+
markBudgetOmitted("coverageDetail", "coverage_compacted_to_byte_limit");
|
|
727
|
+
bundle.coverage = compactEvidenceValue(bundle.coverage, {
|
|
728
|
+
arrayItems: 3,
|
|
729
|
+
depth: 3,
|
|
730
|
+
objectKeys: 8,
|
|
731
|
+
stringChars: 240
|
|
732
|
+
});
|
|
733
|
+
refresh();
|
|
734
|
+
}
|
|
735
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.links) {
|
|
736
|
+
markBudgetOmitted("additionalLinks", "links_compacted_to_byte_limit");
|
|
737
|
+
bundle.links = Object.fromEntries(Object.entries(bundle.links).filter(([key]) => ["self", "report"].includes(key)));
|
|
738
|
+
refresh();
|
|
739
|
+
}
|
|
740
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.evidenceSummary) {
|
|
741
|
+
markBudgetOmitted("evidenceDetail", "evidence_compacted_to_preserve_priority_content");
|
|
742
|
+
bundle.evidenceSummary = compactPriorityEvidenceSummary(bundle.evidenceSummary);
|
|
743
|
+
refresh();
|
|
744
|
+
}
|
|
745
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.disclaimer === OBSERVATION_ONLY_DISCLAIMER) {
|
|
746
|
+
markBudgetOmitted("duplicateDisclaimer", "duplicate_disclaimer_omitted_to_byte_limit");
|
|
747
|
+
bundle.disclaimer = null;
|
|
748
|
+
refresh();
|
|
749
|
+
}
|
|
750
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.findings.length > 0) {
|
|
751
|
+
markBudgetOmitted("findings", "finding_omitted_to_byte_limit");
|
|
752
|
+
bundle.findings = [];
|
|
753
|
+
bundle.findingsMetadata.shown = 0;
|
|
754
|
+
bundle.findingsMetadata.truncated = bundle.findingsMetadata.total > 0;
|
|
755
|
+
bundle.mcpMetadata.findingsTruncated = bundle.findingsMetadata.total > 0;
|
|
756
|
+
refresh();
|
|
757
|
+
}
|
|
758
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.evidenceSummary) {
|
|
759
|
+
markBudgetOmitted("evidence", "evidence_digest_omitted_to_byte_limit");
|
|
760
|
+
delete bundle.evidenceSummary;
|
|
761
|
+
bundle.mcpMetadata.heavyEvidenceIncluded = false;
|
|
762
|
+
refresh();
|
|
763
|
+
}
|
|
764
|
+
if (bundle.mcpMetadata.truncated) {
|
|
765
|
+
const required = Math.min(...recommendationCandidates.filter((value) => Number.isFinite(value) && value > maxBytes));
|
|
766
|
+
bundle.mcpMetadata.nextRecommendedMaxBytes = Math.min(200_000, Math.max(maxBytes + 1_000, Math.ceil((Number.isFinite(required) ? required : maxBytes + 1_000) / 1_000) * 1_000));
|
|
767
|
+
bundle.recommendedNextAction = `Retry with maxBytes=${bundle.mcpMetadata.nextRecommendedMaxBytes} or open ${bundle.reportUrl ? "the report URL" : "an available content URL"}.`;
|
|
768
|
+
refresh();
|
|
769
|
+
}
|
|
770
|
+
if (bundle.mcpMetadata.actualBytes > maxBytes) {
|
|
771
|
+
const minimal = {
|
|
772
|
+
type: bundle.type,
|
|
773
|
+
detail: bundle.detail,
|
|
774
|
+
scanId: bundle.scanId,
|
|
775
|
+
domain: bundle.domain,
|
|
776
|
+
url: bundle.url,
|
|
777
|
+
status: bundle.status,
|
|
778
|
+
score: bundle.score,
|
|
779
|
+
scoreStatus: bundle.scoreStatus,
|
|
780
|
+
scoreVersion: bundle.scoreVersion,
|
|
781
|
+
scoreUpdatedAt: bundle.scoreUpdatedAt,
|
|
782
|
+
riskLevel: bundle.riskLevel,
|
|
783
|
+
resultDisposition: bundle.resultDisposition,
|
|
784
|
+
noGo: bundle.noGo,
|
|
785
|
+
coverage: null,
|
|
786
|
+
createdAt: bundle.createdAt,
|
|
787
|
+
startedAt: bundle.startedAt,
|
|
788
|
+
completedAt: bundle.completedAt,
|
|
789
|
+
scanTimeSeconds: bundle.scanTimeSeconds,
|
|
790
|
+
timing: bundle.timing,
|
|
791
|
+
summary: {
|
|
792
|
+
headline: bundle.summary?.headline ?? null,
|
|
793
|
+
executiveSummary: null,
|
|
794
|
+
counts: bundle.summary?.counts ?? null,
|
|
795
|
+
agentInterpretation: null
|
|
796
|
+
},
|
|
797
|
+
findings: [],
|
|
798
|
+
findingsMetadata: {
|
|
799
|
+
shown: 0,
|
|
800
|
+
total: bundle.findingsMetadata.total,
|
|
801
|
+
truncated: bundle.findingsMetadata.total > 0
|
|
802
|
+
},
|
|
803
|
+
links: Object.fromEntries(Object.entries(bundle.links ?? {}).filter(([key]) => ["docs", "report", "self"].includes(key))),
|
|
804
|
+
reportUrl: bundle.reportUrl,
|
|
805
|
+
recommendedNextTool: null,
|
|
806
|
+
recommendedNextAction: bundle.recommendedNextAction,
|
|
807
|
+
error: bundle.error,
|
|
808
|
+
mcpMetadata: {
|
|
809
|
+
detail,
|
|
810
|
+
heavyEvidenceIncluded: false,
|
|
811
|
+
findingsTruncated: bundle.findingsMetadata.total > 0,
|
|
812
|
+
requestedMaxBytes: maxBytes,
|
|
813
|
+
actualBytes: 0,
|
|
814
|
+
truncated: true,
|
|
815
|
+
truncationReason: "minimal_canonical_result_returned_to_byte_limit",
|
|
816
|
+
omittedSections: [...new Set([...bundle.mcpMetadata.omittedSections, "summaryDetail", "findings", "evidence"])],
|
|
817
|
+
nextRecommendedMaxBytes: bundle.mcpMetadata.nextRecommendedMaxBytes ?? Math.min(200_000, maxBytes + 1_000),
|
|
818
|
+
omittedContentAvailableViaUrl: Object.keys(contentUrls).length > 0,
|
|
819
|
+
contentUrls
|
|
820
|
+
},
|
|
821
|
+
observationOnlyDisclaimer: bundle.observationOnlyDisclaimer,
|
|
822
|
+
disclaimer: bundle.disclaimer
|
|
823
|
+
};
|
|
824
|
+
updateBundleActualBytes(minimal);
|
|
825
|
+
return minimal;
|
|
826
|
+
}
|
|
827
|
+
return bundle;
|
|
366
828
|
}
|
|
367
829
|
export function explainFinding(report, findingId) {
|
|
368
830
|
const finding = findingsFromReport(report).find((candidate) => candidate.id === findingId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@certscore/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"mcpName": "ai.certscore/mcp",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "MCP server for CertScore public website risk-signal workflows.",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"dist",
|
|
32
32
|
"README.md",
|
|
33
33
|
"LICENSE",
|
|
34
|
-
"server.json"
|
|
34
|
+
"server.json",
|
|
35
|
+
"server-light.json"
|
|
35
36
|
],
|
|
36
37
|
"publishConfig": {
|
|
37
38
|
"access": "public"
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
],
|
|
64
65
|
"license": "proprietary",
|
|
65
66
|
"dependencies": {
|
|
66
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
67
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
67
68
|
"zod": "^3.25.76"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|