@certscore/mcp 0.2.16 → 0.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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;
@@ -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;AAOF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE;IAClD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;;;;;EAYA;AAoJD,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,aA8XzE"}
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
- export function resolveMcpScanSiteWaitBudget(input) {
11
- const totalBudgetMs = Math.min(input.maxWaitSeconds ? input.maxWaitSeconds * 1_000 : DEFAULT_MCP_SCAN_TOOL_BUDGET_MS, MAX_MCP_SCAN_TOOL_BUDGET_MS);
12
- const elapsedMs = Math.max(0, input.nowMs - input.startedAtMs);
13
- return {
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
- if (!substitution)
45
- return undefined;
46
- const status = typeof value.status === "string" ? ` Status=${value.status}.` : "";
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
- if (input.waitForCompletion === false || created.type === "certscore_scan") {
314
- const guided = withExampleDomainDemo(withMcpAgentGuidance(created), demoSubstitution);
315
- return toToolResult(guided, exampleDomainDemoText(guided, demoSubstitution));
316
- }
317
- const waitBudget = resolveMcpScanSiteWaitBudget({
318
- maxWaitSeconds: input.maxWaitSeconds,
319
- nowMs: Date.now(),
320
- startedAtMs: toolStartedAtMs,
321
- });
322
- if (waitBudget.remainingWaitMs === 0) {
323
- console.warn(JSON.stringify({
324
- event: "mcp.certscore_scan_site.wait_budget_consumed",
325
- jobId: created.jobId ?? null,
326
- scanId: created.scanId ?? created.scan_id ?? null,
327
- status: created.status ?? null,
328
- ...waitBudget,
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;
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
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-refusal 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. 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 post-refusal 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. 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.";
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 ?? value.recommendedNextAction ?? (active
302
- ? `Poll certscore_get_scan_status with scanId ${value.scanId ?? value.jobId} after the recommended delay.`
303
- : usable
304
- ? `Call certscore_get_scan_bundle with scanId ${value.scanId ?? value.jobId} for the canonical findings and limitations.`
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
- return [
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
- ].join("\n");
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,