@certscore/mcp 0.2.19 → 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 +16 -3
- package/dist/certscore-mcp.mjs +20095 -15081
- 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 +41 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +164 -34
- package/dist/tools.d.ts +11 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +157 -53
- package/package.json +3 -2
- 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,5 +1,8 @@
|
|
|
1
|
+
import type { McpResponseSummary } from "@website-signal-risk-scanner/shared";
|
|
2
|
+
import { type McpCallerInput } from "@website-signal-risk-scanner/shared/dist/mcp-caller-input.js";
|
|
1
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import type
|
|
4
|
+
import { type RequestInfo } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { type McpTaskContext } from "@website-signal-risk-scanner/shared/dist/mcp-product-context.js";
|
|
3
6
|
export interface CertScoreMcpOptions {
|
|
4
7
|
apiKey?: string;
|
|
5
8
|
baseUrl?: string;
|
|
@@ -12,12 +15,44 @@ export interface CertScoreMcpOptions {
|
|
|
12
15
|
toolProfile?: "full" | "light";
|
|
13
16
|
initialPreConsentPreviewWaitMs?: number;
|
|
14
17
|
exampleDomainDemoUrl?: string | null;
|
|
18
|
+
onToolInvocationStarted?: (input: {
|
|
19
|
+
requestId: string;
|
|
20
|
+
toolName: string;
|
|
21
|
+
startedAt: string;
|
|
22
|
+
}) => void | Promise<void>;
|
|
15
23
|
onToolInvocation?: (observation: McpToolInvocationObservation, requestContext: McpToolInvocationRequestContext) => void | Promise<void>;
|
|
16
24
|
}
|
|
17
25
|
export type McpToolInvocationRequestContext = {
|
|
18
26
|
headers: RequestInfo["headers"] | null;
|
|
19
27
|
};
|
|
20
28
|
export type McpToolInvocationObservation = {
|
|
29
|
+
requestId?: string;
|
|
30
|
+
timing?: {
|
|
31
|
+
startedAt: string;
|
|
32
|
+
responseGeneratedAt: string;
|
|
33
|
+
};
|
|
34
|
+
callerInput?: McpCallerInput;
|
|
35
|
+
captureBasis?: "protocol_request";
|
|
36
|
+
taskContext?: McpTaskContext;
|
|
37
|
+
response?: {
|
|
38
|
+
bytes: number | null;
|
|
39
|
+
truncated: boolean | null;
|
|
40
|
+
effectiveMaxBytes?: number;
|
|
41
|
+
summary?: McpResponseSummary;
|
|
42
|
+
};
|
|
43
|
+
requestArguments?: {
|
|
44
|
+
values: Record<string, string | number | boolean>;
|
|
45
|
+
omitted: boolean;
|
|
46
|
+
};
|
|
47
|
+
rateLimit?: {
|
|
48
|
+
kind: "scan_creation" | "upstream";
|
|
49
|
+
retryAfterSeconds?: number;
|
|
50
|
+
scope?: string;
|
|
51
|
+
windowId?: string;
|
|
52
|
+
limit?: number;
|
|
53
|
+
used?: number;
|
|
54
|
+
windowSeconds?: number;
|
|
55
|
+
};
|
|
21
56
|
durationMs: number;
|
|
22
57
|
errorCode: string | null;
|
|
23
58
|
freshness: "latest" | "refresh" | null;
|
|
@@ -34,6 +69,11 @@ export type McpToolInvocationObservation = {
|
|
|
34
69
|
toolName: string;
|
|
35
70
|
transportOutcome: "mcp_result" | "mcp_error";
|
|
36
71
|
};
|
|
72
|
+
/** Retain only bounded tool options; URL paths, queries and unknown/free-text inputs are omitted. */
|
|
73
|
+
export declare function projectMcpTelemetryArguments(args: Record<string, unknown>): {
|
|
74
|
+
values: Record<string, string | number | boolean>;
|
|
75
|
+
omitted: boolean;
|
|
76
|
+
};
|
|
37
77
|
export declare function projectMcpToolInvocationObservation(input: {
|
|
38
78
|
args: unknown;
|
|
39
79
|
durationMs: number;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { captureMcpResponse, withResponseCapture } from "./response-capture.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { captureMcpCallerInput } from "@website-signal-risk-scanner/shared/dist/mcp-caller-input.js";
|
|
1
5
|
import { CertScoreClient } from "@certscore/sdk";
|
|
2
6
|
import { certScoreMcpToolContracts, isCanonicalScanId } from "@certscore/api-contracts";
|
|
3
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import { CallToolRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { sanitizeMcpTaskContext } from "@website-signal-risk-scanner/shared/dist/mcp-product-context.js";
|
|
4
10
|
import { CERTSCORE_MCP_VERSION } from "./version.js";
|
|
5
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";
|
|
6
12
|
const LIGHT_MCP_BUNDLE_RESPONSE_CEILING_BYTES = 25_000;
|
|
@@ -159,6 +165,38 @@ function isCertScoreCanaryUrl(value) {
|
|
|
159
165
|
return false;
|
|
160
166
|
}
|
|
161
167
|
}
|
|
168
|
+
/** Retain only bounded tool options; URL paths, queries and unknown/free-text inputs are omitted. */
|
|
169
|
+
export function projectMcpTelemetryArguments(args) {
|
|
170
|
+
const values = {};
|
|
171
|
+
const options = {
|
|
172
|
+
freshness: ["latest", "refresh"], scanFrom: ["eu_de", "eu_ie", "california"],
|
|
173
|
+
detail: ["tiny", "quick", "standard", "full", "summary", "evidence", "findings"], format: ["json", "markdown"],
|
|
174
|
+
};
|
|
175
|
+
for (const [key, allowed] of Object.entries(options)) {
|
|
176
|
+
if (typeof args[key] === "string" && allowed.includes(args[key]))
|
|
177
|
+
values[key] = args[key];
|
|
178
|
+
}
|
|
179
|
+
for (const key of ["scanId", "jobId", "findingId"]) {
|
|
180
|
+
const token = boundedTelemetryToken(args[key], 128);
|
|
181
|
+
if (token)
|
|
182
|
+
values[key] = token;
|
|
183
|
+
}
|
|
184
|
+
for (const key of ["maxWaitSeconds", "maxBytes", "maxFindings", "maxPreConsentRows", "maxRows", "limit", "offset"]) {
|
|
185
|
+
const value = args[key];
|
|
186
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 1_000_000)
|
|
187
|
+
values[key] = value;
|
|
188
|
+
}
|
|
189
|
+
if (typeof args.waitForCompletion === "boolean")
|
|
190
|
+
values.waitForCompletion = args.waitForCompletion;
|
|
191
|
+
const url = telemetryUrl(args.url);
|
|
192
|
+
if (url)
|
|
193
|
+
values.url = url;
|
|
194
|
+
const domain = telemetryHostname(args.domain);
|
|
195
|
+
if (domain && /^[a-z0-9.-]+$/.test(domain))
|
|
196
|
+
values.domain = domain;
|
|
197
|
+
const omitted = Object.keys(args).some((key) => !(key in values) || args[key] !== values[key]);
|
|
198
|
+
return { values, omitted };
|
|
199
|
+
}
|
|
162
200
|
export function projectMcpToolInvocationObservation(input) {
|
|
163
201
|
const args = input.args && typeof input.args === "object" && !Array.isArray(input.args)
|
|
164
202
|
? input.args
|
|
@@ -167,9 +205,17 @@ export function projectMcpToolInvocationObservation(input) {
|
|
|
167
205
|
const error = result.error && typeof result.error === "object" && !Array.isArray(result.error)
|
|
168
206
|
? result.error
|
|
169
207
|
: null;
|
|
170
|
-
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);
|
|
171
216
|
const rateLimited = errorCode === "rate_limited" || result.status === "rate_limited";
|
|
172
|
-
const isError =
|
|
217
|
+
const isError = !completedNoGo
|
|
218
|
+
&& (Boolean(input.result?.isError) || Boolean(error));
|
|
173
219
|
const outcome = rateLimited ? "rate_limited" : isError ? "error" : "success";
|
|
174
220
|
const resultScanId = boundedTelemetryToken(result.scanId ?? result.scan_id ?? result.jobId, 128);
|
|
175
221
|
const inputScanId = boundedTelemetryToken(args.scanId, 128);
|
|
@@ -192,6 +238,8 @@ export function projectMcpToolInvocationObservation(input) {
|
|
|
192
238
|
? "new"
|
|
193
239
|
: "unavailable";
|
|
194
240
|
return {
|
|
241
|
+
requestArguments: projectMcpTelemetryArguments(args),
|
|
242
|
+
...(rateLimited ? { rateLimit: projectMcpRateLimit(error ?? result) } : {}),
|
|
195
243
|
durationMs: Math.max(0, Math.min(Math.round(input.durationMs), 3_600_000)),
|
|
196
244
|
errorCode: rateLimited ? "rate_limited" : errorCode,
|
|
197
245
|
freshness: args.freshness === "refresh" ? "refresh" : input.toolName === "certscore_scan_site" ? "latest" : null,
|
|
@@ -205,13 +253,30 @@ export function projectMcpToolInvocationObservation(input) {
|
|
|
205
253
|
: result.scanFrom === "eu_de" || result.scanFrom === "eu_ie" || result.scanFrom === "california"
|
|
206
254
|
? result.scanFrom
|
|
207
255
|
: null,
|
|
208
|
-
scanId:
|
|
256
|
+
scanId: outcome === "error" && ["invalid_scan_id", "invalid_arguments", "invalid_url", "unknown_tool"].includes(errorCode ?? "")
|
|
257
|
+
? null : resultScanId ?? inputScanId,
|
|
209
258
|
scanStatus: boundedTelemetryToken(result.status, 64),
|
|
210
259
|
targetHostname,
|
|
211
260
|
toolName: input.toolName,
|
|
212
261
|
transportOutcome: isError ? "mcp_error" : "mcp_result",
|
|
213
262
|
};
|
|
214
263
|
}
|
|
264
|
+
function projectMcpRateLimit(error) {
|
|
265
|
+
const creation = error.creationRateLimit && typeof error.creationRateLimit === "object" && !Array.isArray(error.creationRateLimit)
|
|
266
|
+
? error.creationRateLimit : null;
|
|
267
|
+
const projected = { kind: creation ? "scan_creation" : "upstream" };
|
|
268
|
+
for (const key of ["scope", "windowId"]) {
|
|
269
|
+
const value = boundedTelemetryToken(creation?.[key], 128);
|
|
270
|
+
if (value)
|
|
271
|
+
projected[key] = value;
|
|
272
|
+
}
|
|
273
|
+
for (const key of ["retryAfterSeconds", "limit", "used", "windowSeconds"]) {
|
|
274
|
+
const value = key === "retryAfterSeconds" ? error[key] : creation?.[key];
|
|
275
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 1_000_000)
|
|
276
|
+
projected[key] = value;
|
|
277
|
+
}
|
|
278
|
+
return projected;
|
|
279
|
+
}
|
|
215
280
|
function observeToolInvocation(observer, observation, requestContext) {
|
|
216
281
|
if (!observer)
|
|
217
282
|
return;
|
|
@@ -247,6 +312,7 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
247
312
|
server.createToolError = (message) => message.includes("Input validation error:")
|
|
248
313
|
? toInvalidArgumentsToolError(message)
|
|
249
314
|
: sdkCreateToolError(message);
|
|
315
|
+
const registeredToolNames = new Set();
|
|
250
316
|
const lightTools = new Set(["certscore_scan_site", "certscore_get_scan_status", "certscore_get_scan_bundle"]);
|
|
251
317
|
const scanIdTools = new Set([
|
|
252
318
|
"certscore_explain_finding",
|
|
@@ -259,44 +325,105 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
259
325
|
"certscore_get_scan_status",
|
|
260
326
|
"certscore_list_findings",
|
|
261
327
|
]);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
328
|
+
// Intercept SDK registration through its public API so lookup/validation errors
|
|
329
|
+
// and original arguments are observed once, before SDK normalization. Restore
|
|
330
|
+
// the registration method after constructing this server; no private handler map.
|
|
331
|
+
const registerRequest = server.server.setRequestHandler.bind(server.server);
|
|
332
|
+
server.server.setRequestHandler = ((schema, handler) => {
|
|
333
|
+
if (schema !== CallToolRequestSchema)
|
|
334
|
+
return registerRequest(schema, handler);
|
|
335
|
+
return registerRequest(CallToolRequestSchema, async (request, extra) => {
|
|
269
336
|
const startedAt = Date.now();
|
|
337
|
+
const requestId = randomUUID();
|
|
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
|
+
}
|
|
343
|
+
const args = request.params.arguments ?? {};
|
|
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);
|
|
355
|
+
let result;
|
|
356
|
+
let protocolFailure;
|
|
270
357
|
try {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
+
});
|
|
283
377
|
return result;
|
|
284
378
|
}
|
|
285
379
|
catch (error) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
args: input,
|
|
289
|
-
durationMs: Date.now() - startedAt,
|
|
290
|
-
result: { isError: true, structuredContent: { error: { code: "handler_exception" } } },
|
|
291
|
-
toolName: name,
|
|
292
|
-
}),
|
|
293
|
-
errorCode: "handler_exception",
|
|
294
|
-
outcome: "error",
|
|
295
|
-
transportOutcome: "mcp_error",
|
|
296
|
-
}, { headers: extra.requestInfo?.headers ?? null });
|
|
380
|
+
protocolFailure = error;
|
|
381
|
+
result ??= { isError: true, structuredContent: { error: { code: "handler_exception" } } };
|
|
297
382
|
throw error;
|
|
298
383
|
}
|
|
384
|
+
finally {
|
|
385
|
+
try {
|
|
386
|
+
const observation = projectMcpToolInvocationObservation({ args, durationMs: Date.now() - startedAt, result, toolName: name });
|
|
387
|
+
if (!known)
|
|
388
|
+
observation.errorCode = "unknown_tool";
|
|
389
|
+
else if (observation.outcome === "error" && !observation.errorCode)
|
|
390
|
+
observation.errorCode = "protocol_error";
|
|
391
|
+
const payload = telemetryResultRecord(result);
|
|
392
|
+
const metadata = payload.mcpMetadata;
|
|
393
|
+
observeToolInvocation(options.onToolInvocation, {
|
|
394
|
+
...observation,
|
|
395
|
+
requestId,
|
|
396
|
+
timing: { startedAt: new Date(startedAt).toISOString(), responseGeneratedAt: new Date().toISOString() },
|
|
397
|
+
captureBasis: "protocol_request",
|
|
398
|
+
callerInput: captureMcpCallerInput(args, request.params._meta),
|
|
399
|
+
...(taskContext ? { taskContext } : {}),
|
|
400
|
+
response: {
|
|
401
|
+
summary: captureMcpResponse(result, protocolFailure),
|
|
402
|
+
bytes: protocolFailure ? null : Math.min(10_000_000, Buffer.byteLength(JSON.stringify(result ?? null))),
|
|
403
|
+
truncated: typeof metadata?.truncated === "boolean" ? metadata.truncated : null,
|
|
404
|
+
...(typeof metadata?.effectiveMaxBytes === "number" ? { effectiveMaxBytes: metadata.effectiveMaxBytes } : {}),
|
|
405
|
+
},
|
|
406
|
+
}, { headers: extra.requestInfo?.headers ?? null });
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
// Best-effort telemetry must never replace a tool response or its error.
|
|
410
|
+
console.error("[certscore-mcp] telemetry projection failed");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
299
413
|
});
|
|
414
|
+
});
|
|
415
|
+
const registerMcpTool = server.registerTool.bind(server);
|
|
416
|
+
const registerTool = (name, contract, handler) => {
|
|
417
|
+
if (options.toolProfile === "light" && !lightTools.has(name))
|
|
418
|
+
return;
|
|
419
|
+
const typedHandler = handler;
|
|
420
|
+
registerMcpTool(name, contract, async (input, extra) => {
|
|
421
|
+
const scanId = input && typeof input === "object" && !Array.isArray(input)
|
|
422
|
+
? input.scanId : null;
|
|
423
|
+
return scanIdTools.has(name) && !isCanonicalScanId(scanId)
|
|
424
|
+
? toInvalidScanIdToolError() : typedHandler(input, extra);
|
|
425
|
+
});
|
|
426
|
+
registeredToolNames.add(name);
|
|
300
427
|
};
|
|
301
428
|
registerTool("certscore_scan_site", toolContract("certscore_scan_site"), async (input, extra) => {
|
|
302
429
|
const toolStartedAtMs = Date.now();
|
|
@@ -304,11 +431,13 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
304
431
|
const client = clientForRequest(extra);
|
|
305
432
|
const demoSubstitution = exampleDomainDemoSubstitution(input.url, options.exampleDomainDemoUrl);
|
|
306
433
|
const effectiveUrl = demoSubstitution?.effectiveUrl ?? input.url;
|
|
434
|
+
let creationCompleted = false;
|
|
307
435
|
try {
|
|
308
436
|
const created = await client.scans.create(effectiveUrl, {
|
|
309
437
|
freshness: input.freshness ?? "latest",
|
|
310
438
|
scanFrom: input.scanFrom
|
|
311
439
|
});
|
|
440
|
+
creationCompleted = true;
|
|
312
441
|
console.log(JSON.stringify({
|
|
313
442
|
event: "mcp.certscore_scan_site.creation_completed",
|
|
314
443
|
durationMs: Date.now() - creationStartedAtMs,
|
|
@@ -387,7 +516,7 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
387
516
|
durationMs: Date.now() - creationStartedAtMs,
|
|
388
517
|
errorName: error instanceof Error ? error.name : "UnknownError",
|
|
389
518
|
}));
|
|
390
|
-
return toToolError(error);
|
|
519
|
+
return toToolError(error, { scanCreation: !creationCompleted });
|
|
391
520
|
}
|
|
392
521
|
});
|
|
393
522
|
registerTool("certscore_get_scan", toolContract("certscore_get_scan"), async ({ scanId }) => {
|
|
@@ -560,5 +689,6 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
560
689
|
return toToolError(error);
|
|
561
690
|
}
|
|
562
691
|
});
|
|
692
|
+
server.server.setRequestHandler = registerRequest;
|
|
563
693
|
return server;
|
|
564
694
|
}
|
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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"}
|