@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.
- package/README.md +47 -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 +689 -255
- package/dist/response-capture.d.ts +3 -0
- package/dist/response-capture.d.ts.map +1 -1
- package/dist/response-capture.js +29 -4
- 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 +5 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +120 -27
- package/dist/tools.d.ts +7 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +92 -21
- package/package.json +1 -1
- package/server-light.json +1 -1
- package/server.json +2 -2
|
@@ -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;
|
|
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"}
|
package/dist/response-capture.js
CHANGED
|
@@ -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(
|
|
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;
|
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":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAE9E,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,8DAA8D,CAAC;AAG1H,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAA8C,KAAK,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAClH,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,iEAAiE,CAAC;AAI9H,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,kFAAkF;IAClF,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IAC7B,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,MAAM,MAAM,GAAG,SAAS,CAAC;IAC5C,uBAAuB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtH,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,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5D,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,YAAY,CAAC,EAAE,kBAAkB,CAAC;IAClC,WAAW,CAAC,EAAE,cAAc,CAAC;IAC7B,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;KAAE,CAAC;IACzH,gBAAgB,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3F,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,eAAe,GAAG,UAAU,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzK,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,qGAAqG;AACrG,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;EAwBzE;AAED,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,CAkE/B;AAiCD,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,mBAAwB,aA6iBzE"}
|
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
|
|
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
|
-
:
|
|
254
|
-
?
|
|
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(
|
|
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
|
|
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) =>
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
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
|
|
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;
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|