@certscore/mcp 0.2.18 → 0.2.20
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 +14 -1
- package/dist/certscore-mcp.mjs +4938 -192
- package/dist/server.d.ts +29 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +108 -30
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +56 -3
- package/package.json +3 -2
- package/server-light.json +1 -1
- package/server.json +2 -2
package/dist/server.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { type McpCallerInput } from "@website-signal-risk-scanner/shared/dist/mcp-caller-input.js";
|
|
1
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import type
|
|
3
|
+
import { type RequestInfo } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { type McpTaskContext } from "@website-signal-risk-scanner/shared/dist/mcp-product-context.js";
|
|
3
5
|
export interface CertScoreMcpOptions {
|
|
4
6
|
apiKey?: string;
|
|
5
7
|
baseUrl?: string;
|
|
@@ -18,6 +20,27 @@ export type McpToolInvocationRequestContext = {
|
|
|
18
20
|
headers: RequestInfo["headers"] | null;
|
|
19
21
|
};
|
|
20
22
|
export type McpToolInvocationObservation = {
|
|
23
|
+
callerInput?: McpCallerInput;
|
|
24
|
+
captureBasis?: "protocol_request";
|
|
25
|
+
taskContext?: McpTaskContext;
|
|
26
|
+
response?: {
|
|
27
|
+
bytes: number;
|
|
28
|
+
truncated: boolean | null;
|
|
29
|
+
effectiveMaxBytes?: number;
|
|
30
|
+
};
|
|
31
|
+
requestArguments?: {
|
|
32
|
+
values: Record<string, string | number | boolean>;
|
|
33
|
+
omitted: boolean;
|
|
34
|
+
};
|
|
35
|
+
rateLimit?: {
|
|
36
|
+
kind: "scan_creation" | "upstream";
|
|
37
|
+
retryAfterSeconds?: number;
|
|
38
|
+
scope?: string;
|
|
39
|
+
windowId?: string;
|
|
40
|
+
limit?: number;
|
|
41
|
+
used?: number;
|
|
42
|
+
windowSeconds?: number;
|
|
43
|
+
};
|
|
21
44
|
durationMs: number;
|
|
22
45
|
errorCode: string | null;
|
|
23
46
|
freshness: "latest" | "refresh" | null;
|
|
@@ -34,6 +57,11 @@ export type McpToolInvocationObservation = {
|
|
|
34
57
|
toolName: string;
|
|
35
58
|
transportOutcome: "mcp_result" | "mcp_error";
|
|
36
59
|
};
|
|
60
|
+
/** Retain only bounded tool options; URL paths, queries and unknown/free-text inputs are omitted. */
|
|
61
|
+
export declare function projectMcpTelemetryArguments(args: Record<string, unknown>): {
|
|
62
|
+
values: Record<string, string | number | boolean>;
|
|
63
|
+
omitted: boolean;
|
|
64
|
+
};
|
|
37
65
|
export declare function projectMcpToolInvocationObservation(input: {
|
|
38
66
|
args: unknown;
|
|
39
67
|
durationMs: number;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { captureMcpCallerInput } from "@website-signal-risk-scanner/shared/dist/mcp-caller-input.js";
|
|
1
2
|
import { CertScoreClient } from "@certscore/sdk";
|
|
2
3
|
import { certScoreMcpToolContracts, isCanonicalScanId } from "@certscore/api-contracts";
|
|
3
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { sanitizeMcpTaskContext } from "@website-signal-risk-scanner/shared/dist/mcp-product-context.js";
|
|
4
7
|
import { CERTSCORE_MCP_VERSION } from "./version.js";
|
|
5
8
|
import { boundEvidencePacket, buildScanBundle, exportFindings, findingListText, limitPreConsentRows, markdownReportText, MAX_EVIDENCE_PACKET_CHARS, normalizeDetail, normalizeFormat, paginateFindingList, preConsentInventoryText, pulseReportText, scanBundleText, scanSiteText, scanStatusText, toInvalidArgumentsToolError, toInvalidScanIdToolError, toToolError, toToolResult, withMcpAgentGuidance, withMcpScanProvenanceGuidance } from "./tools.js";
|
|
6
9
|
const LIGHT_MCP_BUNDLE_RESPONSE_CEILING_BYTES = 25_000;
|
|
@@ -159,6 +162,38 @@ function isCertScoreCanaryUrl(value) {
|
|
|
159
162
|
return false;
|
|
160
163
|
}
|
|
161
164
|
}
|
|
165
|
+
/** Retain only bounded tool options; URL paths, queries and unknown/free-text inputs are omitted. */
|
|
166
|
+
export function projectMcpTelemetryArguments(args) {
|
|
167
|
+
const values = {};
|
|
168
|
+
const options = {
|
|
169
|
+
freshness: ["latest", "refresh"], scanFrom: ["eu_de", "eu_ie", "california"],
|
|
170
|
+
detail: ["tiny", "quick", "standard", "full", "summary", "evidence", "findings"], format: ["json", "markdown"],
|
|
171
|
+
};
|
|
172
|
+
for (const [key, allowed] of Object.entries(options)) {
|
|
173
|
+
if (typeof args[key] === "string" && allowed.includes(args[key]))
|
|
174
|
+
values[key] = args[key];
|
|
175
|
+
}
|
|
176
|
+
for (const key of ["scanId", "jobId", "findingId"]) {
|
|
177
|
+
const token = boundedTelemetryToken(args[key], 128);
|
|
178
|
+
if (token)
|
|
179
|
+
values[key] = token;
|
|
180
|
+
}
|
|
181
|
+
for (const key of ["maxWaitSeconds", "maxBytes", "maxFindings", "maxPreConsentRows", "maxRows", "limit", "offset"]) {
|
|
182
|
+
const value = args[key];
|
|
183
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 1_000_000)
|
|
184
|
+
values[key] = value;
|
|
185
|
+
}
|
|
186
|
+
if (typeof args.waitForCompletion === "boolean")
|
|
187
|
+
values.waitForCompletion = args.waitForCompletion;
|
|
188
|
+
const url = telemetryUrl(args.url);
|
|
189
|
+
if (url)
|
|
190
|
+
values.url = url;
|
|
191
|
+
const domain = telemetryHostname(args.domain);
|
|
192
|
+
if (domain && /^[a-z0-9.-]+$/.test(domain))
|
|
193
|
+
values.domain = domain;
|
|
194
|
+
const omitted = Object.keys(args).some((key) => !(key in values) || args[key] !== values[key]);
|
|
195
|
+
return { values, omitted };
|
|
196
|
+
}
|
|
162
197
|
export function projectMcpToolInvocationObservation(input) {
|
|
163
198
|
const args = input.args && typeof input.args === "object" && !Array.isArray(input.args)
|
|
164
199
|
? input.args
|
|
@@ -192,6 +227,8 @@ export function projectMcpToolInvocationObservation(input) {
|
|
|
192
227
|
? "new"
|
|
193
228
|
: "unavailable";
|
|
194
229
|
return {
|
|
230
|
+
requestArguments: projectMcpTelemetryArguments(args),
|
|
231
|
+
...(rateLimited ? { rateLimit: projectMcpRateLimit(error ?? result) } : {}),
|
|
195
232
|
durationMs: Math.max(0, Math.min(Math.round(input.durationMs), 3_600_000)),
|
|
196
233
|
errorCode: rateLimited ? "rate_limited" : errorCode,
|
|
197
234
|
freshness: args.freshness === "refresh" ? "refresh" : input.toolName === "certscore_scan_site" ? "latest" : null,
|
|
@@ -212,6 +249,22 @@ export function projectMcpToolInvocationObservation(input) {
|
|
|
212
249
|
transportOutcome: isError ? "mcp_error" : "mcp_result",
|
|
213
250
|
};
|
|
214
251
|
}
|
|
252
|
+
function projectMcpRateLimit(error) {
|
|
253
|
+
const creation = error.creationRateLimit && typeof error.creationRateLimit === "object" && !Array.isArray(error.creationRateLimit)
|
|
254
|
+
? error.creationRateLimit : null;
|
|
255
|
+
const projected = { kind: creation ? "scan_creation" : "upstream" };
|
|
256
|
+
for (const key of ["scope", "windowId"]) {
|
|
257
|
+
const value = boundedTelemetryToken(creation?.[key], 128);
|
|
258
|
+
if (value)
|
|
259
|
+
projected[key] = value;
|
|
260
|
+
}
|
|
261
|
+
for (const key of ["retryAfterSeconds", "limit", "used", "windowSeconds"]) {
|
|
262
|
+
const value = key === "retryAfterSeconds" ? error[key] : creation?.[key];
|
|
263
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 1_000_000)
|
|
264
|
+
projected[key] = value;
|
|
265
|
+
}
|
|
266
|
+
return projected;
|
|
267
|
+
}
|
|
215
268
|
function observeToolInvocation(observer, observation, requestContext) {
|
|
216
269
|
if (!observer)
|
|
217
270
|
return;
|
|
@@ -259,43 +312,67 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
259
312
|
"certscore_get_scan_status",
|
|
260
313
|
"certscore_list_findings",
|
|
261
314
|
]);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
315
|
+
// Intercept SDK registration through its public API so lookup/validation errors
|
|
316
|
+
// and original arguments are observed once, before SDK normalization. Restore
|
|
317
|
+
// the registration method after constructing this server; no private handler map.
|
|
318
|
+
const registerRequest = server.server.setRequestHandler.bind(server.server);
|
|
319
|
+
server.server.setRequestHandler = ((schema, handler) => {
|
|
320
|
+
if (schema !== CallToolRequestSchema)
|
|
321
|
+
return registerRequest(schema, handler);
|
|
322
|
+
return registerRequest(CallToolRequestSchema, async (request, extra) => {
|
|
269
323
|
const startedAt = Date.now();
|
|
324
|
+
const name = request.params.name;
|
|
325
|
+
const args = request.params.arguments ?? {};
|
|
326
|
+
const known = certScoreMcpToolContracts.some(tool => tool.name === name)
|
|
327
|
+
&& (options.toolProfile !== "light" || lightTools.has(name));
|
|
328
|
+
let result;
|
|
270
329
|
try {
|
|
271
|
-
|
|
272
|
-
? input.scanId
|
|
273
|
-
: null;
|
|
274
|
-
const result = scanIdTools.has(name) && !isCanonicalScanId(scanId)
|
|
275
|
-
? toInvalidScanIdToolError()
|
|
276
|
-
: await typedHandler(input, extra);
|
|
277
|
-
observeToolInvocation(options.onToolInvocation, projectMcpToolInvocationObservation({
|
|
278
|
-
args: input,
|
|
279
|
-
durationMs: Date.now() - startedAt,
|
|
280
|
-
result,
|
|
281
|
-
toolName: name,
|
|
282
|
-
}), { headers: extra.requestInfo?.headers ?? null });
|
|
330
|
+
result = await handler(request, extra);
|
|
283
331
|
return result;
|
|
284
332
|
}
|
|
285
333
|
catch (error) {
|
|
286
|
-
|
|
287
|
-
...projectMcpToolInvocationObservation({
|
|
288
|
-
args: input,
|
|
289
|
-
durationMs: Date.now() - startedAt,
|
|
290
|
-
result: { isError: true, structuredContent: { error: { code: "handler_exception" } } },
|
|
291
|
-
toolName: name,
|
|
292
|
-
}),
|
|
293
|
-
errorCode: "handler_exception",
|
|
294
|
-
outcome: "error",
|
|
295
|
-
transportOutcome: "mcp_error",
|
|
296
|
-
}, { headers: extra.requestInfo?.headers ?? null });
|
|
334
|
+
result = { isError: true, structuredContent: { error: { code: "handler_exception" } } };
|
|
297
335
|
throw error;
|
|
298
336
|
}
|
|
337
|
+
finally {
|
|
338
|
+
try {
|
|
339
|
+
const observation = projectMcpToolInvocationObservation({ args, durationMs: Date.now() - startedAt, result, toolName: name });
|
|
340
|
+
if (!known)
|
|
341
|
+
observation.errorCode = "unknown_tool";
|
|
342
|
+
else if (observation.outcome === "error" && !observation.errorCode)
|
|
343
|
+
observation.errorCode = "protocol_error";
|
|
344
|
+
const payload = telemetryResultRecord(result);
|
|
345
|
+
const metadata = payload.mcpMetadata;
|
|
346
|
+
const taskContext = sanitizeMcpTaskContext(args.taskContext);
|
|
347
|
+
observeToolInvocation(options.onToolInvocation, {
|
|
348
|
+
...observation,
|
|
349
|
+
captureBasis: "protocol_request",
|
|
350
|
+
callerInput: captureMcpCallerInput(args, request.params._meta),
|
|
351
|
+
...(taskContext ? { taskContext } : {}),
|
|
352
|
+
response: {
|
|
353
|
+
bytes: Math.min(10_000_000, Buffer.byteLength(JSON.stringify(result ?? null))),
|
|
354
|
+
truncated: typeof metadata?.truncated === "boolean" ? metadata.truncated : null,
|
|
355
|
+
...(typeof metadata?.effectiveMaxBytes === "number" ? { effectiveMaxBytes: metadata.effectiveMaxBytes } : {}),
|
|
356
|
+
},
|
|
357
|
+
}, { headers: extra.requestInfo?.headers ?? null });
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// Best-effort telemetry must never replace a tool response or its error.
|
|
361
|
+
console.error("[certscore-mcp] telemetry projection failed");
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
const registerMcpTool = server.registerTool.bind(server);
|
|
367
|
+
const registerTool = (name, contract, handler) => {
|
|
368
|
+
if (options.toolProfile === "light" && !lightTools.has(name))
|
|
369
|
+
return;
|
|
370
|
+
const typedHandler = handler;
|
|
371
|
+
registerMcpTool(name, contract, async (input, extra) => {
|
|
372
|
+
const scanId = input && typeof input === "object" && !Array.isArray(input)
|
|
373
|
+
? input.scanId : null;
|
|
374
|
+
return scanIdTools.has(name) && !isCanonicalScanId(scanId)
|
|
375
|
+
? toInvalidScanIdToolError() : typedHandler(input, extra);
|
|
299
376
|
});
|
|
300
377
|
};
|
|
301
378
|
registerTool("certscore_scan_site", toolContract("certscore_scan_site"), async (input, extra) => {
|
|
@@ -560,5 +637,6 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
560
637
|
return toToolError(error);
|
|
561
638
|
}
|
|
562
639
|
});
|
|
640
|
+
server.server.setRequestHandler = registerRequest;
|
|
563
641
|
return server;
|
|
564
642
|
}
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAkB,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,yBAAyB,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAG5M,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAiBjD,KAAK,sBAAsB,GAAG,eAAe,GAAG,aAAa,GAAG,UAAU,CAAC;AAE3E,KAAK,kBAAkB,GAAG,kBAAkB,GAAG,gCAAgC,GAAG,yBAAyB,GAAG,SAAS,CAAC;AAcxH,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.js
CHANGED
|
@@ -30,6 +30,9 @@ function toolResultSummary(payload) {
|
|
|
30
30
|
return "CertScore tool call completed. Read structuredContent for the result.";
|
|
31
31
|
}
|
|
32
32
|
const record = payload;
|
|
33
|
+
const noGoText = canonicalNoGoText(record);
|
|
34
|
+
if (noGoText)
|
|
35
|
+
return noGoText;
|
|
33
36
|
const type = typeof record.type === "string" ? record.type : "result";
|
|
34
37
|
const status = typeof record.status === "string" ? `; status=${record.status}` : "";
|
|
35
38
|
const scanId = typeof record.scanId === "string" ? `; scanId=${record.scanId}` : "";
|
|
@@ -991,7 +994,34 @@ function canonicalScanProvenanceText(value) {
|
|
|
991
994
|
const numeric = (field) => typeof field === "number" && Number.isFinite(field) ? String(field) : "unavailable";
|
|
992
995
|
return `Canonical scan provenance: scanId=${present(extractScanId(value))}; scanFrom/execution region=${present(value.scanFrom)}; completedAt=${present(value.completedAt)}; startedAt=${present(value.startedAt)}; createdAt=${present(value.createdAt)}; retrieval mode=${present(value.provenance?.retrievalMode)}; original creation decision=${present(value.provenance?.creationDecision)}; scan age seconds=${numeric(value.provenance?.scanAgeSeconds)}; compatibility provenance mode=${present(value.provenance?.mode)}.`;
|
|
993
996
|
}
|
|
997
|
+
/** Present only the API's retained disposition; never infer blockers from raw evidence. */
|
|
998
|
+
function canonicalNoGo(value) {
|
|
999
|
+
return value.resultDisposition === "no_go" && value.noGo && typeof value.noGo === "object" && !Array.isArray(value.noGo)
|
|
1000
|
+
? value.noGo
|
|
1001
|
+
: null;
|
|
1002
|
+
}
|
|
1003
|
+
function canonicalNoGoText(value) {
|
|
1004
|
+
const noGo = canonicalNoGo(value);
|
|
1005
|
+
if (!noGo)
|
|
1006
|
+
return null;
|
|
1007
|
+
return [
|
|
1008
|
+
`CertScore scan: ${boundedText(noGo.title, 240) ?? "Scan access limitation"}; status=${value.status ?? "completed_limited"}; Not scored.`,
|
|
1009
|
+
boundedText(noGo.explanation, 1_200),
|
|
1010
|
+
boundedText(noGo.summary, 600),
|
|
1011
|
+
noGo.evidenceExcerpt ? `Retained evidence: ${boundedText(noGo.evidenceExcerpt, 1_200)}` : null,
|
|
1012
|
+
noGo.recommendedNextAction ? `Next: ${boundedText(noGo.recommendedNextAction, 1_000)}` : null,
|
|
1013
|
+
typeof noGo.retryLikelyToHelp === "boolean" ? `Retry likely to help: ${noGo.retryLikelyToHelp ? "yes" : "no"}.` : null,
|
|
1014
|
+
value.mcpMetadata?.truncated ? "Additional retained details were omitted to fit the response budget; see omission metadata and the report URL." : null,
|
|
1015
|
+
canonicalScanProvenanceText(value),
|
|
1016
|
+
`Full report: ${reportUrlFor(value) ?? "not available"}.`,
|
|
1017
|
+
OBSERVATION_ONLY_DISCLAIMER,
|
|
1018
|
+
SCAN_PROVENANCE_GROUNDING,
|
|
1019
|
+
].filter(Boolean).join("\n");
|
|
1020
|
+
}
|
|
994
1021
|
export function scanStatusText(value) {
|
|
1022
|
+
const noGoText = canonicalNoGoText(value);
|
|
1023
|
+
if (noGoText)
|
|
1024
|
+
return noGoText;
|
|
995
1025
|
const reportUrl = reportUrlFor(value);
|
|
996
1026
|
const nextAction = typeof value.recommendedNextAction === "string" && value.recommendedNextAction.trim()
|
|
997
1027
|
? value.recommendedNextAction.trim()
|
|
@@ -1008,6 +1038,9 @@ export function scanStatusText(value) {
|
|
|
1008
1038
|
]);
|
|
1009
1039
|
}
|
|
1010
1040
|
export function scanSiteText(value, leadingLines = []) {
|
|
1041
|
+
const noGoText = canonicalNoGoText(value);
|
|
1042
|
+
if (noGoText)
|
|
1043
|
+
return noGoText;
|
|
1011
1044
|
const scanId = extractScanId(value) ?? "unknown";
|
|
1012
1045
|
const active = value.status === "queued" || value.status === "running" || value.status === "finalizing";
|
|
1013
1046
|
const nextAction = typeof value.recommendedNextAction === "string" && value.recommendedNextAction.trim()
|
|
@@ -1203,6 +1236,9 @@ export function preConsentInventoryText(value) {
|
|
|
1203
1236
|
return boundedResultText(`Pre-consent cookie/tracker evidence for ${domain}; scanId=${scanId}; ${returned} of ${total} rows returned${truncated ? " (truncated)" : ""}. Enumerate only these observed rows.`, rows.map((row) => preConsentRowText(compactPreConsentRow(row))), value);
|
|
1204
1237
|
}
|
|
1205
1238
|
export function pulseReportText(value, label = "CertScore report") {
|
|
1239
|
+
const noGoText = canonicalNoGoText(value);
|
|
1240
|
+
if (noGoText)
|
|
1241
|
+
return noGoText;
|
|
1206
1242
|
const scanId = extractScanId(value) ?? "unknown";
|
|
1207
1243
|
const domain = typeof value.domain === "string" ? value.domain : "unknown domain";
|
|
1208
1244
|
const score = typeof value.summary?.score === "number"
|
|
@@ -1226,6 +1262,9 @@ export function markdownReportText(value) {
|
|
|
1226
1262
|
return boundedResultText(`CertScore report for scanId=${extractScanId(value) ?? "unknown"}. The following is a bounded canonical report excerpt.`, excerpt ? [excerpt] : ["No Markdown report body was returned."], value);
|
|
1227
1263
|
}
|
|
1228
1264
|
export function scanBundleText(bundle) {
|
|
1265
|
+
const noGoText = canonicalNoGoText(bundle);
|
|
1266
|
+
if (noGoText)
|
|
1267
|
+
return noGoText;
|
|
1229
1268
|
const score = typeof bundle.score === "number" ? `; CertScore score=${bundle.score}` : "";
|
|
1230
1269
|
const footer = [OBSERVATION_ONLY_DISCLAIMER, SCAN_BUNDLE_INTERPRETATION_STATEMENT];
|
|
1231
1270
|
const lines = [
|
|
@@ -1516,7 +1555,7 @@ export function buildScanBundle(input) {
|
|
|
1516
1555
|
...(detail === "evidence" || detail === "full"
|
|
1517
1556
|
? { evidenceSummary: bundleEvidenceSummary(evidence, allFindings, links, detail === "full") }
|
|
1518
1557
|
: {}),
|
|
1519
|
-
...(detail === "full" ? {
|
|
1558
|
+
...(detail === "full" && Object.keys(report).length > 0 ? {
|
|
1520
1559
|
fullReport: compactEvidenceValue(deduplicatedReport.residual, {
|
|
1521
1560
|
arrayItems: 50,
|
|
1522
1561
|
depth: 8,
|
|
@@ -1588,14 +1627,27 @@ export function buildScanBundle(input) {
|
|
|
1588
1627
|
bundle.mcpMetadata.nextRecommendedMaxBytes = completeBytes <= responseCeilingBytes
|
|
1589
1628
|
? Math.max(5_000, Math.ceil(completeBytes / 1_000) * 1_000)
|
|
1590
1629
|
: null;
|
|
1591
|
-
bundle.recommendedNextAction = canonicalFindingsComplete
|
|
1630
|
+
bundle.recommendedNextAction = canonicalNoGo(bundle)?.recommendedNextAction ?? (canonicalFindingsComplete
|
|
1592
1631
|
? "Canonical findings complete; retry only for omitted envelope detail."
|
|
1593
1632
|
: bundle.mcpMetadata.nextRecommendedMaxBytes
|
|
1594
1633
|
? `Retry with maxBytes=${bundle.mcpMetadata.nextRecommendedMaxBytes} to retrieve the complete requested tier, or open ${bundle.reportUrl ? "the report URL" : "an available content URL"}.`
|
|
1595
|
-
: `The complete requested tier exceeds the MCP byte ceiling; open ${bundle.reportUrl ? "the report URL" : "an available content URL"}
|
|
1634
|
+
: `The complete requested tier exceeds the MCP byte ceiling; open ${bundle.reportUrl ? "the report URL" : "an available content URL"}.`);
|
|
1596
1635
|
refresh();
|
|
1597
1636
|
};
|
|
1598
1637
|
captureFullPayloadBytes();
|
|
1638
|
+
// Access disposition and its remedy outrank optional lane detail in a no-go envelope.
|
|
1639
|
+
// Retained evidence remains available through the reported content URLs.
|
|
1640
|
+
if (canonicalNoGo(bundle)) {
|
|
1641
|
+
for (const section of ["gpcResponse", "postAcceptObservation", "postRefusalObservation"]) {
|
|
1642
|
+
if (bundle.mcpMetadata.actualBytes <= maxBytes)
|
|
1643
|
+
break;
|
|
1644
|
+
if (bundle[section]) {
|
|
1645
|
+
markBudgetOmitted(section, "lane_detail_omitted_to_preserve_no_go");
|
|
1646
|
+
delete bundle[section];
|
|
1647
|
+
refresh();
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1599
1651
|
if (bundle.mcpMetadata.actualBytes > maxBytes && bundle.fullReport) {
|
|
1600
1652
|
markBudgetOmitted("fullReport", "full_report_omitted_to_byte_limit");
|
|
1601
1653
|
delete bundle.fullReport;
|
|
@@ -1728,6 +1780,7 @@ export function buildScanBundle(input) {
|
|
|
1728
1780
|
scanId: bundle.scanId,
|
|
1729
1781
|
domain: bundle.domain,
|
|
1730
1782
|
url: bundle.url,
|
|
1783
|
+
scanFrom: bundle.scanFrom,
|
|
1731
1784
|
status: bundle.status,
|
|
1732
1785
|
score: bundle.score,
|
|
1733
1786
|
scoreLabel: bundle.scoreLabel,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@certscore/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.20",
|
|
4
4
|
"mcpName": "ai.certscore/mcp",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "MCP server for CertScore public website risk-signal workflows.",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@certscore/api-contracts": "workspace:*",
|
|
72
|
-
"@certscore/sdk": "workspace:*"
|
|
72
|
+
"@certscore/sdk": "workspace:*",
|
|
73
|
+
"@website-signal-risk-scanner/shared": "workspace:*"
|
|
73
74
|
}
|
|
74
75
|
}
|
package/server-light.json
CHANGED
package/server.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"url": "https://github.com/ergoveritas1-alt/certscore.ai",
|
|
9
9
|
"source": "github"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.2.
|
|
11
|
+
"version": "0.2.20",
|
|
12
12
|
"remotes": [
|
|
13
13
|
{
|
|
14
14
|
"type": "streamable-http",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"registryType": "npm",
|
|
21
21
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
22
22
|
"identifier": "@certscore/mcp",
|
|
23
|
-
"version": "0.2.
|
|
23
|
+
"version": "0.2.20",
|
|
24
24
|
"transport": {
|
|
25
25
|
"type": "stdio"
|
|
26
26
|
},
|