@certscore/mcp 0.2.18 → 0.2.20

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