@certscore/mcp 0.2.20 → 0.2.22
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 +44 -8
- package/dist/adoption.d.ts +7 -0
- package/dist/adoption.d.ts.map +1 -0
- package/dist/adoption.js +23 -0
- package/dist/certscore-mcp.mjs +3927 -3252
- 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 +15 -0
- package/dist/response-capture.d.ts.map +1 -0
- package/dist/response-capture.js +89 -0
- package/dist/response-guidance.d.ts +4 -0
- package/dist/response-guidance.d.ts.map +1 -0
- package/dist/response-guidance.js +100 -0
- package/dist/server.d.ts +18 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +182 -37
- package/dist/tools.d.ts +18 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +146 -58
- package/package.json +1 -1
- package/server-light.json +1 -1
- package/server.json +2 -2
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAMrD,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgDD,wBAAsB,2BAA2B,CAAC,OAAO,GAAE,yBAA8B;;;GAyFxF"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAMrD,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgDD,wBAAsB,2BAA2B,CAAC,OAAO,GAAE,yBAA8B;;;GAyFxF;AAsBD,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type McpResponseSummary } from "@website-signal-risk-scanner/shared/dist/mcp-response-summary.js";
|
|
2
|
+
type SafeMetadata = {
|
|
3
|
+
firstResult?: McpResponseSummary["firstResult"];
|
|
4
|
+
previewWaitMs?: number;
|
|
5
|
+
internalReadCount?: number;
|
|
6
|
+
message?: string;
|
|
7
|
+
recommendedNextAction?: string;
|
|
8
|
+
upstream?: McpResponseSummary["upstream"];
|
|
9
|
+
};
|
|
10
|
+
/** Only call with template-owned text. Never mark upstream messages or caller values. */
|
|
11
|
+
export declare function withResponseCapture<T extends object>(result: T, metadata: SafeMetadata): T;
|
|
12
|
+
export declare function transferResponseCapture<T extends object>(payload: unknown, result: T): T;
|
|
13
|
+
export declare function captureMcpResponse(result: unknown, protocolError?: unknown): McpResponseSummary;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=response-capture.d.ts.map
|
|
@@ -0,0 +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,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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { mcpResponseSummarySchema } from "@website-signal-risk-scanner/shared/dist/mcp-response-summary.js";
|
|
2
|
+
const controlled = new WeakMap();
|
|
3
|
+
/** Only call with template-owned text. Never mark upstream messages or caller values. */
|
|
4
|
+
export function withResponseCapture(result, metadata) {
|
|
5
|
+
controlled.set(result, { ...controlled.get(result), ...metadata });
|
|
6
|
+
return result;
|
|
7
|
+
}
|
|
8
|
+
export function transferResponseCapture(payload, result) {
|
|
9
|
+
const metadata = payload && typeof payload === "object" ? controlled.get(payload) : undefined;
|
|
10
|
+
return metadata ? withResponseCapture(result, metadata) : result;
|
|
11
|
+
}
|
|
12
|
+
const record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
13
|
+
const token = (value) => typeof value === "string" && /^[a-zA-Z0-9_.:-]{1,80}$/.test(value) ? value : undefined;
|
|
14
|
+
export function captureMcpResponse(result, protocolError) {
|
|
15
|
+
const outer = record(result);
|
|
16
|
+
let payload = record(outer.structuredContent);
|
|
17
|
+
if (!Object.keys(payload).length && Array.isArray(outer.content)) {
|
|
18
|
+
const text = outer.content.find((item) => item?.type === "text")?.text;
|
|
19
|
+
if (typeof text === "string") {
|
|
20
|
+
try {
|
|
21
|
+
payload = record(JSON.parse(text));
|
|
22
|
+
}
|
|
23
|
+
catch { /* Markdown is never retained. */ }
|
|
24
|
+
}
|
|
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 };
|
|
41
|
+
const protocol = record(protocolError);
|
|
42
|
+
const error = protocolError ? record(protocol.data) : record(payload.error);
|
|
43
|
+
const safe = controlled.get(protocolError ? protocol : outer) ?? {};
|
|
44
|
+
const retryAfterSeconds = Object.hasOwn(error, "retryAfterSeconds") ? error.retryAfterSeconds : payload.retryAfterSeconds ?? next.retryAfterSeconds;
|
|
45
|
+
const summary = {
|
|
46
|
+
version: 1, captureBasis: "response_generated", templateVersion: "2026-09-11.1",
|
|
47
|
+
kind: protocolError ? "protocol_error" : "tool_result", isError: protocolError ? true : outer.isError === true,
|
|
48
|
+
type: token(payload.type), status: token(scan.status ?? scan.scanStatus), errorCode: token(error.code), reasonCode: token(error.reasonCode),
|
|
49
|
+
...(Number.isInteger(protocolError ? protocol.code : error.mcpCode) ? { mcpCode: protocolError ? protocol.code : error.mcpCode } : {}),
|
|
50
|
+
...(typeof error.retryable === "boolean" ? { retryable: error.retryable } : {}),
|
|
51
|
+
...(retryAfterSeconds === null || Number.isInteger(retryAfterSeconds) && retryAfterSeconds >= 0 && retryAfterSeconds <= 86400 ? { retryAfterSeconds } : {}),
|
|
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 } : {}),
|
|
63
|
+
...safe,
|
|
64
|
+
textOmitted: Boolean((protocolError ? protocol.message : error.message) && !safe.message || (error.recommendedNextAction || payload.recommendedNextAction) && !safe.recommendedNextAction),
|
|
65
|
+
summaryTruncated: false,
|
|
66
|
+
};
|
|
67
|
+
// Issues are captured only from our controlled validation response.
|
|
68
|
+
if (safe.message && Array.isArray(error.issues))
|
|
69
|
+
summary.issues = error.issues.slice(0, 8).map((issue) => ({ field: token(issue.field) ?? "arguments", code: token(issue.code) ?? "invalid", ...(issue.required === true ? { required: true } : {}) }));
|
|
70
|
+
for (const [key, limit] of [["message", 400], ["recommendedNextAction", 800]]) {
|
|
71
|
+
if ((summary[key]?.length ?? 0) > limit) {
|
|
72
|
+
summary[key] = summary[key].slice(0, limit);
|
|
73
|
+
summary.summaryTruncated = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Bound UTF-8 bytes as well as character counts; preserve diagnosis before prose.
|
|
77
|
+
for (const key of ["recommendedNextAction", "message", "issues"]) {
|
|
78
|
+
if (Buffer.byteLength(JSON.stringify(summary)) <= 2048)
|
|
79
|
+
break;
|
|
80
|
+
delete summary[key];
|
|
81
|
+
summary.summaryTruncated = true;
|
|
82
|
+
summary.textOmitted = true;
|
|
83
|
+
}
|
|
84
|
+
const parsed = mcpResponseSummarySchema.safeParse(summary);
|
|
85
|
+
return parsed.success ? parsed.data : {
|
|
86
|
+
version: 1, captureBasis: "response_generated", templateVersion: "2026-09-11.1",
|
|
87
|
+
kind: summary.kind, isError: summary.isError, textOmitted: true, summaryTruncated: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -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
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import type { McpResponseSummary } from "@website-signal-risk-scanner/shared";
|
|
1
2
|
import { type McpCallerInput } from "@website-signal-risk-scanner/shared/dist/mcp-caller-input.js";
|
|
2
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
4
|
import { type RequestInfo } from "@modelcontextprotocol/sdk/types.js";
|
|
4
5
|
import { type McpTaskContext } from "@website-signal-risk-scanner/shared/dist/mcp-product-context.js";
|
|
5
6
|
export interface CertScoreMcpOptions {
|
|
6
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;
|
|
7
12
|
baseUrl?: string;
|
|
8
13
|
forwardedClientIp?: string | null;
|
|
9
14
|
resolveForwardedClientIp?: (headers: RequestInfo["headers"]) => string | null;
|
|
@@ -14,19 +19,31 @@ export interface CertScoreMcpOptions {
|
|
|
14
19
|
toolProfile?: "full" | "light";
|
|
15
20
|
initialPreConsentPreviewWaitMs?: number;
|
|
16
21
|
exampleDomainDemoUrl?: string | null;
|
|
22
|
+
resolveRequestId?: () => string | undefined;
|
|
23
|
+
onToolInvocationStarted?: (input: {
|
|
24
|
+
requestId: string;
|
|
25
|
+
toolName: string;
|
|
26
|
+
startedAt: string;
|
|
27
|
+
}) => void | Promise<void>;
|
|
17
28
|
onToolInvocation?: (observation: McpToolInvocationObservation, requestContext: McpToolInvocationRequestContext) => void | Promise<void>;
|
|
18
29
|
}
|
|
19
30
|
export type McpToolInvocationRequestContext = {
|
|
20
31
|
headers: RequestInfo["headers"] | null;
|
|
21
32
|
};
|
|
22
33
|
export type McpToolInvocationObservation = {
|
|
34
|
+
requestId?: string;
|
|
35
|
+
timing?: {
|
|
36
|
+
startedAt: string;
|
|
37
|
+
responseGeneratedAt: string;
|
|
38
|
+
};
|
|
23
39
|
callerInput?: McpCallerInput;
|
|
24
40
|
captureBasis?: "protocol_request";
|
|
25
41
|
taskContext?: McpTaskContext;
|
|
26
42
|
response?: {
|
|
27
|
-
bytes: number;
|
|
43
|
+
bytes: number | null;
|
|
28
44
|
truncated: boolean | null;
|
|
29
45
|
effectiveMaxBytes?: number;
|
|
46
|
+
summary?: McpResponseSummary;
|
|
30
47
|
};
|
|
31
48
|
requestArguments?: {
|
|
32
49
|
values: Record<string, string | number | boolean>;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|