@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/server.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { registerAdoptionFeatures, EXAMPLE_SCAN_ID } from "./adoption.js";
|
|
2
|
+
import { withResponseGuidance } from "./response-guidance.js";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { captureMcpResponse, withResponseCapture } from "./response-capture.js";
|
|
5
|
+
import { z } from "zod";
|
|
1
6
|
import { captureMcpCallerInput } from "@website-signal-risk-scanner/shared/dist/mcp-caller-input.js";
|
|
2
|
-
import { CertScoreClient } from "@certscore/sdk";
|
|
3
|
-
import { certScoreMcpToolContracts, isCanonicalScanId } from "@certscore/api-contracts";
|
|
7
|
+
import { CertScoreClient, CertScoreError } from "@certscore/sdk";
|
|
8
|
+
import { certScoreMcpToolContracts, isCanonicalScanId, reportEvidencePageSchema } from "@certscore/api-contracts";
|
|
4
9
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
-
import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
6
11
|
import { sanitizeMcpTaskContext } from "@website-signal-risk-scanner/shared/dist/mcp-product-context.js";
|
|
7
12
|
import { CERTSCORE_MCP_VERSION } from "./version.js";
|
|
8
13
|
import { boundEvidencePacket, buildScanBundle, exportFindings, findingListText, limitPreConsentRows, markdownReportText, MAX_EVIDENCE_PACKET_CHARS, normalizeDetail, normalizeFormat, paginateFindingList, preConsentInventoryText, pulseReportText, scanBundleText, scanSiteText, scanStatusText, toInvalidArgumentsToolError, toInvalidScanIdToolError, toToolError, toToolResult, withMcpAgentGuidance, withMcpScanProvenanceGuidance } from "./tools.js";
|
|
@@ -202,11 +207,21 @@ export function projectMcpToolInvocationObservation(input) {
|
|
|
202
207
|
const error = result.error && typeof result.error === "object" && !Array.isArray(result.error)
|
|
203
208
|
? result.error
|
|
204
209
|
: null;
|
|
205
|
-
const
|
|
210
|
+
const completedNoGo = result.status === "completed_limited"
|
|
211
|
+
&& result.resultDisposition === "no_go";
|
|
212
|
+
// A completed no-go is a usable terminal scan result. Agent guidance embeds
|
|
213
|
+
// its reason-specific remedy in `error`, but the MCP tool call itself
|
|
214
|
+
// succeeded and must not be counted as a transport or invocation failure.
|
|
215
|
+
const errorCode = completedNoGo
|
|
216
|
+
? null
|
|
217
|
+
: boundedTelemetryToken(error?.code ?? result.errorCode, 100);
|
|
206
218
|
const rateLimited = errorCode === "rate_limited" || result.status === "rate_limited";
|
|
207
|
-
const isError =
|
|
219
|
+
const isError = !completedNoGo
|
|
220
|
+
&& (Boolean(input.result?.isError) || Boolean(error));
|
|
208
221
|
const outcome = rateLimited ? "rate_limited" : isError ? "error" : "success";
|
|
209
|
-
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);
|
|
210
225
|
const inputScanId = boundedTelemetryToken(args.scanId, 128);
|
|
211
226
|
const requestedResource = requestedTelemetryResource(args);
|
|
212
227
|
const targetHostname = input.toolName === "certscore_scan_site"
|
|
@@ -239,11 +254,12 @@ export function projectMcpToolInvocationObservation(input) {
|
|
|
239
254
|
scanDecision,
|
|
240
255
|
scanFrom: args.scanFrom === "eu_de" || args.scanFrom === "eu_ie" || args.scanFrom === "california"
|
|
241
256
|
? args.scanFrom
|
|
242
|
-
:
|
|
243
|
-
?
|
|
257
|
+
: scan.scanFrom === "eu_de" || scan.scanFrom === "eu_ie" || scan.scanFrom === "california"
|
|
258
|
+
? scan.scanFrom
|
|
244
259
|
: null,
|
|
245
|
-
scanId:
|
|
246
|
-
|
|
260
|
+
scanId: outcome === "error" && ["invalid_scan_id", "invalid_arguments", "invalid_url", "unknown_tool"].includes(errorCode ?? "")
|
|
261
|
+
? null : resultScanId ?? inputScanId,
|
|
262
|
+
scanStatus: boundedTelemetryToken(scan.status ?? scan.scanStatus, 64),
|
|
247
263
|
targetHostname,
|
|
248
264
|
toolName: input.toolName,
|
|
249
265
|
transportOutcome: isError ? "mcp_error" : "mcp_result",
|
|
@@ -278,8 +294,8 @@ function observeToolInvocation(observer, observation, requestContext) {
|
|
|
278
294
|
});
|
|
279
295
|
}
|
|
280
296
|
export function createCertScoreMcpServer(options = {}) {
|
|
281
|
-
const createClient = (forwardedClientIp, anonymousRequesterSession) => new CertScoreClient({
|
|
282
|
-
apiKey
|
|
297
|
+
const createClient = (forwardedClientIp, anonymousRequesterSession, apiKey = options.apiKey) => new CertScoreClient({
|
|
298
|
+
apiKey,
|
|
283
299
|
baseUrl: options.baseUrl,
|
|
284
300
|
clientName: "mcp",
|
|
285
301
|
forwardedClientIp,
|
|
@@ -289,24 +305,72 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
289
305
|
timeout: options.timeout
|
|
290
306
|
});
|
|
291
307
|
const client = createClient(options.forwardedClientIp, options.resolveAnonymousRequesterSession?.());
|
|
292
|
-
const clientForRequest = (extra) =>
|
|
293
|
-
|
|
294
|
-
|
|
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
|
+
};
|
|
295
317
|
const server = new McpServer({
|
|
296
318
|
name: "certscore",
|
|
297
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
|
+
})
|
|
298
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
|
+
});
|
|
299
361
|
const sdkCreateToolError = server.createToolError.bind(server);
|
|
300
362
|
server.createToolError = (message) => message.includes("Input validation error:")
|
|
301
363
|
? toInvalidArgumentsToolError(message)
|
|
302
364
|
: sdkCreateToolError(message);
|
|
303
|
-
const
|
|
365
|
+
const registeredToolNames = new Set();
|
|
366
|
+
const lightTools = new Set(["certscore_scan_site", "certscore_get_scan_status", "certscore_get_scan_bundle", "certscore_get_report_evidence_page"]);
|
|
304
367
|
const scanIdTools = new Set([
|
|
305
368
|
"certscore_explain_finding",
|
|
306
369
|
"certscore_export_findings",
|
|
307
370
|
"certscore_get_evidence",
|
|
308
371
|
"certscore_get_pre_consent_cookies_trackers",
|
|
309
372
|
"certscore_get_report",
|
|
373
|
+
"certscore_get_report_evidence_page",
|
|
310
374
|
"certscore_get_scan",
|
|
311
375
|
"certscore_get_scan_bundle",
|
|
312
376
|
"certscore_get_scan_status",
|
|
@@ -321,17 +385,51 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
321
385
|
return registerRequest(schema, handler);
|
|
322
386
|
return registerRequest(CallToolRequestSchema, async (request, extra) => {
|
|
323
387
|
const startedAt = Date.now();
|
|
388
|
+
const requestId = options.resolveRequestId?.() ?? randomUUID();
|
|
324
389
|
const name = request.params.name;
|
|
390
|
+
if (options.onToolInvocationStarted) {
|
|
391
|
+
void Promise.resolve().then(() => options.onToolInvocationStarted({ requestId, toolName: name, startedAt: new Date(startedAt).toISOString() }))
|
|
392
|
+
.catch(() => console.error("[certscore-mcp] request-start observation failed"));
|
|
393
|
+
}
|
|
325
394
|
const args = request.params.arguments ?? {};
|
|
326
|
-
const
|
|
327
|
-
|
|
395
|
+
const taskContext = sanitizeMcpTaskContext(args.taskContext);
|
|
396
|
+
const forwardedRequest = name === "certscore_scan_site" && args.taskContext !== undefined && !taskContext
|
|
397
|
+
? {
|
|
398
|
+
...request,
|
|
399
|
+
params: {
|
|
400
|
+
...request.params,
|
|
401
|
+
arguments: Object.fromEntries(Object.entries(args).filter(([key]) => key !== "taskContext")),
|
|
402
|
+
},
|
|
403
|
+
}
|
|
404
|
+
: request;
|
|
405
|
+
const known = registeredToolNames.has(name);
|
|
328
406
|
let result;
|
|
407
|
+
let protocolFailure;
|
|
329
408
|
try {
|
|
330
|
-
|
|
409
|
+
if (!known) {
|
|
410
|
+
result = { isError: true, structuredContent: { error: { code: "unknown_tool" } } };
|
|
411
|
+
const availableTools = [...registeredToolNames];
|
|
412
|
+
const recommendedNextAction = "Refresh the available tools using your MCP client's tool discovery (tools/list), then call a supported tool.";
|
|
413
|
+
throw withResponseCapture(new McpError(ErrorCode.InvalidParams, `This tool is unavailable on this endpoint. ${recommendedNextAction} Available tools: ${availableTools.join(", ")}.`, { code: "unknown_tool", retryable: false, recommendedNextAction, availableTools }), { message: `This tool is unavailable on this endpoint. ${recommendedNextAction} Available tools: ${availableTools.join(", ")}.`, recommendedNextAction });
|
|
414
|
+
}
|
|
415
|
+
const contract = certScoreMcpToolContracts.find(candidate => candidate.name === name);
|
|
416
|
+
const validation = z.object(contract.inputSchema).safeParse(forwardedRequest.params.arguments ?? {});
|
|
417
|
+
result = validation.success
|
|
418
|
+
? await handler(forwardedRequest, extra)
|
|
419
|
+
: toInvalidArgumentsToolError(`Input validation error: tool ${name}`, {
|
|
420
|
+
tool: name,
|
|
421
|
+
issues: validation.error.issues.map(issue => ({
|
|
422
|
+
// Only schema-owned top-level names; never echo values or dynamic keys.
|
|
423
|
+
field: typeof issue.path[0] === "string" && Object.hasOwn(contract.inputSchema, issue.path[0]) ? issue.path[0] : "arguments",
|
|
424
|
+
code: issue.code,
|
|
425
|
+
...(issue.code === "invalid_type" && issue.received === "undefined" ? { required: true } : {}),
|
|
426
|
+
})).filter((issue, index, issues) => issues.findIndex(other => other.field === issue.field) === index).slice(0, 8),
|
|
427
|
+
});
|
|
331
428
|
return result;
|
|
332
429
|
}
|
|
333
430
|
catch (error) {
|
|
334
|
-
|
|
431
|
+
protocolFailure = error;
|
|
432
|
+
result ??= { isError: true, structuredContent: { error: { code: "handler_exception" } } };
|
|
335
433
|
throw error;
|
|
336
434
|
}
|
|
337
435
|
finally {
|
|
@@ -343,14 +441,16 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
343
441
|
observation.errorCode = "protocol_error";
|
|
344
442
|
const payload = telemetryResultRecord(result);
|
|
345
443
|
const metadata = payload.mcpMetadata;
|
|
346
|
-
const taskContext = sanitizeMcpTaskContext(args.taskContext);
|
|
347
444
|
observeToolInvocation(options.onToolInvocation, {
|
|
348
445
|
...observation,
|
|
446
|
+
requestId,
|
|
447
|
+
timing: { startedAt: new Date(startedAt).toISOString(), responseGeneratedAt: new Date().toISOString() },
|
|
349
448
|
captureBasis: "protocol_request",
|
|
350
|
-
callerInput: captureMcpCallerInput(args, request.params._meta),
|
|
449
|
+
callerInput: captureMcpCallerInput(args, request.params._meta, { expanded: process.env.MCP_EXPANDED_CALLER_INPUT_ENABLED === "1" }),
|
|
351
450
|
...(taskContext ? { taskContext } : {}),
|
|
352
451
|
response: {
|
|
353
|
-
|
|
452
|
+
summary: captureMcpResponse(result, protocolFailure),
|
|
453
|
+
bytes: protocolFailure ? null : Math.min(10_000_000, Buffer.byteLength(JSON.stringify(result ?? null))),
|
|
354
454
|
truncated: typeof metadata?.truncated === "boolean" ? metadata.truncated : null,
|
|
355
455
|
...(typeof metadata?.effectiveMaxBytes === "number" ? { effectiveMaxBytes: metadata.effectiveMaxBytes } : {}),
|
|
356
456
|
},
|
|
@@ -372,20 +472,32 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
372
472
|
const scanId = input && typeof input === "object" && !Array.isArray(input)
|
|
373
473
|
? input.scanId : null;
|
|
374
474
|
return scanIdTools.has(name) && !isCanonicalScanId(scanId)
|
|
375
|
-
? toInvalidScanIdToolError() : typedHandler(input, extra);
|
|
475
|
+
? toInvalidScanIdToolError() : withResponseGuidance(name, input, await typedHandler(input, extra));
|
|
376
476
|
});
|
|
477
|
+
registeredToolNames.add(name);
|
|
377
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
|
+
});
|
|
378
488
|
registerTool("certscore_scan_site", toolContract("certscore_scan_site"), async (input, extra) => {
|
|
379
489
|
const toolStartedAtMs = Date.now();
|
|
380
490
|
const creationStartedAtMs = toolStartedAtMs;
|
|
381
491
|
const client = clientForRequest(extra);
|
|
382
492
|
const demoSubstitution = exampleDomainDemoSubstitution(input.url, options.exampleDomainDemoUrl);
|
|
383
493
|
const effectiveUrl = demoSubstitution?.effectiveUrl ?? input.url;
|
|
494
|
+
let creationCompleted = false;
|
|
384
495
|
try {
|
|
385
496
|
const created = await client.scans.create(effectiveUrl, {
|
|
386
497
|
freshness: input.freshness ?? "latest",
|
|
387
498
|
scanFrom: input.scanFrom
|
|
388
499
|
});
|
|
500
|
+
creationCompleted = true;
|
|
389
501
|
console.log(JSON.stringify({
|
|
390
502
|
event: "mcp.certscore_scan_site.creation_completed",
|
|
391
503
|
durationMs: Date.now() - creationStartedAtMs,
|
|
@@ -394,6 +506,8 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
394
506
|
reused: created.reused === true,
|
|
395
507
|
status: created.status ?? null,
|
|
396
508
|
}));
|
|
509
|
+
let retainedPreviewWaitMs = 0;
|
|
510
|
+
let retainedInternalReadCount = 0;
|
|
397
511
|
let initialResult = created;
|
|
398
512
|
const stableScanId = typeof created.scanId === "string" && created.scanId
|
|
399
513
|
? created.scanId
|
|
@@ -445,6 +559,8 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
445
559
|
scanId: stableScanId,
|
|
446
560
|
}));
|
|
447
561
|
}
|
|
562
|
+
retainedPreviewWaitMs = Date.now() - previewWaitStartedAtMs;
|
|
563
|
+
retainedInternalReadCount = internalReadCount;
|
|
448
564
|
console.log(JSON.stringify({
|
|
449
565
|
event: "mcp.certscore_scan_site.preview_wait_completed",
|
|
450
566
|
durationMs: Date.now() - previewWaitStartedAtMs,
|
|
@@ -456,7 +572,11 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
456
572
|
}));
|
|
457
573
|
}
|
|
458
574
|
const guided = withExampleDomainDemo(withMcpAgentGuidance(initialResult, "unknown", "scan_creation"), demoSubstitution);
|
|
459
|
-
|
|
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;
|
|
460
580
|
}
|
|
461
581
|
catch (error) {
|
|
462
582
|
console.warn(JSON.stringify({
|
|
@@ -464,10 +584,11 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
464
584
|
durationMs: Date.now() - creationStartedAtMs,
|
|
465
585
|
errorName: error instanceof Error ? error.name : "UnknownError",
|
|
466
586
|
}));
|
|
467
|
-
return toToolError(error);
|
|
587
|
+
return toToolError(error, { scanCreation: !creationCompleted });
|
|
468
588
|
}
|
|
469
589
|
});
|
|
470
|
-
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);
|
|
471
592
|
try {
|
|
472
593
|
return toToolResult(await client.scans.get(scanId));
|
|
473
594
|
}
|
|
@@ -491,7 +612,8 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
491
612
|
return toToolError(error);
|
|
492
613
|
}
|
|
493
614
|
});
|
|
494
|
-
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);
|
|
495
617
|
try {
|
|
496
618
|
const normalizedFormat = normalizeFormat(format);
|
|
497
619
|
const result = normalizedFormat === "markdown"
|
|
@@ -518,7 +640,8 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
518
640
|
return toToolError(error);
|
|
519
641
|
}
|
|
520
642
|
});
|
|
521
|
-
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);
|
|
522
645
|
try {
|
|
523
646
|
const bounded = boundEvidencePacket(await client.getScan(scanId, { detail: "evidence", format: "json" }), MAX_EVIDENCE_PACKET_CHARS - 2_500);
|
|
524
647
|
const guided = withMcpAgentGuidance(bounded, "existing_scan_retrieved");
|
|
@@ -528,6 +651,15 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
528
651
|
return toToolError(error);
|
|
529
652
|
}
|
|
530
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
|
+
});
|
|
531
663
|
registerTool("certscore_get_scan_bundle", toolContract("certscore_get_scan_bundle"), async ({ scanId, detail = "summary", maxBytes, maxFindings, maxPreConsentRows }, extra) => {
|
|
532
664
|
const client = clientForRequest(extra);
|
|
533
665
|
try {
|
|
@@ -553,7 +685,7 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
553
685
|
responseCeilingBytes,
|
|
554
686
|
scan
|
|
555
687
|
});
|
|
556
|
-
return toToolResult(bundle, scanBundleText(bundle));
|
|
688
|
+
return toToolResult(bundle, scanBundleText(bundle, { lightTrialCta: options.toolProfile === "light" }));
|
|
557
689
|
}
|
|
558
690
|
const includeEvidence = detail === "evidence" || detail === "full";
|
|
559
691
|
const reportDetail = detail === "full" ? "full" : includeEvidence ? "evidence" : "summary";
|
|
@@ -578,13 +710,14 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
578
710
|
responseCeilingBytes,
|
|
579
711
|
scan
|
|
580
712
|
});
|
|
581
|
-
return toToolResult(bundle, scanBundleText(bundle));
|
|
713
|
+
return toToolResult(bundle, scanBundleText(bundle, { lightTrialCta: options.toolProfile === "light" }));
|
|
582
714
|
}
|
|
583
715
|
catch (error) {
|
|
584
716
|
return toToolError(error);
|
|
585
717
|
}
|
|
586
718
|
});
|
|
587
|
-
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);
|
|
588
721
|
try {
|
|
589
722
|
const report = await client.getScan(scanId, { detail: "full", format: "json" });
|
|
590
723
|
const guided = withMcpAgentGuidance(exportFindings(report), "existing_scan_retrieved");
|
|
@@ -594,7 +727,8 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
594
727
|
return toToolError(error);
|
|
595
728
|
}
|
|
596
729
|
});
|
|
597
|
-
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);
|
|
598
732
|
try {
|
|
599
733
|
const guided = withMcpAgentGuidance(paginateFindingList(await client.findings.list(scanId), { limit, offset }), "existing_scan_retrieved");
|
|
600
734
|
return toToolResult(guided, findingListText(guided));
|
|
@@ -603,7 +737,8 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
603
737
|
return toToolError(error);
|
|
604
738
|
}
|
|
605
739
|
});
|
|
606
|
-
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);
|
|
607
742
|
try {
|
|
608
743
|
const guided = withMcpAgentGuidance(limitPreConsentRows(await client.scans.preConsentCookiesTrackers(scanId), { maxRows }), "existing_scan_retrieved");
|
|
609
744
|
return toToolResult(guided, preConsentInventoryText(guided));
|
|
@@ -612,7 +747,8 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
612
747
|
return toToolError(error);
|
|
613
748
|
}
|
|
614
749
|
});
|
|
615
|
-
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);
|
|
616
752
|
try {
|
|
617
753
|
return toToolResult(await client.findings.explain(scanId, findingId));
|
|
618
754
|
}
|
|
@@ -620,7 +756,8 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
620
756
|
return toToolError(error);
|
|
621
757
|
}
|
|
622
758
|
});
|
|
623
|
-
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);
|
|
624
761
|
try {
|
|
625
762
|
return toToolResult(await client.domains.latest(domain, { scanFrom }));
|
|
626
763
|
}
|
|
@@ -628,7 +765,8 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
628
765
|
return toToolError(error);
|
|
629
766
|
}
|
|
630
767
|
});
|
|
631
|
-
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);
|
|
632
770
|
try {
|
|
633
771
|
const guided = withMcpAgentGuidance(limitPreConsentRows(await client.domains.latestPreConsentCookiesTrackers(domain, { scanFrom }), { maxRows }), "existing_scan_retrieved");
|
|
634
772
|
return toToolResult(guided, preConsentInventoryText(guided));
|
|
@@ -638,5 +776,12 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
638
776
|
}
|
|
639
777
|
});
|
|
640
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
|
+
};
|
|
641
786
|
return server;
|
|
642
787
|
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -13,8 +13,17 @@ type ActionableError = {
|
|
|
13
13
|
mcpCode?: number;
|
|
14
14
|
};
|
|
15
15
|
export declare function toToolResult(payload: unknown, text?: string): CallToolResult;
|
|
16
|
-
export declare function toToolError(error: unknown
|
|
17
|
-
|
|
16
|
+
export declare function toToolError(error: unknown, context?: {
|
|
17
|
+
scanCreation?: boolean;
|
|
18
|
+
}): CallToolResult;
|
|
19
|
+
export declare function toInvalidArgumentsToolError(errorMessage: string, validation?: {
|
|
20
|
+
tool: string;
|
|
21
|
+
issues: {
|
|
22
|
+
field: string;
|
|
23
|
+
code: string;
|
|
24
|
+
required?: boolean;
|
|
25
|
+
}[];
|
|
26
|
+
}): CallToolResult;
|
|
18
27
|
export declare function toInvalidScanIdToolError(): CallToolResult;
|
|
19
28
|
export declare function withMcpAgentGuidance<T extends Record<string, any>>(value: T, fallbackProvenanceMode?: ScanProvenanceMode, pollGuidanceContext?: McpPollGuidanceContext): T & {
|
|
20
29
|
error: ActionableError | null;
|
|
@@ -39,6 +48,10 @@ export declare function scanIdFromPulse(report: PulseResult): string | null;
|
|
|
39
48
|
export declare function findingsFromReport(report: PulseResult): TopFinding[];
|
|
40
49
|
export declare function exportFindings(report: PulseResult): {
|
|
41
50
|
type: string;
|
|
51
|
+
exportVersion: string;
|
|
52
|
+
exportedAt: string;
|
|
53
|
+
returnedFindingCount: number;
|
|
54
|
+
completeness: string;
|
|
42
55
|
scanId: string | null;
|
|
43
56
|
domain: string | null;
|
|
44
57
|
summary: import("packages/certscore-sdk/dist/types.js").PulseSummary | null;
|
|
@@ -71,7 +84,9 @@ export declare function findingListText(value: Record<string, any>, label?: stri
|
|
|
71
84
|
export declare function preConsentInventoryText(value: Record<string, any>): string;
|
|
72
85
|
export declare function pulseReportText(value: Record<string, any>, label?: string): string;
|
|
73
86
|
export declare function markdownReportText(value: Record<string, any>): string;
|
|
74
|
-
export declare function scanBundleText(bundle: Record<string, any
|
|
87
|
+
export declare function scanBundleText(bundle: Record<string, any>, options?: {
|
|
88
|
+
lightTrialCta?: boolean;
|
|
89
|
+
}): string;
|
|
75
90
|
export declare function buildScanBundle(input: {
|
|
76
91
|
detail?: "summary" | "findings" | "evidence" | "full";
|
|
77
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":"
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|