@certscore/mcp 0.2.20 → 0.2.21
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 +3 -3
- package/dist/certscore-mcp.mjs +3442 -3173
- 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 +12 -0
- package/dist/response-capture.d.ts.map +1 -0
- package/dist/response-capture.js +64 -0
- package/dist/server.d.ts +13 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +63 -11
- package/dist/tools.d.ts +11 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +102 -50
- 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,12 @@
|
|
|
1
|
+
import { type McpResponseSummary } from "@website-signal-risk-scanner/shared/dist/mcp-response-summary.js";
|
|
2
|
+
type SafeMetadata = {
|
|
3
|
+
message?: string;
|
|
4
|
+
recommendedNextAction?: string;
|
|
5
|
+
upstream?: McpResponseSummary["upstream"];
|
|
6
|
+
};
|
|
7
|
+
/** Only call with template-owned text. Never mark upstream messages or caller values. */
|
|
8
|
+
export declare function withResponseCapture<T extends object>(result: T, metadata: SafeMetadata): T;
|
|
9
|
+
export declare function transferResponseCapture<T extends object>(payload: unknown, result: T): T;
|
|
10
|
+
export declare function captureMcpResponse(result: unknown, protocolError?: unknown): McpResponseSummary;
|
|
11
|
+
export {};
|
|
12
|
+
//# 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,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAC,UAAU,CAAC,CAAA;CAAE,CAAC;AAEpH,yFAAyF;AACzF,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY,GAAG,CAAC,CAG1F;AACD,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAGxF;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAsC/F"}
|
|
@@ -0,0 +1,64 @@
|
|
|
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, 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 protocol = record(protocolError);
|
|
27
|
+
const error = protocolError ? record(protocol.data) : record(payload.error);
|
|
28
|
+
const safe = controlled.get(protocolError ? protocol : outer) ?? {};
|
|
29
|
+
const retryAfterSeconds = Object.hasOwn(error, "retryAfterSeconds") ? error.retryAfterSeconds : payload.retryAfterSeconds;
|
|
30
|
+
const summary = {
|
|
31
|
+
version: 1, captureBasis: "response_generated", templateVersion: "2026-09-11.1",
|
|
32
|
+
kind: protocolError ? "protocol_error" : "tool_result", isError: protocolError ? true : outer.isError === true,
|
|
33
|
+
type: token(payload.type), status: token(payload.status), errorCode: token(error.code), reasonCode: token(error.reasonCode),
|
|
34
|
+
...(Number.isInteger(protocolError ? protocol.code : error.mcpCode) ? { mcpCode: protocolError ? protocol.code : error.mcpCode } : {}),
|
|
35
|
+
...(typeof error.retryable === "boolean" ? { retryable: error.retryable } : {}),
|
|
36
|
+
...(retryAfterSeconds === null || Number.isInteger(retryAfterSeconds) && retryAfterSeconds >= 0 && retryAfterSeconds <= 86400 ? { retryAfterSeconds } : {}),
|
|
37
|
+
recommendedNextTool: payload.recommendedNextTool === null ? null : token(payload.recommendedNextTool),
|
|
38
|
+
...safe,
|
|
39
|
+
textOmitted: Boolean((protocolError ? protocol.message : error.message) && !safe.message || (error.recommendedNextAction || payload.recommendedNextAction) && !safe.recommendedNextAction),
|
|
40
|
+
summaryTruncated: false,
|
|
41
|
+
};
|
|
42
|
+
// Issues are captured only from our controlled validation response.
|
|
43
|
+
if (safe.message && Array.isArray(error.issues))
|
|
44
|
+
summary.issues = error.issues.slice(0, 8).map((issue) => ({ field: token(issue.field) ?? "arguments", code: token(issue.code) ?? "invalid", ...(issue.required === true ? { required: true } : {}) }));
|
|
45
|
+
for (const [key, limit] of [["message", 400], ["recommendedNextAction", 800]]) {
|
|
46
|
+
if ((summary[key]?.length ?? 0) > limit) {
|
|
47
|
+
summary[key] = summary[key].slice(0, limit);
|
|
48
|
+
summary.summaryTruncated = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Bound UTF-8 bytes as well as character counts; preserve diagnosis before prose.
|
|
52
|
+
for (const key of ["recommendedNextAction", "message", "issues"]) {
|
|
53
|
+
if (Buffer.byteLength(JSON.stringify(summary)) <= 2048)
|
|
54
|
+
break;
|
|
55
|
+
delete summary[key];
|
|
56
|
+
summary.summaryTruncated = true;
|
|
57
|
+
summary.textOmitted = true;
|
|
58
|
+
}
|
|
59
|
+
const parsed = mcpResponseSummarySchema.safeParse(summary);
|
|
60
|
+
return parsed.success ? parsed.data : {
|
|
61
|
+
version: 1, captureBasis: "response_generated", templateVersion: "2026-09-11.1",
|
|
62
|
+
kind: summary.kind, isError: summary.isError, textOmitted: true, summaryTruncated: true,
|
|
63
|
+
};
|
|
64
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
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";
|
|
@@ -14,19 +15,30 @@ export interface CertScoreMcpOptions {
|
|
|
14
15
|
toolProfile?: "full" | "light";
|
|
15
16
|
initialPreConsentPreviewWaitMs?: number;
|
|
16
17
|
exampleDomainDemoUrl?: string | null;
|
|
18
|
+
onToolInvocationStarted?: (input: {
|
|
19
|
+
requestId: string;
|
|
20
|
+
toolName: string;
|
|
21
|
+
startedAt: string;
|
|
22
|
+
}) => void | Promise<void>;
|
|
17
23
|
onToolInvocation?: (observation: McpToolInvocationObservation, requestContext: McpToolInvocationRequestContext) => void | Promise<void>;
|
|
18
24
|
}
|
|
19
25
|
export type McpToolInvocationRequestContext = {
|
|
20
26
|
headers: RequestInfo["headers"] | null;
|
|
21
27
|
};
|
|
22
28
|
export type McpToolInvocationObservation = {
|
|
29
|
+
requestId?: string;
|
|
30
|
+
timing?: {
|
|
31
|
+
startedAt: string;
|
|
32
|
+
responseGeneratedAt: string;
|
|
33
|
+
};
|
|
23
34
|
callerInput?: McpCallerInput;
|
|
24
35
|
captureBasis?: "protocol_request";
|
|
25
36
|
taskContext?: McpTaskContext;
|
|
26
37
|
response?: {
|
|
27
|
-
bytes: number;
|
|
38
|
+
bytes: number | null;
|
|
28
39
|
truncated: boolean | null;
|
|
29
40
|
effectiveMaxBytes?: number;
|
|
41
|
+
summary?: McpResponseSummary;
|
|
30
42
|
};
|
|
31
43
|
requestArguments?: {
|
|
32
44
|
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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"}
|
package/dist/server.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { captureMcpResponse, withResponseCapture } from "./response-capture.js";
|
|
3
|
+
import { z } from "zod";
|
|
1
4
|
import { captureMcpCallerInput } from "@website-signal-risk-scanner/shared/dist/mcp-caller-input.js";
|
|
2
5
|
import { CertScoreClient } from "@certscore/sdk";
|
|
3
6
|
import { certScoreMcpToolContracts, isCanonicalScanId } from "@certscore/api-contracts";
|
|
4
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
-
import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
import { CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
6
9
|
import { sanitizeMcpTaskContext } from "@website-signal-risk-scanner/shared/dist/mcp-product-context.js";
|
|
7
10
|
import { CERTSCORE_MCP_VERSION } from "./version.js";
|
|
8
11
|
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,9 +205,17 @@ export function projectMcpToolInvocationObservation(input) {
|
|
|
202
205
|
const error = result.error && typeof result.error === "object" && !Array.isArray(result.error)
|
|
203
206
|
? result.error
|
|
204
207
|
: null;
|
|
205
|
-
const
|
|
208
|
+
const completedNoGo = result.status === "completed_limited"
|
|
209
|
+
&& result.resultDisposition === "no_go";
|
|
210
|
+
// A completed no-go is a usable terminal scan result. Agent guidance embeds
|
|
211
|
+
// its reason-specific remedy in `error`, but the MCP tool call itself
|
|
212
|
+
// succeeded and must not be counted as a transport or invocation failure.
|
|
213
|
+
const errorCode = completedNoGo
|
|
214
|
+
? null
|
|
215
|
+
: boundedTelemetryToken(error?.code ?? result.errorCode, 100);
|
|
206
216
|
const rateLimited = errorCode === "rate_limited" || result.status === "rate_limited";
|
|
207
|
-
const isError =
|
|
217
|
+
const isError = !completedNoGo
|
|
218
|
+
&& (Boolean(input.result?.isError) || Boolean(error));
|
|
208
219
|
const outcome = rateLimited ? "rate_limited" : isError ? "error" : "success";
|
|
209
220
|
const resultScanId = boundedTelemetryToken(result.scanId ?? result.scan_id ?? result.jobId, 128);
|
|
210
221
|
const inputScanId = boundedTelemetryToken(args.scanId, 128);
|
|
@@ -242,7 +253,8 @@ export function projectMcpToolInvocationObservation(input) {
|
|
|
242
253
|
: result.scanFrom === "eu_de" || result.scanFrom === "eu_ie" || result.scanFrom === "california"
|
|
243
254
|
? result.scanFrom
|
|
244
255
|
: null,
|
|
245
|
-
scanId:
|
|
256
|
+
scanId: outcome === "error" && ["invalid_scan_id", "invalid_arguments", "invalid_url", "unknown_tool"].includes(errorCode ?? "")
|
|
257
|
+
? null : resultScanId ?? inputScanId,
|
|
246
258
|
scanStatus: boundedTelemetryToken(result.status, 64),
|
|
247
259
|
targetHostname,
|
|
248
260
|
toolName: input.toolName,
|
|
@@ -300,6 +312,7 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
300
312
|
server.createToolError = (message) => message.includes("Input validation error:")
|
|
301
313
|
? toInvalidArgumentsToolError(message)
|
|
302
314
|
: sdkCreateToolError(message);
|
|
315
|
+
const registeredToolNames = new Set();
|
|
303
316
|
const lightTools = new Set(["certscore_scan_site", "certscore_get_scan_status", "certscore_get_scan_bundle"]);
|
|
304
317
|
const scanIdTools = new Set([
|
|
305
318
|
"certscore_explain_finding",
|
|
@@ -321,17 +334,51 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
321
334
|
return registerRequest(schema, handler);
|
|
322
335
|
return registerRequest(CallToolRequestSchema, async (request, extra) => {
|
|
323
336
|
const startedAt = Date.now();
|
|
337
|
+
const requestId = randomUUID();
|
|
324
338
|
const name = request.params.name;
|
|
339
|
+
if (options.onToolInvocationStarted) {
|
|
340
|
+
void Promise.resolve().then(() => options.onToolInvocationStarted({ requestId, toolName: name, startedAt: new Date(startedAt).toISOString() }))
|
|
341
|
+
.catch(() => console.error("[certscore-mcp] request-start observation failed"));
|
|
342
|
+
}
|
|
325
343
|
const args = request.params.arguments ?? {};
|
|
326
|
-
const
|
|
327
|
-
|
|
344
|
+
const taskContext = sanitizeMcpTaskContext(args.taskContext);
|
|
345
|
+
const forwardedRequest = name === "certscore_scan_site" && args.taskContext !== undefined && !taskContext
|
|
346
|
+
? {
|
|
347
|
+
...request,
|
|
348
|
+
params: {
|
|
349
|
+
...request.params,
|
|
350
|
+
arguments: Object.fromEntries(Object.entries(args).filter(([key]) => key !== "taskContext")),
|
|
351
|
+
},
|
|
352
|
+
}
|
|
353
|
+
: request;
|
|
354
|
+
const known = registeredToolNames.has(name);
|
|
328
355
|
let result;
|
|
356
|
+
let protocolFailure;
|
|
329
357
|
try {
|
|
330
|
-
|
|
358
|
+
if (!known) {
|
|
359
|
+
result = { isError: true, structuredContent: { error: { code: "unknown_tool" } } };
|
|
360
|
+
const availableTools = [...registeredToolNames];
|
|
361
|
+
const recommendedNextAction = "Refresh the available tools using your MCP client's tool discovery (tools/list), then call a supported tool.";
|
|
362
|
+
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 });
|
|
363
|
+
}
|
|
364
|
+
const contract = certScoreMcpToolContracts.find(candidate => candidate.name === name);
|
|
365
|
+
const validation = z.object(contract.inputSchema).safeParse(forwardedRequest.params.arguments ?? {});
|
|
366
|
+
result = validation.success
|
|
367
|
+
? await handler(forwardedRequest, extra)
|
|
368
|
+
: toInvalidArgumentsToolError(`Input validation error: tool ${name}`, {
|
|
369
|
+
tool: name,
|
|
370
|
+
issues: validation.error.issues.map(issue => ({
|
|
371
|
+
// Only schema-owned top-level names; never echo values or dynamic keys.
|
|
372
|
+
field: typeof issue.path[0] === "string" && Object.hasOwn(contract.inputSchema, issue.path[0]) ? issue.path[0] : "arguments",
|
|
373
|
+
code: issue.code,
|
|
374
|
+
...(issue.code === "invalid_type" && issue.received === "undefined" ? { required: true } : {}),
|
|
375
|
+
})).filter((issue, index, issues) => issues.findIndex(other => other.field === issue.field) === index).slice(0, 8),
|
|
376
|
+
});
|
|
331
377
|
return result;
|
|
332
378
|
}
|
|
333
379
|
catch (error) {
|
|
334
|
-
|
|
380
|
+
protocolFailure = error;
|
|
381
|
+
result ??= { isError: true, structuredContent: { error: { code: "handler_exception" } } };
|
|
335
382
|
throw error;
|
|
336
383
|
}
|
|
337
384
|
finally {
|
|
@@ -343,14 +390,16 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
343
390
|
observation.errorCode = "protocol_error";
|
|
344
391
|
const payload = telemetryResultRecord(result);
|
|
345
392
|
const metadata = payload.mcpMetadata;
|
|
346
|
-
const taskContext = sanitizeMcpTaskContext(args.taskContext);
|
|
347
393
|
observeToolInvocation(options.onToolInvocation, {
|
|
348
394
|
...observation,
|
|
395
|
+
requestId,
|
|
396
|
+
timing: { startedAt: new Date(startedAt).toISOString(), responseGeneratedAt: new Date().toISOString() },
|
|
349
397
|
captureBasis: "protocol_request",
|
|
350
398
|
callerInput: captureMcpCallerInput(args, request.params._meta),
|
|
351
399
|
...(taskContext ? { taskContext } : {}),
|
|
352
400
|
response: {
|
|
353
|
-
|
|
401
|
+
summary: captureMcpResponse(result, protocolFailure),
|
|
402
|
+
bytes: protocolFailure ? null : Math.min(10_000_000, Buffer.byteLength(JSON.stringify(result ?? null))),
|
|
354
403
|
truncated: typeof metadata?.truncated === "boolean" ? metadata.truncated : null,
|
|
355
404
|
...(typeof metadata?.effectiveMaxBytes === "number" ? { effectiveMaxBytes: metadata.effectiveMaxBytes } : {}),
|
|
356
405
|
},
|
|
@@ -374,6 +423,7 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
374
423
|
return scanIdTools.has(name) && !isCanonicalScanId(scanId)
|
|
375
424
|
? toInvalidScanIdToolError() : typedHandler(input, extra);
|
|
376
425
|
});
|
|
426
|
+
registeredToolNames.add(name);
|
|
377
427
|
};
|
|
378
428
|
registerTool("certscore_scan_site", toolContract("certscore_scan_site"), async (input, extra) => {
|
|
379
429
|
const toolStartedAtMs = Date.now();
|
|
@@ -381,11 +431,13 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
381
431
|
const client = clientForRequest(extra);
|
|
382
432
|
const demoSubstitution = exampleDomainDemoSubstitution(input.url, options.exampleDomainDemoUrl);
|
|
383
433
|
const effectiveUrl = demoSubstitution?.effectiveUrl ?? input.url;
|
|
434
|
+
let creationCompleted = false;
|
|
384
435
|
try {
|
|
385
436
|
const created = await client.scans.create(effectiveUrl, {
|
|
386
437
|
freshness: input.freshness ?? "latest",
|
|
387
438
|
scanFrom: input.scanFrom
|
|
388
439
|
});
|
|
440
|
+
creationCompleted = true;
|
|
389
441
|
console.log(JSON.stringify({
|
|
390
442
|
event: "mcp.certscore_scan_site.creation_completed",
|
|
391
443
|
durationMs: Date.now() - creationStartedAtMs,
|
|
@@ -464,7 +516,7 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
464
516
|
durationMs: Date.now() - creationStartedAtMs,
|
|
465
517
|
errorName: error instanceof Error ? error.name : "UnknownError",
|
|
466
518
|
}));
|
|
467
|
-
return toToolError(error);
|
|
519
|
+
return toToolError(error, { scanCreation: !creationCompleted });
|
|
468
520
|
}
|
|
469
521
|
});
|
|
470
522
|
registerTool("certscore_get_scan", toolContract("certscore_get_scan"), async ({ scanId }) => {
|
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;
|
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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"}
|