@certscore/mcp 0.2.16 → 0.2.18
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 +51 -39
- package/dist/certscore-mcp.mjs +518 -141
- package/dist/server.d.ts +1 -10
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +92 -72
- package/dist/tools.d.ts +3 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +225 -12
- package/package.json +1 -1
- package/server-light.json +1 -1
- package/server.json +2 -2
package/dist/server.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface CertScoreMcpOptions {
|
|
|
10
10
|
anonymousSurface?: "mcp_light" | "mcp_anonymous" | null;
|
|
11
11
|
timeout?: number;
|
|
12
12
|
toolProfile?: "full" | "light";
|
|
13
|
+
initialPreConsentPreviewWaitMs?: number;
|
|
13
14
|
exampleDomainDemoUrl?: string | null;
|
|
14
15
|
onToolInvocation?: (observation: McpToolInvocationObservation, requestContext: McpToolInvocationRequestContext) => void | Promise<void>;
|
|
15
16
|
}
|
|
@@ -33,16 +34,6 @@ export type McpToolInvocationObservation = {
|
|
|
33
34
|
toolName: string;
|
|
34
35
|
transportOutcome: "mcp_result" | "mcp_error";
|
|
35
36
|
};
|
|
36
|
-
export declare function resolveMcpScanSiteWaitBudget(input: {
|
|
37
|
-
maxWaitSeconds?: number;
|
|
38
|
-
startedAtMs: number;
|
|
39
|
-
nowMs: number;
|
|
40
|
-
}): {
|
|
41
|
-
elapsedMs: number;
|
|
42
|
-
remainingWaitMs: number;
|
|
43
|
-
responseReserveMs: number;
|
|
44
|
-
totalBudgetMs: number;
|
|
45
|
-
};
|
|
46
37
|
export declare function projectMcpToolInvocationObservation(input: {
|
|
47
38
|
args: unknown;
|
|
48
39
|
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":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAItE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,wBAAwB,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,KAAK,MAAM,GAAG,IAAI,CAAC;IAC9E,gCAAgC,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACvD,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,gBAAgB,CAAC,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,gBAAgB,CAAC,EAAE,CACjB,WAAW,EAAE,4BAA4B,EACzC,cAAc,EAAE,+BAA+B,KAC5C,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAID,MAAM,MAAM,+BAA+B,GAAG;IAC5C,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;CACxC,CAAC;AA4BF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,SAAS,GAAG,OAAO,GAAG,cAAc,CAAC;IAC9C,YAAY,EAAE,SAAS,GAAG,cAAc,CAAC;IACzC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,qBAAqB,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;IACtE,YAAY,EAAE,QAAQ,GAAG,KAAK,GAAG,aAAa,GAAG,gBAAgB,CAAC;IAClE,QAAQ,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,IAAI,CAAC;IAClD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,YAAY,GAAG,WAAW,CAAC;CAC9C,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAItE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,wBAAwB,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,KAAK,MAAM,GAAG,IAAI,CAAC;IAC9E,gCAAgC,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACvD,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,gBAAgB,CAAC,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,gBAAgB,CAAC,EAAE,CACjB,WAAW,EAAE,4BAA4B,EACzC,cAAc,EAAE,+BAA+B,KAC5C,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAID,MAAM,MAAM,+BAA+B,GAAG;IAC5C,OAAO,EAAE,WAAW,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;CACxC,CAAC;AA4BF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,SAAS,GAAG,OAAO,GAAG,cAAc,CAAC;IAC9C,YAAY,EAAE,SAAS,GAAG,cAAc,CAAC;IACzC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,qBAAqB,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;IACtE,YAAY,EAAE,QAAQ,GAAG,KAAK,GAAG,aAAa,GAAG,gBAAgB,CAAC;IAClE,QAAQ,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,IAAI,CAAC;IAClD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,YAAY,GAAG,WAAW,CAAC;CAC9C,CAAC;AAqKF,wBAAgB,mCAAmC,CAAC,KAAK,EAAE;IACzD,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,4BAA4B,CAqD/B;AAkBD,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,mBAAwB,aAwZzE"}
|
package/dist/server.js
CHANGED
|
@@ -2,21 +2,12 @@ import { CertScoreClient } from "@certscore/sdk";
|
|
|
2
2
|
import { certScoreMcpToolContracts, isCanonicalScanId } from "@certscore/api-contracts";
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
4
|
import { CERTSCORE_MCP_VERSION } from "./version.js";
|
|
5
|
-
import { boundEvidencePacket, buildScanBundle, exportFindings, findingListText, limitPreConsentRows, markdownReportText, MAX_EVIDENCE_PACKET_CHARS, normalizeDetail, normalizeFormat, paginateFindingList, preConsentInventoryText, pulseReportText, scanBundleText, scanStatusText, toInvalidArgumentsToolError, toInvalidScanIdToolError, toToolError, toToolResult, withMcpAgentGuidance, withMcpScanProvenanceGuidance } from "./tools.js";
|
|
6
|
-
const DEFAULT_MCP_SCAN_TOOL_BUDGET_MS = 25_000;
|
|
7
|
-
const MAX_MCP_SCAN_TOOL_BUDGET_MS = 45_000;
|
|
8
|
-
const MCP_SCAN_TOOL_RESPONSE_RESERVE_MS = 1_000;
|
|
5
|
+
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";
|
|
9
6
|
const LIGHT_MCP_BUNDLE_RESPONSE_CEILING_BYTES = 25_000;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
elapsedMs,
|
|
15
|
-
remainingWaitMs: Math.max(0, totalBudgetMs - elapsedMs - MCP_SCAN_TOOL_RESPONSE_RESERVE_MS),
|
|
16
|
-
responseReserveMs: MCP_SCAN_TOOL_RESPONSE_RESERVE_MS,
|
|
17
|
-
totalBudgetMs,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
7
|
+
const MCP_SCAN_SITE_TARGET_RESPONSE_MS = 11_000;
|
|
8
|
+
const MCP_SCAN_SITE_TARGET_RESPONSE_RESERVE_MS = 250;
|
|
9
|
+
const MCP_SCAN_SITE_PREVIEW_POLL_INTERVAL_MS = 750;
|
|
10
|
+
const MCP_SCAN_SITE_MAX_PREVIEW_WAIT_MS = 10_000;
|
|
20
11
|
function exampleDomainDemoSubstitution(requestedUrl, demoUrl) {
|
|
21
12
|
if (!demoUrl)
|
|
22
13
|
return null;
|
|
@@ -41,11 +32,9 @@ function withExampleDomainDemo(value, substitution) {
|
|
|
41
32
|
return substitution ? { ...value, demoSubstitution: substitution } : value;
|
|
42
33
|
}
|
|
43
34
|
function exampleDomainDemoText(value, substitution) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const scanId = typeof value.scanId === "string" ? ` ScanId=${value.scanId}.` : "";
|
|
48
|
-
return `${substitution.message}${status}${scanId} Full result and substitution provenance are in structuredContent.`;
|
|
35
|
+
return scanSiteText(value, substitution
|
|
36
|
+
? [`${substitution.message} Substitution provenance is in structuredContent.`]
|
|
37
|
+
: []);
|
|
49
38
|
}
|
|
50
39
|
async function retryTransientOriginFailure(operation) {
|
|
51
40
|
try {
|
|
@@ -73,6 +62,15 @@ function scanCreationMetadata(value) {
|
|
|
73
62
|
upgradeMessage: value.upgradeMessage
|
|
74
63
|
};
|
|
75
64
|
}
|
|
65
|
+
function activeScan(value) {
|
|
66
|
+
return value.status === "queued" || value.status === "running" || value.status === "finalizing";
|
|
67
|
+
}
|
|
68
|
+
function hasPreConsentPreview(value) {
|
|
69
|
+
return Boolean(value.preConsentPreview && typeof value.preConsentPreview === "object" && !Array.isArray(value.preConsentPreview));
|
|
70
|
+
}
|
|
71
|
+
function delay(ms) {
|
|
72
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
73
|
+
}
|
|
76
74
|
function toolContract(name) {
|
|
77
75
|
const contract = certScoreMcpToolContracts.find((candidate) => candidate.name === name);
|
|
78
76
|
if (!contract) {
|
|
@@ -302,6 +300,7 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
302
300
|
};
|
|
303
301
|
registerTool("certscore_scan_site", toolContract("certscore_scan_site"), async (input, extra) => {
|
|
304
302
|
const toolStartedAtMs = Date.now();
|
|
303
|
+
const creationStartedAtMs = toolStartedAtMs;
|
|
305
304
|
const client = clientForRequest(extra);
|
|
306
305
|
const demoSubstitution = exampleDomainDemoSubstitution(input.url, options.exampleDomainDemoUrl);
|
|
307
306
|
const effectiveUrl = demoSubstitution?.effectiveUrl ?? input.url;
|
|
@@ -310,63 +309,84 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
310
309
|
freshness: input.freshness ?? "latest",
|
|
311
310
|
scanFrom: input.scanFrom
|
|
312
311
|
});
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
312
|
+
console.log(JSON.stringify({
|
|
313
|
+
event: "mcp.certscore_scan_site.creation_completed",
|
|
314
|
+
durationMs: Date.now() - creationStartedAtMs,
|
|
315
|
+
executionMode: created.executionMode ?? null,
|
|
316
|
+
hasScanId: Boolean(created.scanId ?? created.scan_id),
|
|
317
|
+
reused: created.reused === true,
|
|
318
|
+
status: created.status ?? null,
|
|
319
|
+
}));
|
|
320
|
+
let initialResult = created;
|
|
321
|
+
const stableScanId = typeof created.scanId === "string" && created.scanId
|
|
322
|
+
? created.scanId
|
|
323
|
+
: typeof created.scan_id === "string" && created.scan_id
|
|
324
|
+
? created.scan_id
|
|
325
|
+
: typeof created.jobId === "string" && created.jobId
|
|
326
|
+
? created.jobId
|
|
327
|
+
: null;
|
|
328
|
+
const configuredPreviewWaitMs = options.toolProfile === "light"
|
|
329
|
+
? Math.min(Math.max(0, options.initialPreConsentPreviewWaitMs ?? 10_000), MCP_SCAN_SITE_MAX_PREVIEW_WAIT_MS)
|
|
330
|
+
: 0;
|
|
331
|
+
const totalRemainingMs = Math.max(0, toolStartedAtMs + MCP_SCAN_SITE_TARGET_RESPONSE_MS - MCP_SCAN_SITE_TARGET_RESPONSE_RESERVE_MS - Date.now());
|
|
332
|
+
const previewWaitMs = Math.min(configuredPreviewWaitMs, totalRemainingMs);
|
|
333
|
+
if (stableScanId && previewWaitMs > 0 && activeScan(initialResult)) {
|
|
334
|
+
const previewWaitStartedAtMs = Date.now();
|
|
335
|
+
const previewDeadlineMs = previewWaitStartedAtMs + previewWaitMs;
|
|
336
|
+
let internalReadCount = 0;
|
|
337
|
+
try {
|
|
338
|
+
while (Date.now() < previewDeadlineMs && activeScan(initialResult) && !hasPreConsentPreview(initialResult)) {
|
|
339
|
+
await delay(Math.min(MCP_SCAN_SITE_PREVIEW_POLL_INTERVAL_MS, previewDeadlineMs - Date.now()));
|
|
340
|
+
const requestRemainingMs = previewDeadlineMs - Date.now();
|
|
341
|
+
if (requestRemainingMs <= 0)
|
|
342
|
+
break;
|
|
343
|
+
const waitAbortController = new AbortController();
|
|
344
|
+
const waitAbortTimer = setTimeout(() => waitAbortController.abort(), requestRemainingMs);
|
|
345
|
+
try {
|
|
346
|
+
internalReadCount += 1;
|
|
347
|
+
const status = await client.scans.status(stableScanId, {
|
|
348
|
+
internalMcpOperation: { operation: "scan_site_wait", scanId: stableScanId },
|
|
349
|
+
signal: waitAbortController.signal,
|
|
350
|
+
});
|
|
351
|
+
initialResult = {
|
|
352
|
+
...status,
|
|
353
|
+
...scanCreationMetadata(created),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
clearTimeout(waitAbortTimer);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
console.warn(JSON.stringify({
|
|
363
|
+
event: "mcp.certscore_scan_site.preview_wait_deferred",
|
|
364
|
+
durationMs: Date.now() - previewWaitStartedAtMs,
|
|
365
|
+
errorName: error instanceof Error ? error.name : "UnknownError",
|
|
366
|
+
hasPreview: hasPreConsentPreview(initialResult),
|
|
367
|
+
internalReadCount,
|
|
368
|
+
scanId: stableScanId,
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
console.log(JSON.stringify({
|
|
372
|
+
event: "mcp.certscore_scan_site.preview_wait_completed",
|
|
373
|
+
durationMs: Date.now() - previewWaitStartedAtMs,
|
|
374
|
+
hasPreview: hasPreConsentPreview(initialResult),
|
|
375
|
+
internalReadCount,
|
|
376
|
+
scanId: stableScanId,
|
|
377
|
+
status: initialResult.status ?? null,
|
|
378
|
+
totalDurationMs: Date.now() - toolStartedAtMs,
|
|
329
379
|
}));
|
|
330
|
-
const guided = withExampleDomainDemo(withMcpAgentGuidance(created), demoSubstitution);
|
|
331
|
-
return toToolResult(guided, exampleDomainDemoText(guided, demoSubstitution));
|
|
332
|
-
}
|
|
333
|
-
const waitAbortController = new AbortController();
|
|
334
|
-
const waitAbortTimer = setTimeout(() => waitAbortController.abort(), waitBudget.remainingWaitMs);
|
|
335
|
-
try {
|
|
336
|
-
const internalMcpOperation = { operation: "scan_site_wait", scanId: created.scanId ?? created.scan_id ?? created.jobId };
|
|
337
|
-
const completed = await client.scans.wait(created, {
|
|
338
|
-
maxWaitMs: waitBudget.remainingWaitMs,
|
|
339
|
-
internalMcpOperation,
|
|
340
|
-
signal: waitAbortController.signal,
|
|
341
|
-
});
|
|
342
|
-
const guided = withExampleDomainDemo(withMcpAgentGuidance({
|
|
343
|
-
...completed,
|
|
344
|
-
...scanCreationMetadata(created)
|
|
345
|
-
}), demoSubstitution);
|
|
346
|
-
return toToolResult(guided, exampleDomainDemoText(guided, demoSubstitution));
|
|
347
|
-
}
|
|
348
|
-
catch (error) {
|
|
349
|
-
const scanId = created.scanId ?? created.scan_id;
|
|
350
|
-
console.warn(JSON.stringify({
|
|
351
|
-
event: "mcp.certscore_scan_site.wait_deferred",
|
|
352
|
-
errorName: error instanceof Error ? error.name : "UnknownError",
|
|
353
|
-
jobId: created.jobId ?? null,
|
|
354
|
-
scanId: scanId ?? null,
|
|
355
|
-
status: created.status ?? null,
|
|
356
|
-
...waitBudget,
|
|
357
|
-
}));
|
|
358
|
-
// Waiting is a convenience layered on top of scan creation. Once the
|
|
359
|
-
// API has accepted a scan, never turn a transient polling or hydration
|
|
360
|
-
// failure into an identity-less tool error that encourages callers to
|
|
361
|
-
// submit a second, non-idempotent certscore_scan_site request.
|
|
362
|
-
const guided = withExampleDomainDemo(withMcpAgentGuidance(created), demoSubstitution);
|
|
363
|
-
return toToolResult(guided, exampleDomainDemoText(guided, demoSubstitution));
|
|
364
|
-
}
|
|
365
|
-
finally {
|
|
366
|
-
clearTimeout(waitAbortTimer);
|
|
367
380
|
}
|
|
381
|
+
const guided = withExampleDomainDemo(withMcpAgentGuidance(initialResult, "unknown", "scan_creation"), demoSubstitution);
|
|
382
|
+
return toToolResult(guided, exampleDomainDemoText(guided, demoSubstitution));
|
|
368
383
|
}
|
|
369
384
|
catch (error) {
|
|
385
|
+
console.warn(JSON.stringify({
|
|
386
|
+
event: "mcp.certscore_scan_site.creation_failed",
|
|
387
|
+
durationMs: Date.now() - creationStartedAtMs,
|
|
388
|
+
errorName: error instanceof Error ? error.name : "UnknownError",
|
|
389
|
+
}));
|
|
370
390
|
return toToolError(error);
|
|
371
391
|
}
|
|
372
392
|
});
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
2
|
import { type FindingList, type JobStatus, type PreConsentCookiesTrackers, type PulseDetail, type PulseFormat, type PulseResult, type ScanResource, type TopFinding } from "@certscore/sdk";
|
|
3
3
|
export declare const MAX_EVIDENCE_PACKET_CHARS = 250000;
|
|
4
|
+
type McpPollGuidanceContext = "scan_creation" | "scan_status" | "upstream";
|
|
4
5
|
type ScanProvenanceMode = "new_scan_started" | "existing_completed_scan_reused" | "existing_scan_retrieved" | "unknown";
|
|
5
6
|
type ActionableError = {
|
|
6
7
|
code: string;
|
|
@@ -15,7 +16,7 @@ export declare function toToolResult(payload: unknown, text?: string): CallToolR
|
|
|
15
16
|
export declare function toToolError(error: unknown): CallToolResult;
|
|
16
17
|
export declare function toInvalidArgumentsToolError(errorMessage: string): CallToolResult;
|
|
17
18
|
export declare function toInvalidScanIdToolError(): CallToolResult;
|
|
18
|
-
export declare function withMcpAgentGuidance<T extends Record<string, any>>(value: T, fallbackProvenanceMode?: ScanProvenanceMode): T & {
|
|
19
|
+
export declare function withMcpAgentGuidance<T extends Record<string, any>>(value: T, fallbackProvenanceMode?: ScanProvenanceMode, pollGuidanceContext?: McpPollGuidanceContext): T & {
|
|
19
20
|
error: ActionableError | null;
|
|
20
21
|
observationOnlyDisclaimer: string;
|
|
21
22
|
};
|
|
@@ -65,6 +66,7 @@ export declare function limitPreConsentRows<T extends Record<string, unknown>>(p
|
|
|
65
66
|
maxRows?: number;
|
|
66
67
|
}): T;
|
|
67
68
|
export declare function scanStatusText(value: Record<string, any>): string;
|
|
69
|
+
export declare function scanSiteText(value: Record<string, any>, leadingLines?: string[]): string;
|
|
68
70
|
export declare function findingListText(value: Record<string, any>, label?: string): string;
|
|
69
71
|
export declare function preConsentInventoryText(value: Record<string, any>): string;
|
|
70
72
|
export declare function pulseReportText(value: Record<string, any>, label?: string): string;
|
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;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.js
CHANGED
|
@@ -8,11 +8,14 @@ const MAX_TOOL_TEXT_CHARS = 8_000;
|
|
|
8
8
|
const LEGAL_REVIEW_DISCLAIMER = "CertScore results are automated public-web observations for human and agentic review, not legal advice, certification, or a compliance determination.";
|
|
9
9
|
const SCAN_PROVENANCE_GROUNDING = "retrievalMode describes how the current tool response obtained the scan; creationDecision describes whether the original scan request created or reused a scan only when that decision is retained. Never infer an unknown creationDecision from scan_id_lookup. For a reused or retrieved existing scan, use only persisted scanFrom and timestamps. Never infer its original scan region from the current request, the user's location, or a default execution region. If persisted region or timestamps are unavailable, report them as unavailable.";
|
|
10
10
|
const INTERPRETATION_STATEMENT = "The CertScore score covers observable public-web scan signals only. Do not infer technologies that are not listed in the returned evidence or any legal compliance status.";
|
|
11
|
-
const SCAN_BUNDLE_RESPONSE_CONTRACT = `Response contract: Report only observed CertScore evidence and CertScore classifications. criticality, priority, and confidence are CertScore metadata; regulatory review lenses are non-determinative CertScore review context—not legal severity, legal exposure, or a compliance determination. Absence of captured consent-action evidence does not establish what happens after Accept, Reject, or Decline. A confirmed post-
|
|
12
|
-
const SCAN_BUNDLE_INTERPRETATION_STATEMENT = "Report only observed CertScore evidence and persisted CertScore classifications. Without corresponding captured post-action evidence, do not infer what Accept, Reject, Decline, or another consent action would do; say the scan does not establish what happens after that action. When postRefusalObservation is confirmed and termination.kind is evidence_satisfied, state the returned
|
|
11
|
+
const SCAN_BUNDLE_RESPONSE_CONTRACT = `Response contract: Report only observed CertScore evidence and CertScore classifications. criticality, priority, and confidence are CertScore metadata; regulatory review lenses are non-determinative CertScore review context—not legal severity, legal exposure, or a compliance determination. Absence of captured consent-action evidence does not establish what happens after Accept, Reject, or Decline. A confirmed post-action observation with termination.kind=evidence_satisfied means the observer intentionally stopped after retaining qualifying evidence; do not treat that termination as uncertainty about the returned observation. Keep any separately returned coverage limitation scoped to what was not measured. Do not extrapolate an observed embed, vendor, or request into unobserved cookies, fingerprinting, tracking, or processing, and do not infer violations or compliance beyond what CertScore observed. ${SCAN_PROVENANCE_GROUNDING}`;
|
|
12
|
+
const SCAN_BUNDLE_INTERPRETATION_STATEMENT = "Report only observed CertScore evidence and persisted CertScore classifications. For gpcResponse, use only GPC response, No observable GPC response, or indeterminate; do not call the result a GPC violation or say GPC was not honored. Keep its jurisdiction-neutral comparison separate from any explicitly returned California scoring policy. Without corresponding captured post-action evidence, do not infer what Accept, Reject, Decline, or another consent action would do; say the scan does not establish what happens after that action. When postAcceptObservation or postRefusalObservation is confirmed and termination.kind is evidence_satisfied, state the returned observation directly and explain that observation stopped intentionally after qualifying evidence was retained. Do not characterize that termination as uncertainty about the observation; mention unmeasured longer-term persistence only when relevant. Treat post-Accept activity as a score-neutral behavior baseline unless a separately projected finding says otherwise. Do not speculate that an observed embed, vendor, or request may cause additional cookies, fingerprinting, tracking, or processing unless CertScore observed that behavior. Treat returned priority or severity as a CertScore classification, not regulatory criticality or legal exposure; prefer ‘observed privacy risk signal’ or ‘CertScore finding’. Do not infer unobserved technologies, legal compliance, or a legal violation from scores or findings.";
|
|
13
13
|
const COMPACT_SCAN_BUNDLE_INTERPRETATION_STATEMENT = "Use only returned CertScore observations and classifications. Do not infer unobserved technologies, post-consent behavior, legal compliance, or violations. Treat priority and severity as CertScore metadata.";
|
|
14
14
|
const OBSERVATION_ONLY_DISCLAIMER = `${LEGAL_REVIEW_DISCLAIMER} No-go, not-observed, and limited-coverage results are not proof of compliance.`;
|
|
15
15
|
const COMPACT_OBSERVATION_ONLY_DISCLAIMER = "Automated public-web observation, not legal advice or a compliance determination; missing or limited evidence is not proof of compliance.";
|
|
16
|
+
const MCP_SCAN_CREATION_POLL_DELAY_SECONDS = 15;
|
|
17
|
+
const MCP_QUEUED_POLL_DELAY_SECONDS = 10;
|
|
18
|
+
const MCP_RUNNING_POLL_DELAY_SECONDS = 5;
|
|
16
19
|
function interpretationGuidance(statement = INTERPRETATION_STATEMENT) {
|
|
17
20
|
return {
|
|
18
21
|
scoreLabel: "CertScore score",
|
|
@@ -31,6 +34,15 @@ function toolResultSummary(payload) {
|
|
|
31
34
|
const status = typeof record.status === "string" ? `; status=${record.status}` : "";
|
|
32
35
|
const scanId = typeof record.scanId === "string" ? `; scanId=${record.scanId}` : "";
|
|
33
36
|
const score = typeof record.score === "number" ? `; CertScore score=${record.score}` : "";
|
|
37
|
+
const preview = record.preConsentPreview && typeof record.preConsentPreview === "object" && !Array.isArray(record.preConsentPreview)
|
|
38
|
+
? record.preConsentPreview
|
|
39
|
+
: null;
|
|
40
|
+
const previewSummary = preview?.summary && typeof preview.summary === "object" && !Array.isArray(preview.summary)
|
|
41
|
+
? preview.summary
|
|
42
|
+
: null;
|
|
43
|
+
const preliminary = preview
|
|
44
|
+
? `; preliminary pre-consent preview=cookies ${previewSummary?.cookieCount ?? "unknown"}, trackers ${previewSummary?.trackerCount ?? "unknown"}, third-party requests ${previewSummary?.thirdPartyRequestCount ?? "unknown"}; preview is not final—continue status polling`
|
|
45
|
+
: "";
|
|
34
46
|
const recordLinks = record.links && typeof record.links === "object" && !Array.isArray(record.links)
|
|
35
47
|
? record.links
|
|
36
48
|
: null;
|
|
@@ -50,7 +62,7 @@ function toolResultSummary(payload) {
|
|
|
50
62
|
: typeof provenanceRecord?.mode === "string"
|
|
51
63
|
? `; provenance=${provenanceRecord.mode}`
|
|
52
64
|
: "";
|
|
53
|
-
return `CertScore ${type}${status}${scanId}${score}${provenance}${report}. Full result is in structuredContent.`;
|
|
65
|
+
return `CertScore ${type}${status}${scanId}${score}${preliminary}${provenance}${report}. Full result is in structuredContent.`;
|
|
54
66
|
}
|
|
55
67
|
export function toToolResult(payload, text) {
|
|
56
68
|
const structuredContent = payload !== null && typeof payload === "object" && !Array.isArray(payload)
|
|
@@ -85,6 +97,7 @@ export function toToolError(error) {
|
|
|
85
97
|
? 30
|
|
86
98
|
: null;
|
|
87
99
|
const code = error instanceof CertScoreError ? error.code : "internal_error";
|
|
100
|
+
const reasonCode = terminalError?.reasonCode === "non_public_target" ? "non_public_target" : null;
|
|
88
101
|
const message = error instanceof Error ? error.message : "Unknown CertScore MCP error.";
|
|
89
102
|
const creationRateLimit = terminalError?.creationRateLimit && typeof terminalError.creationRateLimit === "object" && !Array.isArray(terminalError.creationRateLimit)
|
|
90
103
|
? terminalError.creationRateLimit
|
|
@@ -99,6 +112,7 @@ export function toToolError(error) {
|
|
|
99
112
|
const payload = {
|
|
100
113
|
error: {
|
|
101
114
|
code,
|
|
115
|
+
reasonCode,
|
|
102
116
|
message,
|
|
103
117
|
retryable,
|
|
104
118
|
retryAfterSeconds,
|
|
@@ -271,16 +285,29 @@ function scanProvenance(value, fallbackMode) {
|
|
|
271
285
|
freshnessDecision: typeof value.freshnessDecision === "string" ? value.freshnessDecision : null
|
|
272
286
|
};
|
|
273
287
|
}
|
|
274
|
-
export function withMcpAgentGuidance(value, fallbackProvenanceMode = "unknown") {
|
|
288
|
+
export function withMcpAgentGuidance(value, fallbackProvenanceMode = "unknown", pollGuidanceContext = "upstream") {
|
|
275
289
|
const status = String(value.status ?? "");
|
|
276
290
|
const active = status === "queued" || status === "running" || status === "finalizing";
|
|
277
291
|
const usable = status === "completed" || status === "completed_limited";
|
|
292
|
+
const upstreamRetryAfterSeconds = typeof value.retryAfterSeconds === "number" && Number.isFinite(value.retryAfterSeconds)
|
|
293
|
+
? Math.max(1, Math.ceil(value.retryAfterSeconds))
|
|
294
|
+
: null;
|
|
295
|
+
const retryAfterSeconds = active && pollGuidanceContext === "scan_creation"
|
|
296
|
+
? MCP_SCAN_CREATION_POLL_DELAY_SECONDS
|
|
297
|
+
: active && pollGuidanceContext === "scan_status"
|
|
298
|
+
? status === "queued"
|
|
299
|
+
? MCP_QUEUED_POLL_DELAY_SECONDS
|
|
300
|
+
: MCP_RUNNING_POLL_DELAY_SECONDS
|
|
301
|
+
: upstreamRetryAfterSeconds;
|
|
278
302
|
const error = terminalErrorForResult(value);
|
|
279
303
|
const stableScanId = typeof value.scanId === "string" && value.scanId.trim()
|
|
280
304
|
? value.scanId.trim()
|
|
281
305
|
: typeof value.scan_id === "string" && value.scan_id.trim()
|
|
282
306
|
? value.scan_id.trim()
|
|
283
307
|
: null;
|
|
308
|
+
const hasPreConsentPreview = Boolean(value.preConsentPreview &&
|
|
309
|
+
typeof value.preConsentPreview === "object" &&
|
|
310
|
+
!Array.isArray(value.preConsentPreview));
|
|
284
311
|
const reportUrl = usable
|
|
285
312
|
? typeof value.reportUrl === "string" && value.reportUrl.trim()
|
|
286
313
|
? value.reportUrl.trim()
|
|
@@ -290,24 +317,30 @@ export function withMcpAgentGuidance(value, fallbackProvenanceMode = "unknown")
|
|
|
290
317
|
? `https://certscore.ai/scan/${encodeURIComponent(stableScanId)}`
|
|
291
318
|
: null
|
|
292
319
|
: null;
|
|
320
|
+
const returnedNextAction = typeof value.recommendedNextAction === "string" && value.recommendedNextAction.trim()
|
|
321
|
+
? value.recommendedNextAction.trim()
|
|
322
|
+
: null;
|
|
323
|
+
const activePollAction = `${retryAfterSeconds === null ? "Wait for the recommended delay" : `Wait at least ${retryAfterSeconds} seconds`}, then call certscore_get_scan_status once with scanId ${stableScanId ?? value.jobId}.`;
|
|
324
|
+
const activeNextAction = `${hasPreConsentPreview ? "The returned preConsentPreview is a partial preview of passive evidence. Its counts are checkpoint-only partial counts, not the full scan tally; do not present them as final totals or stop the workflow. " : ""}${activePollAction} Continue with certscore_get_scan_status using the unchanged scanId ${stableScanId ?? value.jobId}. Do not poll in parallel or resubmit certscore_scan_site while this scan is active. After completed or completed_limited, call certscore_get_scan_bundle for the completed scan's final returned tally, canonical findings, and limitations.`;
|
|
293
325
|
return {
|
|
294
326
|
...value,
|
|
327
|
+
retryAfterSeconds,
|
|
295
328
|
error,
|
|
296
329
|
reportUrl,
|
|
297
330
|
scoreLabel: "CertScore score",
|
|
298
331
|
provenance: scanProvenance(value, fallbackProvenanceMode),
|
|
299
332
|
interpretationGuidance: interpretationGuidance(),
|
|
300
333
|
recommendedNextTool: active ? "certscore_get_scan_status" : usable ? "certscore_get_scan_bundle" : null,
|
|
301
|
-
recommendedNextAction: error?.recommendedNextAction ??
|
|
302
|
-
?
|
|
303
|
-
: usable
|
|
304
|
-
? `Call certscore_get_scan_bundle with scanId ${
|
|
305
|
-
: "Review the result and retained limitations."),
|
|
334
|
+
recommendedNextAction: error?.recommendedNextAction ?? (active
|
|
335
|
+
? activeNextAction
|
|
336
|
+
: returnedNextAction ?? (usable
|
|
337
|
+
? `Call certscore_get_scan_bundle with scanId ${stableScanId ?? value.jobId} for the completed scan's final returned tally, canonical findings, and limitations.`
|
|
338
|
+
: "Review the result and retained limitations.")),
|
|
306
339
|
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER
|
|
307
340
|
};
|
|
308
341
|
}
|
|
309
342
|
export function withMcpScanProvenanceGuidance(value, fallbackProvenanceMode) {
|
|
310
|
-
const guided = withMcpAgentGuidance(value, fallbackProvenanceMode);
|
|
343
|
+
const guided = withMcpAgentGuidance(value, fallbackProvenanceMode, "scan_status");
|
|
311
344
|
return {
|
|
312
345
|
...guided,
|
|
313
346
|
interpretationGuidance: interpretationGuidance(`${INTERPRETATION_STATEMENT} ${SCAN_PROVENANCE_GROUNDING}`)
|
|
@@ -960,14 +993,162 @@ function canonicalScanProvenanceText(value) {
|
|
|
960
993
|
}
|
|
961
994
|
export function scanStatusText(value) {
|
|
962
995
|
const reportUrl = reportUrlFor(value);
|
|
963
|
-
|
|
996
|
+
const nextAction = typeof value.recommendedNextAction === "string" && value.recommendedNextAction.trim()
|
|
997
|
+
? value.recommendedNextAction.trim()
|
|
998
|
+
: "Review the returned status and retained limitations.";
|
|
999
|
+
return boundedPreviewResultText([
|
|
964
1000
|
`CertScore scan status: status=${value.status ?? "unknown"}.`,
|
|
1001
|
+
], [...preConsentPreviewTextLines(value), ...terminalLaneResultTextLines(value)], [
|
|
1002
|
+
`Next: ${nextAction}`,
|
|
965
1003
|
canonicalScanProvenanceText(value),
|
|
966
1004
|
`Full report: ${reportUrl ?? "not available"}.`,
|
|
967
1005
|
OBSERVATION_ONLY_DISCLAIMER,
|
|
968
1006
|
INTERPRETATION_STATEMENT,
|
|
969
1007
|
SCAN_PROVENANCE_GROUNDING
|
|
970
|
-
]
|
|
1008
|
+
]);
|
|
1009
|
+
}
|
|
1010
|
+
export function scanSiteText(value, leadingLines = []) {
|
|
1011
|
+
const scanId = extractScanId(value) ?? "unknown";
|
|
1012
|
+
const active = value.status === "queued" || value.status === "running" || value.status === "finalizing";
|
|
1013
|
+
const nextAction = typeof value.recommendedNextAction === "string" && value.recommendedNextAction.trim()
|
|
1014
|
+
? value.recommendedNextAction.trim()
|
|
1015
|
+
: active
|
|
1016
|
+
? `Call certscore_get_scan_status sequentially with scanId ${scanId}; do not resubmit certscore_scan_site.`
|
|
1017
|
+
: "Review the returned result and retained limitations.";
|
|
1018
|
+
return boundedPreviewResultText([...leadingLines,
|
|
1019
|
+
`CertScore scan accepted: scanId=${scanId}; status=${value.status ?? "unknown"}.`,
|
|
1020
|
+
], [...preConsentPreviewTextLines(value), ...terminalLaneResultTextLines(value)], [
|
|
1021
|
+
`Next: ${nextAction}`,
|
|
1022
|
+
`Provenance: retrieval=${value.provenance?.retrievalMode ?? "unknown"}; creation=${value.provenance?.creationDecision ?? "unknown"}.`,
|
|
1023
|
+
canonicalScanProvenanceText(value),
|
|
1024
|
+
OBSERVATION_ONLY_DISCLAIMER,
|
|
1025
|
+
INTERPRETATION_STATEMENT,
|
|
1026
|
+
]);
|
|
1027
|
+
}
|
|
1028
|
+
function terminalLaneResultTextLines(value) {
|
|
1029
|
+
const lines = [];
|
|
1030
|
+
const gpcResponse = value.gpcResponse && typeof value.gpcResponse === "object" && !Array.isArray(value.gpcResponse)
|
|
1031
|
+
? value.gpcResponse
|
|
1032
|
+
: null;
|
|
1033
|
+
if (gpcResponse) {
|
|
1034
|
+
lines.push(`GPC response: ${gpcResponse.findingTitle ?? "GPC response"}; status=${gpcResponse.status ?? "indeterminate"}; Sec-GPC: 1 proof retained on ${gpcResponse.comparison?.enabledProof?.requestsWithSecGpc ?? 0} request(s).`);
|
|
1035
|
+
}
|
|
1036
|
+
for (const [field, label] of [["postAcceptObservation", "Accept Path"], ["postRefusalObservation", "Reject Path"]]) {
|
|
1037
|
+
const observation = value[field] && typeof value[field] === "object" && !Array.isArray(value[field])
|
|
1038
|
+
? value[field]
|
|
1039
|
+
: null;
|
|
1040
|
+
if (observation && typeof observation.interpretation === "string") {
|
|
1041
|
+
lines.push(`${label}: ${observation.interpretation}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
return lines;
|
|
1045
|
+
}
|
|
1046
|
+
function compactPreviewValue(value, fallback = "unknown", maxChars = 280) {
|
|
1047
|
+
if (value === null || value === undefined)
|
|
1048
|
+
return fallback;
|
|
1049
|
+
const normalized = String(value).replace(/\s+/g, " ").trim();
|
|
1050
|
+
if (!normalized)
|
|
1051
|
+
return fallback;
|
|
1052
|
+
return normalized.length > maxChars ? `${normalized.slice(0, Math.max(1, maxChars - 1))}…` : normalized;
|
|
1053
|
+
}
|
|
1054
|
+
function previewObservedTiming(value) {
|
|
1055
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
1056
|
+
? `${Math.round(value)}ms (t+${(value / 1_000).toFixed(3)}s)`
|
|
1057
|
+
: "unavailable";
|
|
1058
|
+
}
|
|
1059
|
+
function preConsentPreviewTextLines(value) {
|
|
1060
|
+
const preview = value.preConsentPreview && typeof value.preConsentPreview === "object" && !Array.isArray(value.preConsentPreview)
|
|
1061
|
+
? value.preConsentPreview
|
|
1062
|
+
: null;
|
|
1063
|
+
if (!preview)
|
|
1064
|
+
return [];
|
|
1065
|
+
const cookies = Array.isArray(preview.cookies) ? preview.cookies : [];
|
|
1066
|
+
const trackers = Array.isArray(preview.trackers) ? preview.trackers : [];
|
|
1067
|
+
const operationalVendors = Array.isArray(preview.operationalVendors) ? preview.operationalVendors : [];
|
|
1068
|
+
const summary = preview.summary && typeof preview.summary === "object" && !Array.isArray(preview.summary)
|
|
1069
|
+
? preview.summary
|
|
1070
|
+
: {};
|
|
1071
|
+
const coverage = preview.runtimeCoverage && typeof preview.runtimeCoverage === "object" && !Array.isArray(preview.runtimeCoverage)
|
|
1072
|
+
? preview.runtimeCoverage
|
|
1073
|
+
: {};
|
|
1074
|
+
const capturedCookieCount = summary.cookieCount ?? cookies.length;
|
|
1075
|
+
const returnedCookieCount = summary.returnedCookieCount ?? cookies.length;
|
|
1076
|
+
const capturedTrackingVendorCount = summary.trackingVendorCount ?? summary.trackerCount ?? trackers.length;
|
|
1077
|
+
const returnedTrackingVendorCount = summary.returnedTrackingVendorCount ?? trackers.length;
|
|
1078
|
+
const capturedOperationalVendorCount = summary.operationalVendorCount ?? operationalVendors.length;
|
|
1079
|
+
const returnedOperationalVendorCount = summary.returnedOperationalVendorCount ?? operationalVendors.length;
|
|
1080
|
+
const lines = [
|
|
1081
|
+
`Partial pre-consent runtime preview: generated=${compactPreviewValue(preview.generatedAt, "unavailable", 80)}; lane=${compactPreviewValue(preview.sourceLane, "runtime_evidence", 80)}; coverage=${compactPreviewValue(coverage.status, "unknown", 80)}; cookies captured=${capturedCookieCount}; cookie identities returned=${returnedCookieCount}; tracking vendors captured=${capturedTrackingVendorCount}; tracking vendor identities returned=${returnedTrackingVendorCount}; operational/security/consent vendors captured=${capturedOperationalVendorCount}; operational identities returned=${returnedOperationalVendorCount}; all classified vendor observations=${summary.vendorCount ?? "unknown"}; third-party requests=${summary.thirdPartyRequestCount ?? "unknown"}.`,
|
|
1082
|
+
"PARTIAL PREVIEW: These are checkpoint-only partial counts, not the full scan tally. Do not present them as final totals or stop the workflow. Wait for terminal scan status, then call certscore_get_scan_bundle for the completed scan's final returned tally, canonical findings, and coverage limitations.",
|
|
1083
|
+
"Metric scope: tracking vendors exclude operational, security, and consent-management vendors. The completed inventory's broader trackerCount may include those categories, so do not compare that field directly with trackingVendorCount.",
|
|
1084
|
+
];
|
|
1085
|
+
if (cookies.length > 0) {
|
|
1086
|
+
lines.push("Cookies observed before a consent choice (metadata only; no cookie values):");
|
|
1087
|
+
for (const candidate of cookies) {
|
|
1088
|
+
const cookie = candidate && typeof candidate === "object" && !Array.isArray(candidate)
|
|
1089
|
+
? candidate
|
|
1090
|
+
: {};
|
|
1091
|
+
lines.push(`- Cookie ${compactPreviewValue(cookie.name, "unnamed", 256)}; domain=${compactPreviewValue(cookie.domain, "unavailable", 253)}; party=${compactPreviewValue(cookie.party, "unknown", 40)}; category/purpose=${compactPreviewValue(cookie.purpose, "unknown", 80)}; essentiality=${compactPreviewValue(cookie.essentiality, "unknown", 40)}; observedAtMs=${previewObservedTiming(cookie.observedAtMs)}.`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
lines.push("Cookies observed before a consent choice: none returned in this preliminary preview.");
|
|
1096
|
+
}
|
|
1097
|
+
if (trackers.length > 0) {
|
|
1098
|
+
lines.push("Tracking vendors/products observed before a consent choice:");
|
|
1099
|
+
for (const candidate of trackers) {
|
|
1100
|
+
const tracker = candidate && typeof candidate === "object" && !Array.isArray(candidate)
|
|
1101
|
+
? candidate
|
|
1102
|
+
: {};
|
|
1103
|
+
const domains = Array.isArray(tracker.domains)
|
|
1104
|
+
? tracker.domains.map((domain) => compactPreviewValue(domain, "unknown", 253)).join(", ")
|
|
1105
|
+
: "none returned";
|
|
1106
|
+
lines.push(`- Tracking vendor ${compactPreviewValue(tracker.vendor, "unknown", 160)}; product=${compactPreviewValue(tracker.product, "unavailable", 160)}; category/purpose=${compactPreviewValue(tracker.purpose, "unknown", 80)}; confidence=${compactPreviewValue(tracker.confidence, "unknown", 20)}; domains=${compactPreviewValue(domains, "none returned", 800)}.`);
|
|
1107
|
+
}
|
|
1108
|
+
lines.push("Tracker timing: per-tracker first-seen milliseconds are not part of the preliminary preview contract; do not infer them from cookie timing.");
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
lines.push("Tracking vendors/products observed before a consent choice: none returned in this partial preview.");
|
|
1112
|
+
}
|
|
1113
|
+
if (operationalVendors.length > 0) {
|
|
1114
|
+
lines.push("Operational, security, or consent-management vendors observed (separate from trackingVendorCount):");
|
|
1115
|
+
for (const candidate of operationalVendors) {
|
|
1116
|
+
const vendor = candidate && typeof candidate === "object" && !Array.isArray(candidate)
|
|
1117
|
+
? candidate
|
|
1118
|
+
: {};
|
|
1119
|
+
const domains = Array.isArray(vendor.domains)
|
|
1120
|
+
? vendor.domains.map((domain) => compactPreviewValue(domain, "unknown", 253)).join(", ")
|
|
1121
|
+
: "none returned";
|
|
1122
|
+
lines.push(`- Operational vendor ${compactPreviewValue(vendor.vendor, "unknown", 160)}; product=${compactPreviewValue(vendor.product, "unavailable", 160)}; category/purpose=${compactPreviewValue(vendor.purpose, "unknown", 80)}; confidence=${compactPreviewValue(vendor.confidence, "unknown", 20)}; domains=${compactPreviewValue(domains, "none returned", 800)}.`);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
if (String(capturedCookieCount) !== String(returnedCookieCount) ||
|
|
1126
|
+
String(capturedTrackingVendorCount) !== String(returnedTrackingVendorCount) ||
|
|
1127
|
+
String(capturedOperationalVendorCount) !== String(returnedOperationalVendorCount)) {
|
|
1128
|
+
lines.push(`Preview identity lists are bounded: ${capturedCookieCount} cookies captured/${returnedCookieCount} identities returned; ${capturedTrackingVendorCount} tracking vendors captured/${returnedTrackingVendorCount} identities returned; ${capturedOperationalVendorCount} operational vendors captured/${returnedOperationalVendorCount} identities returned. Use captured counts for checkpoint coverage and returned arrays for names.`);
|
|
1129
|
+
}
|
|
1130
|
+
const disclaimer = typeof preview.observationOnlyDisclaimer === "string" && preview.observationOnlyDisclaimer.trim()
|
|
1131
|
+
? preview.observationOnlyDisclaimer.trim()
|
|
1132
|
+
: "Preliminary passive observations only; not findings, a score, or a final result.";
|
|
1133
|
+
lines.push(`${compactPreviewValue(disclaimer, "Preliminary passive observations only.", 500)} Continue sequential status polling until terminal status; at completed or completed_limited, retrieve certscore_get_scan_bundle before reporting the full scan results or final returned tally.`);
|
|
1134
|
+
return lines;
|
|
1135
|
+
}
|
|
1136
|
+
function boundedPreviewResultText(prefix, bodyLines, suffix) {
|
|
1137
|
+
const lines = [...prefix];
|
|
1138
|
+
let rendered = 0;
|
|
1139
|
+
for (const line of bodyLines) {
|
|
1140
|
+
if ([...lines, line, ...suffix].join("\n").length > MAX_TOOL_TEXT_CHARS)
|
|
1141
|
+
break;
|
|
1142
|
+
lines.push(line);
|
|
1143
|
+
rendered += 1;
|
|
1144
|
+
}
|
|
1145
|
+
if (rendered < bodyLines.length) {
|
|
1146
|
+
const omitted = `${bodyLines.length - rendered} additional preliminary preview line${bodyLines.length - rendered === 1 ? " was" : "s were"} omitted from TextContent to preserve the size limit; all bounded rows remain in structuredContent.`;
|
|
1147
|
+
if ([...lines, omitted, ...suffix].join("\n").length <= MAX_TOOL_TEXT_CHARS)
|
|
1148
|
+
lines.push(omitted);
|
|
1149
|
+
}
|
|
1150
|
+
lines.push(...suffix);
|
|
1151
|
+
return lines.join("\n");
|
|
971
1152
|
}
|
|
972
1153
|
function boundedResultText(header, bodyLines, value) {
|
|
973
1154
|
const footer = [OBSERVATION_ONLY_DISCLAIMER, INTERPRETATION_STATEMENT];
|
|
@@ -1066,6 +1247,34 @@ export function scanBundleText(bundle) {
|
|
|
1066
1247
|
if (coverage) {
|
|
1067
1248
|
append(`Coverage: status=${coverage.status ?? "unknown"}; ${coverage.summary ?? "Review limitations before interpreting absence."}`);
|
|
1068
1249
|
}
|
|
1250
|
+
const gpcResponse = bundle.gpcResponse && typeof bundle.gpcResponse === "object" && !Array.isArray(bundle.gpcResponse)
|
|
1251
|
+
? bundle.gpcResponse
|
|
1252
|
+
: null;
|
|
1253
|
+
if (gpcResponse) {
|
|
1254
|
+
const proof = gpcResponse.comparison?.enabledProof;
|
|
1255
|
+
const californiaPolicy = gpcResponse.californiaPolicy;
|
|
1256
|
+
append(`GPC response: ${gpcResponse.findingTitle ?? "GPC response"}; status=${gpcResponse.status ?? "indeterminate"}; Sec-GPC: 1 proof retained on ${proof?.requestsWithSecGpc ?? 0} request(s).`);
|
|
1257
|
+
if (californiaPolicy?.applied === true && californiaPolicy.deductionPoints === 15) {
|
|
1258
|
+
append("California scoring policy: −15 points. This score effect is separate from the jurisdiction-neutral GPC comparison.");
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
const postAccept = bundle.postAcceptObservation && typeof bundle.postAcceptObservation === "object" && !Array.isArray(bundle.postAcceptObservation)
|
|
1262
|
+
? bundle.postAcceptObservation
|
|
1263
|
+
: null;
|
|
1264
|
+
if (postAccept && typeof postAccept.interpretation === "string") {
|
|
1265
|
+
const termination = postAccept.termination && typeof postAccept.termination === "object" && !Array.isArray(postAccept.termination)
|
|
1266
|
+
? postAccept.termination
|
|
1267
|
+
: null;
|
|
1268
|
+
const intentionalEvidenceStop = termination?.kind === "evidence_satisfied" && termination.intentional === true
|
|
1269
|
+
? " The observation then stopped intentionally because qualifying evidence had been captured."
|
|
1270
|
+
: "";
|
|
1271
|
+
append(`Accept Path: ${postAccept.interpretation}${intentionalEvidenceStop}`);
|
|
1272
|
+
for (const limitation of Array.isArray(postAccept.coverageLimitations)
|
|
1273
|
+
? postAccept.coverageLimitations.slice(0, 3)
|
|
1274
|
+
: []) {
|
|
1275
|
+
append(`Accept Path coverage limitation: ${limitation}`);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1069
1278
|
const postRefusal = bundle.postRefusalObservation && typeof bundle.postRefusalObservation === "object" && !Array.isArray(bundle.postRefusalObservation)
|
|
1070
1279
|
? bundle.postRefusalObservation
|
|
1071
1280
|
: null;
|
|
@@ -1270,6 +1479,8 @@ export function buildScanBundle(input) {
|
|
|
1270
1479
|
scoreVersion: input.scan.scoreVersion ?? null,
|
|
1271
1480
|
scoreUpdatedAt: input.scan.scoreUpdatedAt ?? null,
|
|
1272
1481
|
riskLevel: input.scan.riskLevel ?? null,
|
|
1482
|
+
...(input.scan.gpcResponse ? { gpcResponse: input.scan.gpcResponse } : {}),
|
|
1483
|
+
postAcceptObservation: input.scan.postAcceptObservation ?? null,
|
|
1273
1484
|
postRefusalObservation: input.scan.postRefusalObservation ?? null,
|
|
1274
1485
|
provenance: scanProvenance(input.scan, "existing_scan_retrieved"),
|
|
1275
1486
|
interpretationGuidance: interpretationGuidance(SCAN_BUNDLE_INTERPRETATION_STATEMENT),
|
|
@@ -1524,6 +1735,8 @@ export function buildScanBundle(input) {
|
|
|
1524
1735
|
scoreVersion: bundle.scoreVersion,
|
|
1525
1736
|
scoreUpdatedAt: bundle.scoreUpdatedAt,
|
|
1526
1737
|
riskLevel: bundle.riskLevel,
|
|
1738
|
+
...(bundle.gpcResponse ? { gpcResponse: bundle.gpcResponse } : {}),
|
|
1739
|
+
postAcceptObservation: bundle.postAcceptObservation,
|
|
1527
1740
|
postRefusalObservation: bundle.postRefusalObservation,
|
|
1528
1741
|
provenance: bundle.provenance,
|
|
1529
1742
|
interpretationGuidance: bundle.interpretationGuidance,
|