@certscore/mcp 0.2.21 → 0.2.23

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.
@@ -1,5 +1,8 @@
1
1
  import { type McpResponseSummary } from "@website-signal-risk-scanner/shared/dist/mcp-response-summary.js";
2
2
  type SafeMetadata = {
3
+ firstResult?: McpResponseSummary["firstResult"];
4
+ previewWaitMs?: number;
5
+ internalReadCount?: number;
3
6
  message?: string;
4
7
  recommendedNextAction?: string;
5
8
  upstream?: McpResponseSummary["upstream"];
@@ -1 +1 @@
1
- {"version":3,"file":"response-capture.d.ts","sourceRoot":"","sources":["../src/response-capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,kEAAkE,CAAC;AAErI,KAAK,YAAY,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA;CAAE,CAAC;AAEpH,yFAAyF;AACzF,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY,GAAG,CAAC,CAG1F;AACD,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAGxF;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAsC/F"}
1
+ {"version":3,"file":"response-capture.d.ts","sourceRoot":"","sources":["../src/response-capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,kEAAkE,CAAC;AAErI,KAAK,YAAY,GAAG;IAAE,WAAW,CAAC,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA;CAAE,CAAC;AAEzN,yFAAyF;AACzF,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY,GAAG,CAAC,CAG1F;AACD,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAGxF;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,OAAO,GAAG,kBAAkB,CA6D/F"}
@@ -2,7 +2,7 @@ import { mcpResponseSummarySchema } from "@website-signal-risk-scanner/shared/di
2
2
  const controlled = new WeakMap();
3
3
  /** Only call with template-owned text. Never mark upstream messages or caller values. */
4
4
  export function withResponseCapture(result, metadata) {
5
- controlled.set(result, metadata);
5
+ controlled.set(result, { ...controlled.get(result), ...metadata });
6
6
  return result;
7
7
  }
8
8
  export function transferResponseCapture(payload, result) {
@@ -23,18 +23,43 @@ export function captureMcpResponse(result, protocolError) {
23
23
  catch { /* Markdown is never retained. */ }
24
24
  }
25
25
  }
26
+ const scan = payload.type === "certscore_domain_latest_scan" ? record(payload.scan) : payload;
27
+ const guidance = record(record(outer._meta)['ai.certscore/responseGuidance']);
28
+ const next = record(guidance.nextAction);
29
+ const actionCategories = ["poll_status", "get_bundle", "get_next_page", "create_if_requested", "summarize", "review_connection", "stop_review"];
30
+ const actionCategory = actionCategories.find(value => value === guidance.actionCategory);
31
+ const page = record(guidance.pagination);
32
+ const fm = record(payload.findingsMetadata), inventory = record(payload.preConsentCookiesTrackers), metadata = record(payload.mcpMetadata);
33
+ const counts = {};
34
+ for (const [key, value] of Object.entries({ findingsReturned: fm.returned, findingsTotal: fm.total, inventoryReturned: inventory.returned, inventoryTotal: inventory.total })) {
35
+ if (Number.isInteger(value) && value >= 0)
36
+ counts[key] = value;
37
+ }
38
+ if (Array.isArray(metadata.omittedSections))
39
+ counts.omittedSections = metadata.omittedSections.filter((v) => token(v)).slice(0, 12);
40
+ const quota = { limit: payload.anonymousQuotaLimit, remaining: payload.anonymousQuotaRemaining, resetAt: payload.anonymousQuotaResetAt };
26
41
  const protocol = record(protocolError);
27
42
  const error = protocolError ? record(protocol.data) : record(payload.error);
28
43
  const safe = controlled.get(protocolError ? protocol : outer) ?? {};
29
- const retryAfterSeconds = Object.hasOwn(error, "retryAfterSeconds") ? error.retryAfterSeconds : payload.retryAfterSeconds;
44
+ const retryAfterSeconds = Object.hasOwn(error, "retryAfterSeconds") ? error.retryAfterSeconds : payload.retryAfterSeconds ?? next.retryAfterSeconds;
30
45
  const summary = {
31
46
  version: 1, captureBasis: "response_generated", templateVersion: "2026-09-11.1",
32
47
  kind: protocolError ? "protocol_error" : "tool_result", isError: protocolError ? true : outer.isError === true,
33
- type: token(payload.type), status: token(payload.status), errorCode: token(error.code), reasonCode: token(error.reasonCode),
48
+ type: token(payload.type), status: token(scan.status ?? scan.scanStatus), errorCode: token(error.code), reasonCode: token(error.reasonCode),
34
49
  ...(Number.isInteger(protocolError ? protocol.code : error.mcpCode) ? { mcpCode: protocolError ? protocol.code : error.mcpCode } : {}),
35
50
  ...(typeof error.retryable === "boolean" ? { retryable: error.retryable } : {}),
36
51
  ...(retryAfterSeconds === null || Number.isInteger(retryAfterSeconds) && retryAfterSeconds >= 0 && retryAfterSeconds <= 86400 ? { retryAfterSeconds } : {}),
37
- recommendedNextTool: payload.recommendedNextTool === null ? null : token(payload.recommendedNextTool),
52
+ recommendedNextTool: token(next.tool) ?? (payload.recommendedNextTool === null ? null : token(payload.recommendedNextTool)),
53
+ ...(actionCategory ? { actionCategory } : {}),
54
+ retryDisposition: !protocolError && outer.isError !== true && ["summarize", "get_bundle", "get_next_page", "create_if_requested", "review_connection"].includes(actionCategory ?? "") ? "not_needed" : actionCategory ? "follow_guidance" : "not_recorded",
55
+ scanAssociation: token(scan.scanId ?? scan.scan_id) ? "linked" : payload.type === "certscore_domain_latest_scan" && payload.scan === null ? "no_eligible_scan" : payload.type === "certscore_auth_check" ? "not_applicable" : "not_recorded",
56
+ ...(typeof (error.quotaConsumed ?? payload.quotaConsumed) === "boolean" ? { quotaConsumed: error.quotaConsumed ?? payload.quotaConsumed } : {}),
57
+ ...(typeof (error.scanStarted ?? payload.scanStarted) === "boolean" ? { scanStarted: error.scanStarted ?? payload.scanStarted } : {}),
58
+ ...(token(metadata.truncationReason) ? { omissionReason: token(metadata.truncationReason) } : {}),
59
+ ...(["new_scan", "reused_scan", "not_requested", "unknown"].includes(guidance.creationDecision) ? { creationDecision: guidance.creationDecision } : {}),
60
+ ...(typeof page.complete === "boolean" && (page.nextOffset === null || Number.isInteger(page.nextOffset) && page.nextOffset >= 0) ? { pagination: { complete: page.complete, nextOffset: page.nextOffset } } : {}),
61
+ ...(Number.isInteger(quota.limit) && quota.limit >= 0 && Number.isInteger(quota.remaining) && quota.remaining >= 0 && typeof quota.resetAt === "string" && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?Z$/.test(quota.resetAt) ? { anonymousCreationQuota: quota } : {}),
62
+ ...(Object.keys(counts).length ? { completeness: counts } : {}),
38
63
  ...safe,
39
64
  textOmitted: Boolean((protocolError ? protocol.message : error.message) && !safe.message || (error.recommendedNextAction || payload.recommendedNextAction) && !safe.recommendedNextAction),
40
65
  summaryTruncated: false,
@@ -0,0 +1,4 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ /** Presentation metadata only: never derive findings, eligibility or quota from observations. */
3
+ export declare function withResponseGuidance(tool: string, input: unknown, result: CallToolResult, now?: number): CallToolResult;
4
+ //# sourceMappingURL=response-guidance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-guidance.d.ts","sourceRoot":"","sources":["../src/response-guidance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAuBzE,iGAAiG;AACjG,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,SAAa,GAAG,cAAc,CAqE3H"}
@@ -0,0 +1,100 @@
1
+ import { transferResponseCapture } from './response-capture.js';
2
+ const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
3
+ const string = (value) => typeof value === 'string' && value.trim() ? value.slice(0, 1000) : null;
4
+ const toolPurposes = {
5
+ certscore_get_connection_status: "Check current access and quota without creating a scan.",
6
+ certscore_scan_site: 'Start the requested public-website scan; latest may reuse an eligible result, refresh requests a new scan.',
7
+ certscore_get_scan: 'Read one scan’s identity, lifecycle and overview without starting another scan.',
8
+ certscore_get_scan_status: 'Check whether a scan is ready; follow the polling interval instead of repeatedly fetching reports.',
9
+ certscore_get_report_evidence_page: 'Retrieve the complete report projection in bounded pages, preserving observation limitations.',
10
+ certscore_get_report: 'Read the report in JSON or Markdown for the requested level of detail.',
11
+ certscore_get_evidence: 'Inspect retained supporting evidence when the report summary is insufficient.',
12
+ certscore_get_scan_bundle: 'Use this as the main completed-scan summary: score, findings, evidence and pre-consent inventory together.',
13
+ certscore_export_findings: 'Export the returned canonical findings for downstream review or processing.',
14
+ certscore_list_findings: 'Browse canonical findings in pages; use their exact IDs for explanations.',
15
+ certscore_get_pre_consent_cookies_trackers: 'Inspect observed pre-consent inventory for this exact scan; unknown purpose is not proof of non-essential use.',
16
+ certscore_explain_finding: 'Explain one returned finding and its reviewer action, grounded in retained evidence.',
17
+ certscore_get_latest_domain_scan: 'Find an existing eligible domain scan before requesting new work when freshness is not required.',
18
+ certscore_get_latest_domain_pre_consent_cookies_trackers: 'Read pre-consent inventory from the latest eligible domain scan without starting a new scan.',
19
+ };
20
+ /** Presentation metadata only: never derive findings, eligibility or quota from observations. */
21
+ export function withResponseGuidance(tool, input, result, now = Date.now()) {
22
+ if (result.isError || !result.structuredContent)
23
+ return result;
24
+ const args = record(input), payload = record(result.structuredContent);
25
+ const scan = payload.scan === null ? {} : Object.keys(record(payload.scan)).length ? record(payload.scan) : payload;
26
+ const summary = record(scan.summary), provenance = record(scan.provenance);
27
+ const noGo = scan.resultDisposition === 'no_go' || payload.resultDisposition === 'no_go';
28
+ const scanId = string(scan.scanId) ?? string(scan.scan_id) ?? string(args.scanId);
29
+ const status = string(scan.status);
30
+ const completedAt = string(scan.completedAt);
31
+ const parsedTime = completedAt ? Date.parse(completedAt) : NaN;
32
+ const ageSeconds = Number.isFinite(parsedTime) && parsedTime <= now ? Math.floor((now - parsedTime) / 1000) : null;
33
+ const isCreation = tool === 'certscore_scan_site';
34
+ const active = ['queued', 'running', 'finalizing'].includes(status ?? '');
35
+ const ready = ['completed', 'completed_limited'].includes(status ?? '');
36
+ const pagination = record(payload.pagination);
37
+ const hasMore = pagination.truncated === true || pagination.complete === false;
38
+ const nextOffset = hasMore && Number.isInteger(pagination.offset) && Number.isInteger(pagination.returned) && pagination.returned > 0
39
+ ? pagination.offset + pagination.returned : null;
40
+ let nextAction = { tool: null, arguments: null, instruction: 'Summarize the returned observations and coverage limitations.' };
41
+ if (tool === 'certscore_get_connection_status')
42
+ nextAction = { tool: null, arguments: null, instruction: string(record(payload.diagnostics).nextAction) ?? 'Review the connection diagnostics.' };
43
+ else if (noGo)
44
+ nextAction = { tool: null, arguments: null, instruction: string(scan.recommendedNextAction) ?? 'Review the retained no-go reason. Do not continue polling this scan.' };
45
+ else if (scanId && active)
46
+ nextAction = { tool: 'certscore_get_scan_status', arguments: { scanId }, retryAfterSeconds: scan.retryAfterSeconds ?? null, instruction: 'Wait for the returned polling interval before checking status.' };
47
+ else if (scanId && ready && payload.resultDisposition !== 'no_go' && ['certscore_scan_site', 'certscore_get_scan', 'certscore_get_scan_status', 'certscore_get_latest_domain_scan'].includes(tool))
48
+ nextAction = { tool: 'certscore_get_scan_bundle', arguments: { scanId }, instruction: 'Fetch the report bundle to summarize this completed scan.' };
49
+ else if (tool === 'certscore_get_report_evidence_page' && typeof pagination.nextCursor === 'string')
50
+ nextAction = { tool, arguments: { scanId, cursor: pagination.nextCursor }, instruction: 'Fetch the next report evidence page. Keep the same snapshot until export is complete.' };
51
+ else if (tool === 'certscore_list_findings' && nextOffset !== null)
52
+ nextAction = { tool, arguments: { scanId, offset: nextOffset, limit: pagination.limit }, instruction: 'Fetch the next page if more findings are needed.' };
53
+ else if (payload.scan === null)
54
+ nextAction = { tool: 'certscore_scan_site', arguments: null, instruction: 'No eligible retained scan was returned. Start a scan only if requested, using the intended public website URL.' };
55
+ else if (['failed', 'cancelled', 'canceled', 'no_go'].includes(status ?? '') || payload.resultDisposition === 'no_go')
56
+ nextAction = { tool: null, arguments: null, instruction: string(payload.recommendedNextAction) ?? 'Review the terminal error or no-go reason. Do not continue polling this scan.' };
57
+ const actionCategory = tool === 'certscore_get_connection_status' ? 'review_connection'
58
+ : nextAction.tool === 'certscore_get_scan_status' ? 'poll_status'
59
+ : nextAction.tool === 'certscore_get_scan_bundle' ? 'get_bundle'
60
+ : ['certscore_list_findings', 'certscore_get_report_evidence_page'].includes(String(nextAction.tool)) ? 'get_next_page'
61
+ : nextAction.tool === 'certscore_scan_site' ? 'create_if_requested'
62
+ : noGo || ['failed', 'cancelled', 'canceled', 'no_go'].includes(status ?? '') ? 'stop_review' : 'summarize';
63
+ const findingIds = (Array.isArray(payload.findings) ? payload.findings : Array.isArray(payload.topFindings) ? payload.topFindings : [])
64
+ .slice(0, 20).map((item) => string(record(item).id)).filter(Boolean);
65
+ if (tool === 'certscore_explain_finding' && string(payload.id))
66
+ findingIds.push(string(payload.id));
67
+ const guidance = {
68
+ version: 'certscore.mcp-response-guidance.v1', tool, purpose: toolPurposes[tool] ?? null, scanId, status,
69
+ score: typeof scan.score === 'number' ? scan.score : typeof summary.score === 'number' ? summary.score : null,
70
+ risk: string(scan.riskLevel) ?? string(summary.riskLevel) ?? string(scan.risk) ?? string(summary.risk),
71
+ coverage: string(scan.coverage) ?? string(record(scan.coverage).status),
72
+ reportUrl: string(scan.reportUrl) ?? string(record(scan.links).report),
73
+ retrieval: isCreation ? 'creation_response' : tool.includes('latest_domain') ? 'latest_eligible_domain_scan' : 'retained_result',
74
+ creationDecision: isCreation ? string(provenance.creationDecision) ?? (payload.reused === true ? 'reused_scan' : 'unknown') : 'not_requested',
75
+ quotaConsumed: typeof payload.quotaConsumed === 'boolean' ? payload.quotaConsumed : null,
76
+ completedAt, ageSeconds, scanFrom: string(scan.scanFrom), findingIds,
77
+ returnedRows: Array.isArray(payload.rows) ? payload.rows.length : Array.isArray(record(payload.preConsentCookiesTrackers).rows) ? payload.preConsentCookiesTrackers.rows.length : null,
78
+ evidenceLimits: { truncated: record(payload.evidenceMetadata).truncated ?? record(payload.mcpMetadata).truncated ?? null, total: record(payload.evidenceMetadata).total ?? null, returned: record(payload.evidenceMetadata).returned ?? null },
79
+ pagination: Object.keys(pagination).length ? { ...pagination, nextOffset, complete: !hasMore } : null,
80
+ optionalFollowUps: !noGo && !active && scanId && findingIds.length ? [
81
+ { tool: 'certscore_explain_finding', arguments: { scanId, findingId: findingIds[0] }, reason: 'Explain a returned finding using retained evidence.' },
82
+ { prompt: 'certscore_remediation_checklist', arguments: { scanId }, reason: 'Prepare a proposed checklist; do not claim remediation is verified.' }
83
+ ] : [],
84
+ nextAction, actionCategory,
85
+ };
86
+ // Keep full machine metadata; avoid repeating the entire envelope in model-visible text.
87
+ const compact = Object.fromEntries(Object.entries({
88
+ scanId, status, score: guidance.score, risk: guidance.risk, reportUrl: guidance.reportUrl,
89
+ quotaConsumed: guidance.quotaConsumed, nextAction,
90
+ ...(guidance.pagination ? { pagination: guidance.pagination } : {})
91
+ }).filter(([, value]) => value !== null));
92
+ const overview = `CertScore guidance: ${JSON.stringify(compact)}`;
93
+ const explanation = tool === 'certscore_explain_finding'
94
+ ? `\nFinding: ${JSON.stringify({ id: string(payload.id), observation: string(payload.evidenceSummary) ?? string(record(payload.evidence).summary), interpretation: string(payload.plainEnglish), reviewerAction: string(payload.nextStep) })}` : '';
95
+ return transferResponseCapture(result, {
96
+ ...result,
97
+ _meta: { ...result._meta, 'ai.certscore/responseGuidance': guidance },
98
+ content: [...result.content, { type: 'text', text: overview + explanation }],
99
+ });
100
+ }
package/dist/server.d.ts CHANGED
@@ -5,6 +5,10 @@ import { type RequestInfo } from "@modelcontextprotocol/sdk/types.js";
5
5
  import { type McpTaskContext } from "@website-signal-risk-scanner/shared/dist/mcp-product-context.js";
6
6
  export interface CertScoreMcpOptions {
7
7
  apiKey?: string;
8
+ /** Validated hosted OAuth scopes; never inferred from an unverified token. */
9
+ grantedOAuthScopes?: readonly string[];
10
+ /** Trusted host callback providing the current request's validated credential. */
11
+ resolveApiKey?: () => string;
8
12
  baseUrl?: string;
9
13
  forwardedClientIp?: string | null;
10
14
  resolveForwardedClientIp?: (headers: RequestInfo["headers"]) => string | null;
@@ -15,6 +19,7 @@ export interface CertScoreMcpOptions {
15
19
  toolProfile?: "full" | "light";
16
20
  initialPreConsentPreviewWaitMs?: number;
17
21
  exampleDomainDemoUrl?: string | null;
22
+ resolveRequestId?: () => string | undefined;
18
23
  onToolInvocationStarted?: (input: {
19
24
  requestId: string;
20
25
  toolName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/server.js CHANGED
@@ -1,9 +1,11 @@
1
+ import { registerAdoptionFeatures, EXAMPLE_SCAN_ID } from "./adoption.js";
2
+ import { withResponseGuidance } from "./response-guidance.js";
1
3
  import { randomUUID } from "node:crypto";
2
4
  import { captureMcpResponse, withResponseCapture } from "./response-capture.js";
3
5
  import { z } from "zod";
4
6
  import { captureMcpCallerInput } from "@website-signal-risk-scanner/shared/dist/mcp-caller-input.js";
5
- import { CertScoreClient } from "@certscore/sdk";
6
- import { certScoreMcpToolContracts, isCanonicalScanId } from "@certscore/api-contracts";
7
+ import { CertScoreClient, CertScoreError } from "@certscore/sdk";
8
+ import { certScoreMcpToolContracts, isCanonicalScanId, reportEvidencePageSchema } from "@certscore/api-contracts";
7
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
10
  import { CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
9
11
  import { sanitizeMcpTaskContext } from "@website-signal-risk-scanner/shared/dist/mcp-product-context.js";
@@ -217,7 +219,9 @@ export function projectMcpToolInvocationObservation(input) {
217
219
  const isError = !completedNoGo
218
220
  && (Boolean(input.result?.isError) || Boolean(error));
219
221
  const outcome = rateLimited ? "rate_limited" : isError ? "error" : "success";
220
- const resultScanId = boundedTelemetryToken(result.scanId ?? result.scan_id ?? result.jobId, 128);
222
+ const scan = result.type === "certscore_domain_latest_scan" && result.scan && typeof result.scan === "object"
223
+ ? result.scan : result;
224
+ const resultScanId = boundedTelemetryToken(scan.scanId ?? scan.scan_id ?? scan.jobId, 128);
221
225
  const inputScanId = boundedTelemetryToken(args.scanId, 128);
222
226
  const requestedResource = requestedTelemetryResource(args);
223
227
  const targetHostname = input.toolName === "certscore_scan_site"
@@ -250,12 +254,12 @@ export function projectMcpToolInvocationObservation(input) {
250
254
  scanDecision,
251
255
  scanFrom: args.scanFrom === "eu_de" || args.scanFrom === "eu_ie" || args.scanFrom === "california"
252
256
  ? args.scanFrom
253
- : result.scanFrom === "eu_de" || result.scanFrom === "eu_ie" || result.scanFrom === "california"
254
- ? result.scanFrom
257
+ : scan.scanFrom === "eu_de" || scan.scanFrom === "eu_ie" || scan.scanFrom === "california"
258
+ ? scan.scanFrom
255
259
  : null,
256
260
  scanId: outcome === "error" && ["invalid_scan_id", "invalid_arguments", "invalid_url", "unknown_tool"].includes(errorCode ?? "")
257
261
  ? null : resultScanId ?? inputScanId,
258
- scanStatus: boundedTelemetryToken(result.status, 64),
262
+ scanStatus: boundedTelemetryToken(scan.status ?? scan.scanStatus, 64),
259
263
  targetHostname,
260
264
  toolName: input.toolName,
261
265
  transportOutcome: isError ? "mcp_error" : "mcp_result",
@@ -290,8 +294,8 @@ function observeToolInvocation(observer, observation, requestContext) {
290
294
  });
291
295
  }
292
296
  export function createCertScoreMcpServer(options = {}) {
293
- const createClient = (forwardedClientIp, anonymousRequesterSession) => new CertScoreClient({
294
- apiKey: options.apiKey,
297
+ const createClient = (forwardedClientIp, anonymousRequesterSession, apiKey = options.apiKey) => new CertScoreClient({
298
+ apiKey,
295
299
  baseUrl: options.baseUrl,
296
300
  clientName: "mcp",
297
301
  forwardedClientIp,
@@ -301,25 +305,72 @@ export function createCertScoreMcpServer(options = {}) {
301
305
  timeout: options.timeout
302
306
  });
303
307
  const client = createClient(options.forwardedClientIp, options.resolveAnonymousRequesterSession?.());
304
- const clientForRequest = (extra) => options.resolveForwardedClientIp
305
- ? createClient(options.resolveForwardedClientIp(extra.requestInfo?.headers ?? {}), options.resolveAnonymousRequesterSession?.())
306
- : client;
308
+ const clientForRequest = (extra) => {
309
+ if (!options.resolveApiKey && !options.resolveForwardedClientIp)
310
+ return client;
311
+ const apiKey = options.resolveApiKey ? options.resolveApiKey() : options.apiKey;
312
+ if (options.resolveApiKey && !apiKey?.trim()) {
313
+ throw new Error("Validated MCP request credential is unavailable.");
314
+ }
315
+ return createClient(options.resolveForwardedClientIp ? options.resolveForwardedClientIp(extra.requestInfo?.headers ?? {}) : options.forwardedClientIp, options.resolveAnonymousRequesterSession?.(), apiKey);
316
+ };
307
317
  const server = new McpServer({
308
318
  name: "certscore",
309
319
  version: CERTSCORE_MCP_VERSION
320
+ }, {
321
+ instructions: JSON.stringify({
322
+ setup: {
323
+ route: options.toolProfile === "light" ? "light" : options.grantedOAuthScopes ? "hosted_oauth" : options.anonymousSurface ? "anonymous" : "scoped_api_key",
324
+ scopesGranted: options.grantedOAuthScopes ?? null,
325
+ createAllowedByScope: options.grantedOAuthScopes ? options.grantedOAuthScopes.includes("scan:create") : null,
326
+ resources: options.toolProfile === "light" ? [] : ["certscore://connection", "certscore://project-instructions", "certscore://reconnect", "certscore://example-report"],
327
+ prompts: options.toolProfile === "light" ? [] : ["certscore_launch_review", "certscore_compare_scans", "certscore_remediation_checklist"],
328
+ quotaRemaining: null,
329
+ quotaNote: "Remaining allowance is not loaded at handshake. Scan creation enforces current workspace and requester limits; inspect quota errors rather than assuming a fresh allowance.",
330
+ recommendedNextTool: options.grantedOAuthScopes && !options.grantedOAuthScopes.includes("scan:create") ? "certscore_get_latest_domain_scan" : "certscore_scan_site",
331
+ sequence: ["certscore_scan_site", "certscore_get_scan_status", "certscore_get_scan_bundle"],
332
+ guidance: options.toolProfile === "light"
333
+ ? "Light supports eligible public scans, not workspace history. For workspace access connect https://mcp.certscore.ai/mcp using OAuth. Reuse an existing Hosted OAuth connection rather than adding duplicate names."
334
+ : "Start a scan, poll only while active at the returned interval, then fetch its bundle. If scan:create is missing, reauthorize with scan:read scan:create mcp. Reuse existing endpoint installations; client names are labels, not verified identities."
335
+ }
336
+ })
310
337
  });
338
+ if (options.toolProfile !== "light")
339
+ registerAdoptionFeatures(server, async () => {
340
+ try {
341
+ return await clientForRequest({}).getConnectionStatus();
342
+ }
343
+ catch (error) {
344
+ if (error instanceof CertScoreError && error.status === 401)
345
+ return { authenticated: false, status: "reconnect_required", quota: null, nextAction: "Read certscore://reconnect and reconnect the existing Hosted OAuth connector. Do not share tokens." };
346
+ return { authenticated: null, status: "check_unavailable", quota: null, nextAction: "Retry the connection check. If your host reports expired or revoked access, read certscore://reconnect. Do not assume a failed check means quota is available." };
347
+ }
348
+ }, async () => {
349
+ try {
350
+ const exampleClient = clientForRequest({});
351
+ const [report, metadata] = await Promise.all([exampleClient.getScanPulse(EXAMPLE_SCAN_ID), exampleClient.getScanResource(EXAMPLE_SCAN_ID)]);
352
+ return { originalCompletedAt: metadata.completedAt ?? null, coverage: metadata.coverage, status: metadata.status, example: true, label: "Retained example, not a current scan of your website", scanId: EXAMPLE_SCAN_ID,
353
+ reportUrl: `https://certscore.ai/scan/${EXAMPLE_SCAN_ID}`,
354
+ report: JSON.stringify(report).length <= 30000 ? report : null,
355
+ note: "Use original timestamps and coverage from the report. If omitted here for size, open the report URL. No new scan was created." };
356
+ }
357
+ catch {
358
+ return { example: true, available: false, scanId: EXAMPLE_SCAN_ID, nextAction: "The retained example is unavailable. Do not create a replacement scan automatically." };
359
+ }
360
+ });
311
361
  const sdkCreateToolError = server.createToolError.bind(server);
312
362
  server.createToolError = (message) => message.includes("Input validation error:")
313
363
  ? toInvalidArgumentsToolError(message)
314
364
  : sdkCreateToolError(message);
315
365
  const registeredToolNames = new Set();
316
- const lightTools = new Set(["certscore_scan_site", "certscore_get_scan_status", "certscore_get_scan_bundle"]);
366
+ const lightTools = new Set(["certscore_scan_site", "certscore_get_scan_status", "certscore_get_scan_bundle", "certscore_get_report_evidence_page"]);
317
367
  const scanIdTools = new Set([
318
368
  "certscore_explain_finding",
319
369
  "certscore_export_findings",
320
370
  "certscore_get_evidence",
321
371
  "certscore_get_pre_consent_cookies_trackers",
322
372
  "certscore_get_report",
373
+ "certscore_get_report_evidence_page",
323
374
  "certscore_get_scan",
324
375
  "certscore_get_scan_bundle",
325
376
  "certscore_get_scan_status",
@@ -334,7 +385,7 @@ export function createCertScoreMcpServer(options = {}) {
334
385
  return registerRequest(schema, handler);
335
386
  return registerRequest(CallToolRequestSchema, async (request, extra) => {
336
387
  const startedAt = Date.now();
337
- const requestId = randomUUID();
388
+ const requestId = options.resolveRequestId?.() ?? randomUUID();
338
389
  const name = request.params.name;
339
390
  if (options.onToolInvocationStarted) {
340
391
  void Promise.resolve().then(() => options.onToolInvocationStarted({ requestId, toolName: name, startedAt: new Date(startedAt).toISOString() }))
@@ -395,7 +446,7 @@ export function createCertScoreMcpServer(options = {}) {
395
446
  requestId,
396
447
  timing: { startedAt: new Date(startedAt).toISOString(), responseGeneratedAt: new Date().toISOString() },
397
448
  captureBasis: "protocol_request",
398
- callerInput: captureMcpCallerInput(args, request.params._meta),
449
+ callerInput: captureMcpCallerInput(args, request.params._meta, { expanded: process.env.MCP_EXPANDED_CALLER_INPUT_ENABLED === "1" }),
399
450
  ...(taskContext ? { taskContext } : {}),
400
451
  response: {
401
452
  summary: captureMcpResponse(result, protocolFailure),
@@ -421,10 +472,19 @@ export function createCertScoreMcpServer(options = {}) {
421
472
  const scanId = input && typeof input === "object" && !Array.isArray(input)
422
473
  ? input.scanId : null;
423
474
  return scanIdTools.has(name) && !isCanonicalScanId(scanId)
424
- ? toInvalidScanIdToolError() : typedHandler(input, extra);
475
+ ? toInvalidScanIdToolError() : withResponseGuidance(name, input, await typedHandler(input, extra));
425
476
  });
426
477
  registeredToolNames.add(name);
427
478
  };
479
+ registerTool("certscore_get_connection_status", toolContract("certscore_get_connection_status"), async (_input, extra) => {
480
+ try {
481
+ const status = await clientForRequest(extra).getConnectionStatus();
482
+ return toToolResult(status, JSON.stringify(status));
483
+ }
484
+ catch (error) {
485
+ return toToolError(error);
486
+ }
487
+ });
428
488
  registerTool("certscore_scan_site", toolContract("certscore_scan_site"), async (input, extra) => {
429
489
  const toolStartedAtMs = Date.now();
430
490
  const creationStartedAtMs = toolStartedAtMs;
@@ -446,6 +506,8 @@ export function createCertScoreMcpServer(options = {}) {
446
506
  reused: created.reused === true,
447
507
  status: created.status ?? null,
448
508
  }));
509
+ let retainedPreviewWaitMs = 0;
510
+ let retainedInternalReadCount = 0;
449
511
  let initialResult = created;
450
512
  const stableScanId = typeof created.scanId === "string" && created.scanId
451
513
  ? created.scanId
@@ -497,6 +559,8 @@ export function createCertScoreMcpServer(options = {}) {
497
559
  scanId: stableScanId,
498
560
  }));
499
561
  }
562
+ retainedPreviewWaitMs = Date.now() - previewWaitStartedAtMs;
563
+ retainedInternalReadCount = internalReadCount;
500
564
  console.log(JSON.stringify({
501
565
  event: "mcp.certscore_scan_site.preview_wait_completed",
502
566
  durationMs: Date.now() - previewWaitStartedAtMs,
@@ -508,7 +572,11 @@ export function createCertScoreMcpServer(options = {}) {
508
572
  }));
509
573
  }
510
574
  const guided = withExampleDomainDemo(withMcpAgentGuidance(initialResult, "unknown", "scan_creation"), demoSubstitution);
511
- return toToolResult(guided, exampleDomainDemoText(guided, demoSubstitution));
575
+ const toolResult = toToolResult(guided, exampleDomainDemoText(guided, demoSubstitution));
576
+ return options.toolProfile === "light" ? withResponseCapture(toolResult, {
577
+ firstResult: ["completed", "completed_limited"].includes(initialResult.status) ? "completed" : ["failed", "expired"].includes(initialResult.status) ? "failed" : hasPreConsentPreview(initialResult) ? "preview" : activeScan(initialResult) ? "queued" : "unknown",
578
+ previewWaitMs: retainedPreviewWaitMs, internalReadCount: retainedInternalReadCount,
579
+ }) : toolResult;
512
580
  }
513
581
  catch (error) {
514
582
  console.warn(JSON.stringify({
@@ -519,7 +587,8 @@ export function createCertScoreMcpServer(options = {}) {
519
587
  return toToolError(error, { scanCreation: !creationCompleted });
520
588
  }
521
589
  });
522
- registerTool("certscore_get_scan", toolContract("certscore_get_scan"), async ({ scanId }) => {
590
+ registerTool("certscore_get_scan", toolContract("certscore_get_scan"), async ({ scanId }, extra) => {
591
+ const client = clientForRequest(extra);
523
592
  try {
524
593
  return toToolResult(await client.scans.get(scanId));
525
594
  }
@@ -543,7 +612,8 @@ export function createCertScoreMcpServer(options = {}) {
543
612
  return toToolError(error);
544
613
  }
545
614
  });
546
- registerTool("certscore_get_report", toolContract("certscore_get_report"), async ({ scanId, detail, format }) => {
615
+ registerTool("certscore_get_report", toolContract("certscore_get_report"), async ({ scanId, detail, format }, extra) => {
616
+ const client = clientForRequest(extra);
547
617
  try {
548
618
  const normalizedFormat = normalizeFormat(format);
549
619
  const result = normalizedFormat === "markdown"
@@ -570,7 +640,8 @@ export function createCertScoreMcpServer(options = {}) {
570
640
  return toToolError(error);
571
641
  }
572
642
  });
573
- registerTool("certscore_get_evidence", toolContract("certscore_get_evidence"), async ({ scanId }) => {
643
+ registerTool("certscore_get_evidence", toolContract("certscore_get_evidence"), async ({ scanId }, extra) => {
644
+ const client = clientForRequest(extra);
574
645
  try {
575
646
  const bounded = boundEvidencePacket(await client.getScan(scanId, { detail: "evidence", format: "json" }), MAX_EVIDENCE_PACKET_CHARS - 2_500);
576
647
  const guided = withMcpAgentGuidance(bounded, "existing_scan_retrieved");
@@ -580,6 +651,15 @@ export function createCertScoreMcpServer(options = {}) {
580
651
  return toToolError(error);
581
652
  }
582
653
  });
654
+ registerTool("certscore_get_report_evidence_page", toolContract("certscore_get_report_evidence_page"), async ({ scanId, cursor }, extra) => {
655
+ try {
656
+ const page = reportEvidencePageSchema.parse(await clientForRequest(extra).getReportEvidencePage(scanId, { cursor, timeout: 30_000, internalMcpOperation: { operation: "scan_bundle", scanId } }));
657
+ return toToolResult(page, `Report evidence for ${scanId}: ${page.pagination.offset + 1}–${page.pagination.offset + page.pagination.returned} of ${page.pagination.total} entries. ${page.pagination.complete ? "Export complete; preserve report coverage limitations." : `Continue with certscore_get_report_evidence_page using scanId and cursor ${page.pagination.nextCursor}.`} Evidence values are in structuredContent.entries. ${page.download ? `Full report JSON: ${page.download.url} (${page.download.bytes} bytes). ${page.download.instructions}` : ""} ${page.reportUrl}`);
658
+ }
659
+ catch (error) {
660
+ return toToolError(error);
661
+ }
662
+ });
583
663
  registerTool("certscore_get_scan_bundle", toolContract("certscore_get_scan_bundle"), async ({ scanId, detail = "summary", maxBytes, maxFindings, maxPreConsentRows }, extra) => {
584
664
  const client = clientForRequest(extra);
585
665
  try {
@@ -605,7 +685,7 @@ export function createCertScoreMcpServer(options = {}) {
605
685
  responseCeilingBytes,
606
686
  scan
607
687
  });
608
- return toToolResult(bundle, scanBundleText(bundle));
688
+ return toToolResult(bundle, scanBundleText(bundle, { lightTrialCta: options.toolProfile === "light" }));
609
689
  }
610
690
  const includeEvidence = detail === "evidence" || detail === "full";
611
691
  const reportDetail = detail === "full" ? "full" : includeEvidence ? "evidence" : "summary";
@@ -630,13 +710,14 @@ export function createCertScoreMcpServer(options = {}) {
630
710
  responseCeilingBytes,
631
711
  scan
632
712
  });
633
- return toToolResult(bundle, scanBundleText(bundle));
713
+ return toToolResult(bundle, scanBundleText(bundle, { lightTrialCta: options.toolProfile === "light" }));
634
714
  }
635
715
  catch (error) {
636
716
  return toToolError(error);
637
717
  }
638
718
  });
639
- registerTool("certscore_export_findings", toolContract("certscore_export_findings"), async ({ scanId }) => {
719
+ registerTool("certscore_export_findings", toolContract("certscore_export_findings"), async ({ scanId }, extra) => {
720
+ const client = clientForRequest(extra);
640
721
  try {
641
722
  const report = await client.getScan(scanId, { detail: "full", format: "json" });
642
723
  const guided = withMcpAgentGuidance(exportFindings(report), "existing_scan_retrieved");
@@ -646,7 +727,8 @@ export function createCertScoreMcpServer(options = {}) {
646
727
  return toToolError(error);
647
728
  }
648
729
  });
649
- registerTool("certscore_list_findings", toolContract("certscore_list_findings"), async ({ limit, offset, scanId }) => {
730
+ registerTool("certscore_list_findings", toolContract("certscore_list_findings"), async ({ limit, offset, scanId }, extra) => {
731
+ const client = clientForRequest(extra);
650
732
  try {
651
733
  const guided = withMcpAgentGuidance(paginateFindingList(await client.findings.list(scanId), { limit, offset }), "existing_scan_retrieved");
652
734
  return toToolResult(guided, findingListText(guided));
@@ -655,7 +737,8 @@ export function createCertScoreMcpServer(options = {}) {
655
737
  return toToolError(error);
656
738
  }
657
739
  });
658
- registerTool("certscore_get_pre_consent_cookies_trackers", toolContract("certscore_get_pre_consent_cookies_trackers"), async ({ maxRows, scanId }) => {
740
+ registerTool("certscore_get_pre_consent_cookies_trackers", toolContract("certscore_get_pre_consent_cookies_trackers"), async ({ maxRows, scanId }, extra) => {
741
+ const client = clientForRequest(extra);
659
742
  try {
660
743
  const guided = withMcpAgentGuidance(limitPreConsentRows(await client.scans.preConsentCookiesTrackers(scanId), { maxRows }), "existing_scan_retrieved");
661
744
  return toToolResult(guided, preConsentInventoryText(guided));
@@ -664,7 +747,8 @@ export function createCertScoreMcpServer(options = {}) {
664
747
  return toToolError(error);
665
748
  }
666
749
  });
667
- registerTool("certscore_explain_finding", toolContract("certscore_explain_finding"), async ({ scanId, findingId }) => {
750
+ registerTool("certscore_explain_finding", toolContract("certscore_explain_finding"), async ({ scanId, findingId }, extra) => {
751
+ const client = clientForRequest(extra);
668
752
  try {
669
753
  return toToolResult(await client.findings.explain(scanId, findingId));
670
754
  }
@@ -672,7 +756,8 @@ export function createCertScoreMcpServer(options = {}) {
672
756
  return toToolError(error);
673
757
  }
674
758
  });
675
- registerTool("certscore_get_latest_domain_scan", toolContract("certscore_get_latest_domain_scan"), async ({ domain, scanFrom }) => {
759
+ registerTool("certscore_get_latest_domain_scan", toolContract("certscore_get_latest_domain_scan"), async ({ domain, scanFrom }, extra) => {
760
+ const client = clientForRequest(extra);
676
761
  try {
677
762
  return toToolResult(await client.domains.latest(domain, { scanFrom }));
678
763
  }
@@ -680,7 +765,8 @@ export function createCertScoreMcpServer(options = {}) {
680
765
  return toToolError(error);
681
766
  }
682
767
  });
683
- registerTool("certscore_get_latest_domain_pre_consent_cookies_trackers", toolContract("certscore_get_latest_domain_pre_consent_cookies_trackers"), async ({ domain, maxRows, scanFrom }) => {
768
+ registerTool("certscore_get_latest_domain_pre_consent_cookies_trackers", toolContract("certscore_get_latest_domain_pre_consent_cookies_trackers"), async ({ domain, maxRows, scanFrom }, extra) => {
769
+ const client = clientForRequest(extra);
684
770
  try {
685
771
  const guided = withMcpAgentGuidance(limitPreConsentRows(await client.domains.latestPreConsentCookiesTrackers(domain, { scanFrom }), { maxRows }), "existing_scan_retrieved");
686
772
  return toToolResult(guided, preConsentInventoryText(guided));
@@ -690,5 +776,12 @@ export function createCertScoreMcpServer(options = {}) {
690
776
  }
691
777
  });
692
778
  server.server.setRequestHandler = registerRequest;
779
+ // Reconnecting hosts may retain a tool catalog from an older release.
780
+ // Announce the current catalog once the new session has initialized.
781
+ server.server.oninitialized = () => {
782
+ void server.server.sendToolListChanged().catch(() => {
783
+ // A host without a notification channel can still call tools/list normally.
784
+ });
785
+ };
693
786
  return server;
694
787
  }
package/dist/tools.d.ts CHANGED
@@ -48,6 +48,10 @@ export declare function scanIdFromPulse(report: PulseResult): string | null;
48
48
  export declare function findingsFromReport(report: PulseResult): TopFinding[];
49
49
  export declare function exportFindings(report: PulseResult): {
50
50
  type: string;
51
+ exportVersion: string;
52
+ exportedAt: string;
53
+ returnedFindingCount: number;
54
+ completeness: string;
51
55
  scanId: string | null;
52
56
  domain: string | null;
53
57
  summary: import("packages/certscore-sdk/dist/types.js").PulseSummary | null;
@@ -80,7 +84,9 @@ export declare function findingListText(value: Record<string, any>, label?: stri
80
84
  export declare function preConsentInventoryText(value: Record<string, any>): string;
81
85
  export declare function pulseReportText(value: Record<string, any>, label?: string): string;
82
86
  export declare function markdownReportText(value: Record<string, any>): string;
83
- export declare function scanBundleText(bundle: Record<string, any>): string;
87
+ export declare function scanBundleText(bundle: Record<string, any>, options?: {
88
+ lightTrialCta?: boolean;
89
+ }): string;
84
90
  export declare function buildScanBundle(input: {
85
91
  detail?: "summary" | "findings" | "evidence" | "full";
86
92
  evidence?: PulseResult | null;
@@ -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":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAA4C,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,KAAK,yBAAyB,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAetO,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAoBjD,KAAK,sBAAsB,GAAG,eAAe,GAAG,aAAa,GAAG,UAAU,CAAC;AAE3E,KAAK,kBAAkB,GAAG,kBAAkB,GAAG,gCAAgC,GAAG,yBAAyB,GAAG,SAAS,CAAC;AAcxH,KAAK,eAAe,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AA6CF,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,CAa5E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,cAAc,CAoEpG;AAED,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,EAAE,CAAA;CAAE,GAAG,cAAc,CA6C9K;AAED,wBAAgB,wBAAwB,IAAI,cAAc,CAczD;AA+GD,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAChE,KAAK,EAAE,CAAC,EACR,sBAAsB,GAAE,kBAA8B,EACtD,mBAAmB,GAAE,sBAAmC,GACvD,CAAC,GAAG;IACL,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,yBAAyB,EAAE,MAAM,CAAC;CACnC,CA8DA;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,sBAAsB,EAAE,kBAAkB;;;;;;;;WAlE3G,eAAe,GAAG,IAAI;+BACF,MAAM;EAuElC;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,kBAAkB,SAA4B,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0C9H;AAqZD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,WAAW,CAE5E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,WAAW,CAE5E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,iBAEjD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,iBAGlD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,EAAE,CAQpE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;EA0BjD;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,OAAO,EAAE,CAAC,EACV,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAChD,CAAC,CAoBH;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,OAAO,EAAE,CAAC,EACV,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACjC,CAAC,CA2BH;AAwMD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,UAmBxD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,YAAY,GAAE,MAAM,EAAO,UAmBnF;AAkKD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,SAAiC,UAcjG;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,UAqBjE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,SAAqB,UAyBrF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,UAS5D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,UAoJpG;AA2ED,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,MAAM,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;IACtD,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yBAAyB,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC7D,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,IAAI,EAAE,YAAY,CAAC;CACpB,uBAgaA;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCpE"}