@certscore/mcp 0.2.20 → 0.2.21
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 +3 -3
- package/dist/certscore-mcp.mjs +3442 -3173
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/response-capture.d.ts +12 -0
- package/dist/response-capture.d.ts.map +1 -0
- package/dist/response-capture.js +64 -0
- package/dist/server.d.ts +13 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +63 -11
- package/dist/tools.d.ts +11 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +102 -50
- package/package.json +1 -1
- package/server-light.json +1 -1
- package/server.json +2 -2
package/dist/tools.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { apiV2GpcResponseSchema } from "@certscore/api-contracts";
|
|
2
|
+
import { withResponseCapture, transferResponseCapture } from "./response-capture.js";
|
|
3
|
+
import { getCertScoreErrorContext, CertScoreError } from "@certscore/sdk";
|
|
4
|
+
function canonicalGpcObservationSummary(value) {
|
|
5
|
+
const parsed = apiV2GpcResponseSchema.safeParse(value);
|
|
6
|
+
return parsed.success && parsed.data.contractVersion === "certscore.gpc-response-assessment.v3" ? parsed.data.summary : null;
|
|
7
|
+
}
|
|
2
8
|
const MAX_ERROR_RESPONSE_BODY_CHARS = 2_000;
|
|
3
9
|
export const MAX_EVIDENCE_PACKET_CHARS = 250_000;
|
|
4
10
|
const EVIDENCE_STRING_CHARS = 4_000;
|
|
@@ -13,6 +19,8 @@ const SCAN_BUNDLE_INTERPRETATION_STATEMENT = "Report only observed CertScore evi
|
|
|
13
19
|
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
20
|
const OBSERVATION_ONLY_DISCLAIMER = `${LEGAL_REVIEW_DISCLAIMER} No-go, not-observed, and limited-coverage results are not proof of compliance.`;
|
|
15
21
|
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.";
|
|
22
|
+
const PREVIEW_OBSERVATION_ONLY_DISCLAIMER = "Preliminary passive observations only; not findings, a score, or a final result.";
|
|
23
|
+
const SUCCESSFUL_BUNDLE_TRIAL_CTA = "Optional user follow-up: To try CertScore with an account, start a 7-day CertScore trial at https://certscore.ai/login?mode=create_account&utm_source=mcp_light&utm_medium=agent&utm_campaign=scan_bundle. Paid plans add scan history, higher limits, and team or production access. OAuth-capable clients can use https://mcp.certscore.ai/mcp after account authorization and any required workspace scope grant; Light remains no-auth.";
|
|
16
24
|
const MCP_SCAN_CREATION_POLL_DELAY_SECONDS = 15;
|
|
17
25
|
const MCP_QUEUED_POLL_DELAY_SECONDS = 10;
|
|
18
26
|
const MCP_RUNNING_POLL_DELAY_SECONDS = 5;
|
|
@@ -43,8 +51,9 @@ function toolResultSummary(payload) {
|
|
|
43
51
|
const previewSummary = preview?.summary && typeof preview.summary === "object" && !Array.isArray(preview.summary)
|
|
44
52
|
? preview.summary
|
|
45
53
|
: null;
|
|
54
|
+
const active = record.status === "queued" || record.status === "running" || record.status === "finalizing";
|
|
46
55
|
const preliminary = preview
|
|
47
|
-
? `; preliminary pre-consent preview=cookies ${previewSummary?.cookieCount ?? "unknown"}, trackers ${previewSummary?.trackerCount ?? "unknown"}, third-party requests ${previewSummary?.thirdPartyRequestCount ?? "unknown"}; preview is not final
|
|
56
|
+
? `; preliminary pre-consent preview=cookies ${previewSummary?.cookieCount ?? "unknown"}, trackers ${previewSummary?.trackerCount ?? "unknown"}, third-party requests ${previewSummary?.thirdPartyRequestCount ?? "unknown"}; preview is not final—${active ? "continue status polling" : "follow the terminal result guidance"}`
|
|
48
57
|
: "";
|
|
49
58
|
const recordLinks = record.links && typeof record.links === "object" && !Array.isArray(record.links)
|
|
50
59
|
? record.links
|
|
@@ -71,7 +80,7 @@ export function toToolResult(payload, text) {
|
|
|
71
80
|
const structuredContent = payload !== null && typeof payload === "object" && !Array.isArray(payload)
|
|
72
81
|
? payload
|
|
73
82
|
: { value: payload };
|
|
74
|
-
return {
|
|
83
|
+
return transferResponseCapture(payload, {
|
|
75
84
|
structuredContent,
|
|
76
85
|
content: [
|
|
77
86
|
{
|
|
@@ -79,9 +88,9 @@ export function toToolResult(payload, text) {
|
|
|
79
88
|
text: text ?? toolResultSummary(payload)
|
|
80
89
|
}
|
|
81
90
|
]
|
|
82
|
-
};
|
|
91
|
+
});
|
|
83
92
|
}
|
|
84
|
-
export function toToolError(error) {
|
|
93
|
+
export function toToolError(error, context = {}) {
|
|
85
94
|
const responseRecord = error instanceof CertScoreError && error.responseBody && typeof error.responseBody === "object" && !Array.isArray(error.responseBody)
|
|
86
95
|
? error.responseBody
|
|
87
96
|
: null;
|
|
@@ -100,26 +109,33 @@ export function toToolError(error) {
|
|
|
100
109
|
? 30
|
|
101
110
|
: null;
|
|
102
111
|
const code = error instanceof CertScoreError ? error.code : "internal_error";
|
|
103
|
-
const reasonCode = terminalError?.reasonCode === "
|
|
104
|
-
const
|
|
112
|
+
const reasonCode = typeof terminalError?.reasonCode === "string" && ["non_public_target", "domain_not_found", "dns_unavailable"].includes(terminalError.reasonCode) ? terminalError.reasonCode : null;
|
|
113
|
+
const targetRejected = context.scanCreation === true && code === "invalid_url" && status === 400;
|
|
114
|
+
const originalMessage = error instanceof Error ? error.message : "Unknown CertScore MCP error.";
|
|
115
|
+
const message = targetRejected ? `Scan target rejected. No scan was started. ${originalMessage}` : originalMessage;
|
|
105
116
|
const creationRateLimit = terminalError?.creationRateLimit && typeof terminalError.creationRateLimit === "object" && !Array.isArray(terminalError.creationRateLimit)
|
|
106
117
|
? terminalError.creationRateLimit
|
|
107
118
|
: null;
|
|
108
|
-
const recommendedNextAction =
|
|
109
|
-
?
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
const recommendedNextAction = targetRejected
|
|
120
|
+
? reasonCode === "non_public_target"
|
|
121
|
+
? "Ask for a publicly reachable HTTP or HTTPS website, then call certscore_scan_site with that URL. Do not retry this private or ineligible target or try to bypass the public-target checks."
|
|
122
|
+
: 'Check the spelling and DNS of the intended hostname, then call certscore_scan_site with the actual public website URL, for example {"url":"https://example.com"}. A bare domain is accepted, but example.com and www.example.com are different hostnames; use www only if it is the intended site. Do not repeat the same invalid request. If the correct URL is unclear, ask the user.'
|
|
123
|
+
: typeof terminalError?.recommendedNextAction === "string"
|
|
124
|
+
? terminalError.recommendedNextAction
|
|
125
|
+
: creationRateLimit
|
|
126
|
+
? `No scan was created. Wait ${retryAfterSeconds ?? 30} seconds, then retry the same request. If the limit continues after that delay, contact support@certscore.ai.`
|
|
127
|
+
: retryable
|
|
128
|
+
? `Wait ${retryAfterSeconds ?? 30} seconds, then retry the same request. Stop and contact support@certscore.ai if the error repeats.`
|
|
129
|
+
: "Correct the request using the error details, then retry only if the requested operation is still appropriate.";
|
|
115
130
|
const payload = {
|
|
116
131
|
error: {
|
|
117
132
|
code,
|
|
118
133
|
reasonCode,
|
|
119
134
|
message,
|
|
120
|
-
retryable,
|
|
121
|
-
retryAfterSeconds,
|
|
135
|
+
retryable: targetRejected ? false : retryable,
|
|
136
|
+
retryAfterSeconds: targetRejected ? null : retryAfterSeconds,
|
|
122
137
|
recommendedNextAction,
|
|
138
|
+
...(targetRejected ? { field: "url", scanStarted: false, inputCorrectionRequired: true } : {}),
|
|
123
139
|
...(creationRateLimit
|
|
124
140
|
? { creationRateLimit }
|
|
125
141
|
: {}),
|
|
@@ -130,26 +146,31 @@ export function toToolError(error) {
|
|
|
130
146
|
} : {})
|
|
131
147
|
}
|
|
132
148
|
};
|
|
133
|
-
return {
|
|
134
|
-
content: [
|
|
149
|
+
return withResponseCapture({
|
|
150
|
+
content: [
|
|
151
|
+
{ type: "text", text: JSON.stringify(payload) },
|
|
152
|
+
...(targetRejected ? [{ type: "text", text: `${message} ${recommendedNextAction}` }] : []),
|
|
153
|
+
],
|
|
135
154
|
isError: true
|
|
136
|
-
};
|
|
155
|
+
}, { ...(targetRejected || typeof terminalError?.recommendedNextAction !== "string" ? { recommendedNextAction } : {}), ...(getCertScoreErrorContext(error) ? { upstream: getCertScoreErrorContext(error) } : {}) });
|
|
137
156
|
}
|
|
138
|
-
export function toInvalidArgumentsToolError(errorMessage) {
|
|
139
|
-
const tool = errorMessage.match(/tool ([a-z_]+)/i)?.[1] ?? null;
|
|
140
|
-
const field = errorMessage.match(/\bat ([a-zA-Z0-9_.-]+)/)?.[1]
|
|
157
|
+
export function toInvalidArgumentsToolError(errorMessage, validation) {
|
|
158
|
+
const tool = validation?.tool ?? errorMessage.match(/tool ([a-z_]+)/i)?.[1] ?? null;
|
|
159
|
+
const field = validation?.issues[0]?.field ?? errorMessage.match(/\bat ([a-zA-Z0-9_.-]+)/)?.[1]
|
|
141
160
|
?? (errorMessage.includes("url") ? "url" : errorMessage.includes("scanId") ? "scanId" : null);
|
|
142
|
-
const missing = /required|expected string, received undefined/i.test(errorMessage);
|
|
143
|
-
const message =
|
|
144
|
-
?
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
161
|
+
const missing = validation?.issues[0]?.required === true || /required|expected string, received undefined/i.test(errorMessage);
|
|
162
|
+
const message = validation && validation.issues.length > 1
|
|
163
|
+
? `Invalid arguments in fields: ${validation.issues.map(issue => issue.field).join(", ")}.`
|
|
164
|
+
: field
|
|
165
|
+
? missing
|
|
166
|
+
? `The ${field} field is required.`
|
|
167
|
+
: `The ${field} field is invalid.`
|
|
168
|
+
: `The arguments for ${tool ?? "this tool"} are invalid.`;
|
|
148
169
|
const recommendedNextAction = field === "url"
|
|
149
170
|
? "Provide a public URL or domain."
|
|
150
171
|
: field === "scanId"
|
|
151
172
|
? "Provide the stable scanId returned by certscore_scan_site."
|
|
152
|
-
: "Correct the named fields using the tool input schema, then retry.";
|
|
173
|
+
: "Correct the named fields using the tool input schema. Omit optional parameters to use defaults; do not send null. Refresh tool discovery (tools/list) for the accepted types, values and limits, then retry.";
|
|
153
174
|
const error = {
|
|
154
175
|
code: "invalid_arguments",
|
|
155
176
|
message,
|
|
@@ -157,7 +178,8 @@ export function toInvalidArgumentsToolError(errorMessage) {
|
|
|
157
178
|
retryable: false,
|
|
158
179
|
retryAfterSeconds: null,
|
|
159
180
|
recommendedNextAction,
|
|
160
|
-
mcpCode: -32602
|
|
181
|
+
mcpCode: -32602,
|
|
182
|
+
...(validation ? { issues: validation.issues } : {})
|
|
161
183
|
};
|
|
162
184
|
const payload = tool === "certscore_scan_site"
|
|
163
185
|
? {
|
|
@@ -172,28 +194,31 @@ export function toInvalidArgumentsToolError(errorMessage) {
|
|
|
172
194
|
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER
|
|
173
195
|
}
|
|
174
196
|
: { error };
|
|
175
|
-
return {
|
|
197
|
+
return withResponseCapture({
|
|
176
198
|
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
177
199
|
...(tool === "certscore_scan_site" ? { structuredContent: payload } : {}),
|
|
178
200
|
isError: true
|
|
179
|
-
};
|
|
201
|
+
}, { message: error.message, recommendedNextAction: error.recommendedNextAction });
|
|
180
202
|
}
|
|
181
203
|
export function toInvalidScanIdToolError() {
|
|
182
204
|
const error = {
|
|
183
205
|
code: "invalid_scan_id",
|
|
184
206
|
field: "scanId",
|
|
185
|
-
message: "
|
|
207
|
+
message: "Invalid scanId. Use the unchanged UUID returned by certscore_scan_site. This request did not start a scan.",
|
|
186
208
|
retryable: false,
|
|
187
209
|
retryAfterSeconds: null,
|
|
188
210
|
recommendedNextAction: "Use the unchanged scanId returned by certscore_scan_site. Do not use placeholders, report URLs, domains, or job IDs.",
|
|
189
211
|
mcpCode: -32602
|
|
190
212
|
};
|
|
191
|
-
return {
|
|
213
|
+
return withResponseCapture({
|
|
192
214
|
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
193
215
|
isError: true
|
|
194
|
-
};
|
|
216
|
+
}, { message: error.message, recommendedNextAction: error.recommendedNextAction });
|
|
195
217
|
}
|
|
196
218
|
function terminalErrorForResult(value) {
|
|
219
|
+
const terminalGuidance = value.status === "failed" || value.status === "expired"
|
|
220
|
+
? ` This scan is terminal; polling the same scanId will not resume it. A freshness=refresh request starts a new scan and uses scan quota. Stop if the failure repeats.${typeof value.scanId === "string" && /^[a-f0-9-]{36}$/i.test(value.scanId) ? ` Support reference: ${value.scanId}.` : ""}`
|
|
221
|
+
: "";
|
|
197
222
|
const existing = value.error && typeof value.error === "object" && !Array.isArray(value.error)
|
|
198
223
|
? value.error
|
|
199
224
|
: null;
|
|
@@ -204,11 +229,11 @@ function terminalErrorForResult(value) {
|
|
|
204
229
|
message: typeof existing.message === "string" ? existing.message : "The scan did not produce a canonical result.",
|
|
205
230
|
retryable,
|
|
206
231
|
retryAfterSeconds: typeof existing.retryAfterSeconds === "number" ? existing.retryAfterSeconds : retryable ? 30 : null,
|
|
207
|
-
recommendedNextAction: typeof existing.recommendedNextAction === "string"
|
|
232
|
+
recommendedNextAction: (typeof existing.recommendedNextAction === "string"
|
|
208
233
|
? existing.recommendedNextAction
|
|
209
234
|
: retryable
|
|
210
235
|
? "Wait for the recommended delay, then retry certscore_scan_site with freshness=refresh."
|
|
211
|
-
: "Stop and review the scan limitations before deciding whether to change the URL."
|
|
236
|
+
: "Stop and review the scan limitations before deciding whether to change the URL.") + (typeof existing.recommendedNextAction === "string" && existing.recommendedNextAction.endsWith(terminalGuidance) ? "" : terminalGuidance)
|
|
212
237
|
};
|
|
213
238
|
}
|
|
214
239
|
if (value.status === "completed_limited" && value.noGo) {
|
|
@@ -246,7 +271,8 @@ function terminalErrorForResult(value) {
|
|
|
246
271
|
recommendedNextAction: "Wait for the recommended delay, then retry the same certscore_scan_site request."
|
|
247
272
|
}
|
|
248
273
|
};
|
|
249
|
-
|
|
274
|
+
const error = fallback[String(value.status)];
|
|
275
|
+
return error ? { ...error, recommendedNextAction: error.recommendedNextAction + terminalGuidance } : null;
|
|
250
276
|
}
|
|
251
277
|
function scanProvenance(value, fallbackMode) {
|
|
252
278
|
const executionMode = value.executionMode === "new_scan" || value.executionMode === "reused_scan"
|
|
@@ -311,6 +337,12 @@ export function withMcpAgentGuidance(value, fallbackProvenanceMode = "unknown",
|
|
|
311
337
|
const hasPreConsentPreview = Boolean(value.preConsentPreview &&
|
|
312
338
|
typeof value.preConsentPreview === "object" &&
|
|
313
339
|
!Array.isArray(value.preConsentPreview));
|
|
340
|
+
const preConsentPreview = hasPreConsentPreview
|
|
341
|
+
? {
|
|
342
|
+
...value.preConsentPreview,
|
|
343
|
+
observationOnlyDisclaimer: PREVIEW_OBSERVATION_ONLY_DISCLAIMER,
|
|
344
|
+
}
|
|
345
|
+
: value.preConsentPreview;
|
|
314
346
|
const reportUrl = usable
|
|
315
347
|
? typeof value.reportUrl === "string" && value.reportUrl.trim()
|
|
316
348
|
? value.reportUrl.trim()
|
|
@@ -325,8 +357,9 @@ export function withMcpAgentGuidance(value, fallbackProvenanceMode = "unknown",
|
|
|
325
357
|
: null;
|
|
326
358
|
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}.`;
|
|
327
359
|
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.`;
|
|
328
|
-
return {
|
|
360
|
+
return withResponseCapture({
|
|
329
361
|
...value,
|
|
362
|
+
preConsentPreview,
|
|
330
363
|
retryAfterSeconds,
|
|
331
364
|
error,
|
|
332
365
|
reportUrl,
|
|
@@ -340,14 +373,14 @@ export function withMcpAgentGuidance(value, fallbackProvenanceMode = "unknown",
|
|
|
340
373
|
? `Call certscore_get_scan_bundle with scanId ${stableScanId ?? value.jobId} for the completed scan's final returned tally, canonical findings, and limitations.`
|
|
341
374
|
: "Review the result and retained limitations.")),
|
|
342
375
|
observationOnlyDisclaimer: OBSERVATION_ONLY_DISCLAIMER
|
|
343
|
-
};
|
|
376
|
+
}, error && !value.error && !value.noGo ? { message: error.message, recommendedNextAction: error.recommendedNextAction } : {});
|
|
344
377
|
}
|
|
345
378
|
export function withMcpScanProvenanceGuidance(value, fallbackProvenanceMode) {
|
|
346
379
|
const guided = withMcpAgentGuidance(value, fallbackProvenanceMode, "scan_status");
|
|
347
|
-
return {
|
|
380
|
+
return transferResponseCapture(guided, {
|
|
348
381
|
...guided,
|
|
349
382
|
interpretationGuidance: interpretationGuidance(`${INTERPRETATION_STATEMENT} ${SCAN_PROVENANCE_GROUNDING}`)
|
|
350
|
-
};
|
|
383
|
+
});
|
|
351
384
|
}
|
|
352
385
|
export function boundEvidencePacket(payload, maxSerializedChars = MAX_EVIDENCE_PACKET_CHARS) {
|
|
353
386
|
const originalSerializedChars = measureSerializedChars(payload);
|
|
@@ -1022,7 +1055,9 @@ export function scanStatusText(value) {
|
|
|
1022
1055
|
const noGoText = canonicalNoGoText(value);
|
|
1023
1056
|
if (noGoText)
|
|
1024
1057
|
return noGoText;
|
|
1025
|
-
const reportUrl =
|
|
1058
|
+
const reportUrl = value.status === "completed" || value.status === "completed_limited"
|
|
1059
|
+
? reportUrlFor(value)
|
|
1060
|
+
: null;
|
|
1026
1061
|
const nextAction = typeof value.recommendedNextAction === "string" && value.recommendedNextAction.trim()
|
|
1027
1062
|
? value.recommendedNextAction.trim()
|
|
1028
1063
|
: "Review the returned status and retained limitations.";
|
|
@@ -1064,6 +1099,9 @@ function terminalLaneResultTextLines(value) {
|
|
|
1064
1099
|
? value.gpcResponse
|
|
1065
1100
|
: null;
|
|
1066
1101
|
if (gpcResponse) {
|
|
1102
|
+
const observationSummary = canonicalGpcObservationSummary(gpcResponse);
|
|
1103
|
+
if (observationSummary)
|
|
1104
|
+
lines.push(`GPC observation: ${observationSummary}`);
|
|
1067
1105
|
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).`);
|
|
1068
1106
|
}
|
|
1069
1107
|
for (const [field, label] of [["postAcceptObservation", "Accept Path"], ["postRefusalObservation", "Reject Path"]]) {
|
|
@@ -1110,9 +1148,17 @@ function preConsentPreviewTextLines(value) {
|
|
|
1110
1148
|
const returnedTrackingVendorCount = summary.returnedTrackingVendorCount ?? trackers.length;
|
|
1111
1149
|
const capturedOperationalVendorCount = summary.operationalVendorCount ?? operationalVendors.length;
|
|
1112
1150
|
const returnedOperationalVendorCount = summary.returnedOperationalVendorCount ?? operationalVendors.length;
|
|
1151
|
+
const status = String(value.status ?? "");
|
|
1152
|
+
const active = status === "queued" || status === "running" || status === "finalizing";
|
|
1153
|
+
const usable = status === "completed" || status === "completed_limited";
|
|
1154
|
+
const previewWorkflowGuidance = active
|
|
1155
|
+
? "Wait for terminal scan status, then call certscore_get_scan_bundle for the completed scan's final returned tally, canonical findings, and coverage limitations."
|
|
1156
|
+
: usable
|
|
1157
|
+
? "This scan is terminal. Do not use these checkpoint counts as the final tally; call certscore_get_scan_bundle for the canonical findings and coverage limitations."
|
|
1158
|
+
: "This scan is terminal without a completed result. Treat this preview as retained diagnostic context only, do not continue polling, and follow the terminal Next guidance.";
|
|
1113
1159
|
const lines = [
|
|
1114
1160
|
`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"}.`,
|
|
1115
|
-
|
|
1161
|
+
`PARTIAL PREVIEW: These are checkpoint-only partial counts, not the full scan tally. ${previewWorkflowGuidance}`,
|
|
1116
1162
|
"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.",
|
|
1117
1163
|
];
|
|
1118
1164
|
if (cookies.length > 0) {
|
|
@@ -1160,10 +1206,11 @@ function preConsentPreviewTextLines(value) {
|
|
|
1160
1206
|
String(capturedOperationalVendorCount) !== String(returnedOperationalVendorCount)) {
|
|
1161
1207
|
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.`);
|
|
1162
1208
|
}
|
|
1163
|
-
|
|
1164
|
-
?
|
|
1165
|
-
:
|
|
1166
|
-
|
|
1209
|
+
lines.push(`${PREVIEW_OBSERVATION_ONLY_DISCLAIMER} ${active
|
|
1210
|
+
? "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."
|
|
1211
|
+
: usable
|
|
1212
|
+
? "The scan is terminal; retrieve certscore_get_scan_bundle before reporting the full scan results or final returned tally."
|
|
1213
|
+
: "The scan is terminal without a completed result; do not continue polling or report this preview as a final result."}`);
|
|
1167
1214
|
return lines;
|
|
1168
1215
|
}
|
|
1169
1216
|
function boundedPreviewResultText(prefix, bodyLines, suffix) {
|
|
@@ -1266,7 +1313,7 @@ export function scanBundleText(bundle) {
|
|
|
1266
1313
|
if (noGoText)
|
|
1267
1314
|
return noGoText;
|
|
1268
1315
|
const score = typeof bundle.score === "number" ? `; CertScore score=${bundle.score}` : "";
|
|
1269
|
-
const footer = [OBSERVATION_ONLY_DISCLAIMER, SCAN_BUNDLE_INTERPRETATION_STATEMENT];
|
|
1316
|
+
const footer = [SUCCESSFUL_BUNDLE_TRIAL_CTA, OBSERVATION_ONLY_DISCLAIMER, SCAN_BUNDLE_INTERPRETATION_STATEMENT];
|
|
1270
1317
|
const lines = [
|
|
1271
1318
|
SCAN_BUNDLE_RESPONSE_CONTRACT,
|
|
1272
1319
|
`CertScore scan bundle for ${bundle.domain ?? "unknown domain"}; status=${bundle.status ?? "unknown"}${score}; scanId=${bundle.scanId ?? "unknown"}.`,
|
|
@@ -1290,6 +1337,9 @@ export function scanBundleText(bundle) {
|
|
|
1290
1337
|
? bundle.gpcResponse
|
|
1291
1338
|
: null;
|
|
1292
1339
|
if (gpcResponse) {
|
|
1340
|
+
const observationSummary = canonicalGpcObservationSummary(gpcResponse);
|
|
1341
|
+
if (observationSummary)
|
|
1342
|
+
append(`GPC observation: ${observationSummary}`);
|
|
1293
1343
|
const proof = gpcResponse.comparison?.enabledProof;
|
|
1294
1344
|
const californiaPolicy = gpcResponse.californiaPolicy;
|
|
1295
1345
|
append(`GPC response: ${gpcResponse.findingTitle ?? "GPC response"}; status=${gpcResponse.status ?? "indeterminate"}; Sec-GPC: 1 proof retained on ${proof?.requestsWithSecGpc ?? 0} request(s).`);
|
|
@@ -1522,7 +1572,9 @@ export function buildScanBundle(input) {
|
|
|
1522
1572
|
postAcceptObservation: input.scan.postAcceptObservation ?? null,
|
|
1523
1573
|
postRefusalObservation: input.scan.postRefusalObservation ?? null,
|
|
1524
1574
|
provenance: scanProvenance(input.scan, "existing_scan_retrieved"),
|
|
1525
|
-
interpretationGuidance: interpretationGuidance(
|
|
1575
|
+
interpretationGuidance: interpretationGuidance(input.scan.gpcResponse?.contractVersion === "certscore.gpc-response-assessment.v3"
|
|
1576
|
+
? SCAN_BUNDLE_INTERPRETATION_STATEMENT.replace("For gpcResponse,", "For the paired gpcResponse.status,") + " Report the separate bounded observation, CMP-recorded state and directly observed requests. Observation completion does not mean GPC was honored."
|
|
1577
|
+
: SCAN_BUNDLE_INTERPRETATION_STATEMENT),
|
|
1526
1578
|
resultDisposition: input.scan.resultDisposition ?? null,
|
|
1527
1579
|
noGo: input.scan.noGo ?? null,
|
|
1528
1580
|
coverage: input.scan.coverage ?? null,
|
package/package.json
CHANGED
package/server-light.json
CHANGED
package/server.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"url": "https://github.com/ergoveritas1-alt/certscore.ai",
|
|
9
9
|
"source": "github"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.2.
|
|
11
|
+
"version": "0.2.21",
|
|
12
12
|
"remotes": [
|
|
13
13
|
{
|
|
14
14
|
"type": "streamable-http",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"registryType": "npm",
|
|
21
21
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
22
22
|
"identifier": "@certscore/mcp",
|
|
23
|
-
"version": "0.2.
|
|
23
|
+
"version": "0.2.21",
|
|
24
24
|
"transport": {
|
|
25
25
|
"type": "stdio"
|
|
26
26
|
},
|