@certscore/mcp 0.2.7 → 0.2.12
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 +43 -11
- package/dist/certscore-mcp.mjs +290 -19
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -4
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +105 -7
- package/dist/tools.d.ts +64 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +67 -0
- package/package.json +4 -3
- package/server-light.json +19 -0
- package/server.json +10 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
CertScore MCP exposes a focused Model Context Protocol server for CertScore Pulse workflows.
|
|
4
4
|
|
|
5
|
-
Status: public developer preview. Version 0.2.
|
|
5
|
+
Status: public developer preview. Version 0.2.12 adds the focused hosted CertScore Light workflow and current remote registry metadata. Local WC01 development uses `pnpm mcp:certscore`.
|
|
6
6
|
|
|
7
7
|
Public docs:
|
|
8
8
|
|
|
@@ -14,11 +14,12 @@ Public docs:
|
|
|
14
14
|
## Tools
|
|
15
15
|
|
|
16
16
|
- `create_scan` - Deprecated compatibility alias of scan_site. Use scan_site for new integrations. Returns completed-limited no-go disposition and reason-specific guidance when applicable.
|
|
17
|
-
- `scan_site` -
|
|
17
|
+
- `scan_site` - Recommended first call. Starts or reuses a public-web scan, reports freshness and anonymous quota decisions, and waits up to 45 seconds by default. If still running, use get_scan_status; otherwise use get_scan_bundle. Completed no-go scans retain completed_limited status and reason-specific guidance.
|
|
18
18
|
- `get_scan` - Retrieve the API v2 public-safe scan resource, including completed-limited no-go disposition, reason-specific guidance, and timing when available.
|
|
19
19
|
- `get_scan_status` - Retrieve terminal status, including completed_limited no-go disposition and reason-specific guidance. Pass jobId only before a stable scanId is available.
|
|
20
20
|
- `get_report` - Retrieve a summary Pulse report, including customer-safe no-go messaging when coverage is completed-limited. Use get_evidence for the larger bounded packet.
|
|
21
21
|
- `get_evidence` - Retrieve the bounded structured Evidence JSON packet for a stable scan ID. Excludes raw cookie values, raw bodies, sensitive payloads, full DOM, and unredacted query values.
|
|
22
|
+
- `get_scan_bundle` - Recommended second call after scan_site. Returns the canonical scan state, compact report summary, findings, bounded evidence summary, and pre-consent inventory in one agent-friendly response.
|
|
22
23
|
- `export_findings` - Return structured findings plus completed-limited no-go disposition and guidance for downstream review or ticketing workflows.
|
|
23
24
|
- `list_findings` - List API v2 public-safe findings already projected for a scan.
|
|
24
25
|
- `get_pre_consent_cookies_trackers` - Retrieve the public-safe Cookies & Trackers (Pre-consent) report table as compact JSON for a scan.
|
|
@@ -59,6 +60,22 @@ https://certscore.ai/.well-known/oauth-authorization-server
|
|
|
59
60
|
|
|
60
61
|
The hosted service uses OAuth authorization code with PKCE. Default read access requests `scan:read mcp`; support-gated scan creation additionally requests `scan:create`. The same tool implementation and output contracts power stdio and hosted transports.
|
|
61
62
|
|
|
63
|
+
For low-volume agent discovery without account or OAuth setup, use the unauthenticated endpoint:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
https://mcp.certscore.ai/mcp/anonymous
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For the simplest no-account workflow, use CertScore Light:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
https://mcp.certscore.ai/mcp/light
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Light exposes only `scan_site`, `get_scan_status`, and `get_scan_bundle`. The allowance is 20 new scans per requester IP per UTC day.
|
|
76
|
+
Eligible recent-result reuse does not consume the allowance. Every no-account response includes the higher-volume contact path at
|
|
77
|
+
`support@certscore.ai`.
|
|
78
|
+
|
|
62
79
|
## Configuration
|
|
63
80
|
|
|
64
81
|
Install with Homebrew on macOS:
|
|
@@ -124,9 +141,22 @@ Stdio API keys use `pulse:read` and `mcp`; creating scans additionally requires
|
|
|
124
141
|
certscore-mcp --version
|
|
125
142
|
certscore-mcp --help
|
|
126
143
|
CERTSCORE_API_KEY=... certscore-mcp doctor
|
|
144
|
+
CERTSCORE_API_KEY=... certscore-mcp doctor --check-auth
|
|
127
145
|
```
|
|
128
146
|
|
|
129
|
-
The doctor command checks binary startup, version output, Node.js runtime compatibility, the configured CertScore base URL, API v2 health, and API key presence.
|
|
147
|
+
The doctor command checks binary startup, version output, Node.js runtime compatibility, the configured CertScore base URL, API v2 health, and API key presence. Add `--check-auth` to validate the credential against `/api/v2/auth/check` without creating a scan. It does not print secrets or inspect raw scanner artifacts.
|
|
148
|
+
|
|
149
|
+
## No-account agent scan path
|
|
150
|
+
|
|
151
|
+
Agents that cannot create an account or configure OAuth can use the public API v2 scan path without an `Authorization` header:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
curl -X POST https://certscore.ai/api/v2/scans \
|
|
155
|
+
-H "Content-Type: application/json" \
|
|
156
|
+
-d '{"url":"https://example.com","freshness":"latest","scanFrom":"eu_ie"}'
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
New anonymous scans are limited to 20 per requester IP per UTC day. Reusing an eligible recent result does not consume the quota. Poll the returned status resource, then retrieve findings or evidence. Contact `support@certscore.ai` for a higher-volume allowance, including when reuse serves the current request.
|
|
130
160
|
|
|
131
161
|
## MCP Client Examples
|
|
132
162
|
|
|
@@ -198,14 +228,16 @@ Local repo config for contributors:
|
|
|
198
228
|
|
|
199
229
|
## Agent Workflow
|
|
200
230
|
|
|
201
|
-
1. Call `scan_site` with a public URL.
|
|
202
|
-
2.
|
|
203
|
-
3. Call `
|
|
204
|
-
4. Call `list_findings`
|
|
205
|
-
5. Call `
|
|
206
|
-
6. Call `
|
|
207
|
-
|
|
208
|
-
|
|
231
|
+
1. Call `scan_site` with a public URL. It normally returns the completed scan resource in the same tool call.
|
|
232
|
+
2. Only if it returns a non-terminal job, call `get_scan_status` using the stable `scanId` until completion.
|
|
233
|
+
3. Call `get_scan_bundle` for the normal compact review handoff.
|
|
234
|
+
4. Call `get_report`, `get_evidence`, `list_findings`, or `get_pre_consent_cookies_trackers` only when the task needs a dedicated view.
|
|
235
|
+
5. Call `explain_finding` when a reviewer needs evidence and caveats for a specific finding.
|
|
236
|
+
6. Call `get_latest_domain_scan` or `get_latest_domain_pre_consent_cookies_trackers` when the user asks for latest eligible public data for a domain.
|
|
237
|
+
|
|
238
|
+
`scan_site` reports whether the result was reused, why the freshness decision was made, whether anonymous quota was consumed, the remaining daily allowance, the UTC reset time, and the recommended next tool.
|
|
239
|
+
|
|
240
|
+
With `freshness: "latest"`, CertScore reuses an eligible scan completed within the last 24 hours for the same normalized target and scan region. A reusable result must have completed usable page coverage and must not be an early-loss, no-page, or otherwise non-reusable limited result. Reuse does not consume anonymous quota. `freshnessDecision` states whether a recent result was reused or a new scan was queued; `reusedScanAgeSeconds` reports the reused result's age.
|
|
209
241
|
|
|
210
242
|
```json
|
|
211
243
|
{
|
package/dist/certscore-mcp.mjs
CHANGED
|
@@ -15386,6 +15386,9 @@ var StdioServerTransport = class {
|
|
|
15386
15386
|
import { realpathSync } from "node:fs";
|
|
15387
15387
|
import { fileURLToPath } from "node:url";
|
|
15388
15388
|
|
|
15389
|
+
// ../certscore-sdk/dist/client.js
|
|
15390
|
+
import { createHmac } from "node:crypto";
|
|
15391
|
+
|
|
15389
15392
|
// ../certscore-sdk/dist/errors.js
|
|
15390
15393
|
var CertScoreError = class extends Error {
|
|
15391
15394
|
status;
|
|
@@ -15587,6 +15590,9 @@ function isStatus(body) {
|
|
|
15587
15590
|
var CertScoreClient = class {
|
|
15588
15591
|
apiKey;
|
|
15589
15592
|
baseUrl;
|
|
15593
|
+
clientName;
|
|
15594
|
+
forwardedClientIp;
|
|
15595
|
+
anonymousRequesterSecret;
|
|
15590
15596
|
timeout;
|
|
15591
15597
|
scans;
|
|
15592
15598
|
findings;
|
|
@@ -15596,6 +15602,9 @@ var CertScoreClient = class {
|
|
|
15596
15602
|
constructor(options = {}) {
|
|
15597
15603
|
this.apiKey = options.apiKey;
|
|
15598
15604
|
this.baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
15605
|
+
this.clientName = options.clientName ?? "sdk";
|
|
15606
|
+
this.forwardedClientIp = options.forwardedClientIp?.trim() || void 0;
|
|
15607
|
+
this.anonymousRequesterSecret = options.anonymousRequesterSecret?.trim() || void 0;
|
|
15599
15608
|
this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
15600
15609
|
this.scans = {
|
|
15601
15610
|
create: (url2, scanOptions) => this.createScanResource(url2, scanOptions),
|
|
@@ -16002,7 +16011,8 @@ var CertScoreClient = class {
|
|
|
16002
16011
|
}
|
|
16003
16012
|
headers(jsonBody = false) {
|
|
16004
16013
|
const headers = {
|
|
16005
|
-
Accept: "application/json, text/markdown;q=0.9"
|
|
16014
|
+
Accept: "application/json, text/markdown;q=0.9",
|
|
16015
|
+
"X-CertScore-Client": this.clientName
|
|
16006
16016
|
};
|
|
16007
16017
|
if (jsonBody) {
|
|
16008
16018
|
headers["Content-Type"] = "application/json; charset=utf-8";
|
|
@@ -16010,6 +16020,17 @@ var CertScoreClient = class {
|
|
|
16010
16020
|
if (this.apiKey) {
|
|
16011
16021
|
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
16012
16022
|
}
|
|
16023
|
+
if (this.forwardedClientIp && !this.apiKey) {
|
|
16024
|
+
headers["X-Forwarded-For"] = this.forwardedClientIp;
|
|
16025
|
+
if (this.anonymousRequesterSecret) {
|
|
16026
|
+
const timestamp = String(Math.floor(Date.now() / 1e3));
|
|
16027
|
+
const message = `${timestamp}.${this.forwardedClientIp}`;
|
|
16028
|
+
const proof = createHmac("sha256", this.anonymousRequesterSecret).update(message).digest("base64url");
|
|
16029
|
+
headers["X-CertScore-Anonymous-Requester-IP"] = this.forwardedClientIp;
|
|
16030
|
+
headers["X-CertScore-Anonymous-Requester-Timestamp"] = timestamp;
|
|
16031
|
+
headers["X-CertScore-Anonymous-Requester-Proof"] = proof;
|
|
16032
|
+
}
|
|
16033
|
+
}
|
|
16013
16034
|
return headers;
|
|
16014
16035
|
}
|
|
16015
16036
|
url(path) {
|
|
@@ -20253,6 +20274,14 @@ var pulseStatusSchema = external_exports2.object({
|
|
|
20253
20274
|
resultUrl: external_exports2.string().nullable().optional(),
|
|
20254
20275
|
reportUrl: external_exports2.string().nullable().optional(),
|
|
20255
20276
|
retryAfterSeconds: external_exports2.number().int().nullable().optional(),
|
|
20277
|
+
recovery: external_exports2.object({
|
|
20278
|
+
alternateRegionAttempted: external_exports2.literal(true),
|
|
20279
|
+
fallbackScanFrom: external_exports2.string(),
|
|
20280
|
+
noGoReason: external_exports2.string(),
|
|
20281
|
+
primaryScanFrom: external_exports2.string(),
|
|
20282
|
+
primaryScanId: external_exports2.string(),
|
|
20283
|
+
claimedAt: external_exports2.string()
|
|
20284
|
+
}).passthrough().optional(),
|
|
20256
20285
|
capabilities: pulseCapabilitiesSchema.optional(),
|
|
20257
20286
|
agentInterpretation: pulseAgentInterpretationSchema.optional(),
|
|
20258
20287
|
disclaimer: external_exports2.string().optional()
|
|
@@ -20295,6 +20324,19 @@ var apiV2PreConsentInventoryKindSchema = external_exports2.enum(["cookie", "trac
|
|
|
20295
20324
|
var apiV2PreConsentInventoryPhaseSchema = external_exports2.literal("pre_consent");
|
|
20296
20325
|
var apiV2PreConsentInventoryPrioritySchema = external_exports2.enum(["high", "medium", "review_needed", "contextual", "unknown"]);
|
|
20297
20326
|
var apiV2PreConsentInventoryConfidenceSchema = external_exports2.enum(["high", "medium", "low", "unknown"]);
|
|
20327
|
+
var apiV2ScanCreationMetadataShape = {
|
|
20328
|
+
executionMode: external_exports2.enum(["new_scan", "reused_scan"]).optional(),
|
|
20329
|
+
reused: external_exports2.boolean().optional(),
|
|
20330
|
+
reusedScanAgeSeconds: external_exports2.number().int().min(0).nullable().optional(),
|
|
20331
|
+
freshnessDecision: external_exports2.string().optional(),
|
|
20332
|
+
quotaConsumed: external_exports2.boolean().optional(),
|
|
20333
|
+
anonymousQuotaLimit: external_exports2.number().int().min(0).nullable().optional(),
|
|
20334
|
+
anonymousQuotaRemaining: external_exports2.number().int().min(0).nullable().optional(),
|
|
20335
|
+
anonymousQuotaResetAt: external_exports2.string().nullable().optional(),
|
|
20336
|
+
upgradeSupportEmail: external_exports2.string().email().nullable().optional(),
|
|
20337
|
+
upgradeMessage: external_exports2.string().nullable().optional(),
|
|
20338
|
+
recommendedNextTool: external_exports2.enum(["get_scan_status", "get_scan_bundle"]).optional()
|
|
20339
|
+
};
|
|
20298
20340
|
var apiV2LinksSchema = external_exports2.object({
|
|
20299
20341
|
self: external_exports2.string().optional(),
|
|
20300
20342
|
status: external_exports2.string().optional(),
|
|
@@ -20312,7 +20354,8 @@ var apiV2ErrorSchema = external_exports2.object({
|
|
|
20312
20354
|
retryAfterSeconds: external_exports2.number().int().nullable().optional()
|
|
20313
20355
|
}).passthrough(),
|
|
20314
20356
|
links: apiV2LinksSchema.optional(),
|
|
20315
|
-
disclaimer: external_exports2.string().optional()
|
|
20357
|
+
disclaimer: external_exports2.string().optional(),
|
|
20358
|
+
...apiV2ScanCreationMetadataShape
|
|
20316
20359
|
}).passthrough();
|
|
20317
20360
|
var apiV2CreateScanRequestSchema = external_exports2.object({
|
|
20318
20361
|
url: external_exports2.string().min(1),
|
|
@@ -20337,7 +20380,8 @@ var apiV2ScanJobSchema = external_exports2.object({
|
|
|
20337
20380
|
lastUpdatedAt: external_exports2.string().optional(),
|
|
20338
20381
|
retryAfterSeconds: external_exports2.number().int().nullable().optional(),
|
|
20339
20382
|
links: apiV2LinksSchema.optional(),
|
|
20340
|
-
disclaimer: external_exports2.string().optional()
|
|
20383
|
+
disclaimer: external_exports2.string().optional(),
|
|
20384
|
+
...apiV2ScanCreationMetadataShape
|
|
20341
20385
|
}).passthrough();
|
|
20342
20386
|
var apiV2ScanResourceSchema = external_exports2.object({
|
|
20343
20387
|
type: external_exports2.literal("certscore_scan"),
|
|
@@ -20526,6 +20570,13 @@ var mcpCreateScanInputSchema = {
|
|
|
20526
20570
|
freshness: mcpPulseFreshnessSchema.optional().describe("Use latest to reuse recent scans or refresh to request a new scan when eligible."),
|
|
20527
20571
|
scanFrom: mcpScanFromSchema.optional().describe("Optional scan execution context for newly queued scans.")
|
|
20528
20572
|
};
|
|
20573
|
+
var mcpScanSiteInputSchema = {
|
|
20574
|
+
url: mcpCreateScanInputSchema.url,
|
|
20575
|
+
freshness: mcpCreateScanInputSchema.freshness,
|
|
20576
|
+
scanFrom: mcpCreateScanInputSchema.scanFrom,
|
|
20577
|
+
waitForCompletion: external_exports2.boolean().optional().describe("Wait for a completed scan resource in this tool call. Defaults to true. Set false only for an explicitly asynchronous workflow."),
|
|
20578
|
+
maxWaitSeconds: external_exports2.number().int().min(1).max(45).optional().describe("Maximum time to wait before returning the still-running job. Defaults to 45 seconds; never turns an active scan into an error.")
|
|
20579
|
+
};
|
|
20529
20580
|
var mcpGetScanStatusInputSchema = {
|
|
20530
20581
|
jobId: external_exports2.string().min(1).optional().describe("Pulse job ID for a just-created scan that has not yet returned a scanId."),
|
|
20531
20582
|
scanId: external_exports2.string().min(1).optional().describe("Preferred stable CertScore scan ID for API v2 scan status.")
|
|
@@ -20541,6 +20592,11 @@ var mcpGetReportInputSchema = {
|
|
|
20541
20592
|
var mcpGetEvidenceInputSchema = {
|
|
20542
20593
|
scanId: external_exports2.string().min(1).describe("Stable CertScore scan ID.")
|
|
20543
20594
|
};
|
|
20595
|
+
var mcpGetScanBundleInputSchema = {
|
|
20596
|
+
scanId: external_exports2.string().min(1).describe("Stable CertScore scan ID."),
|
|
20597
|
+
maxFindings: external_exports2.number().int().min(1).max(50).optional().describe("Maximum compact findings to return. Defaults to 20."),
|
|
20598
|
+
maxPreConsentRows: external_exports2.number().int().min(1).max(50).optional().describe("Maximum pre-consent inventory rows to return. Defaults to 20.")
|
|
20599
|
+
};
|
|
20544
20600
|
var mcpExportFindingsInputSchema = {
|
|
20545
20601
|
scanId: external_exports2.string().min(1).describe("Stable CertScore scan ID.")
|
|
20546
20602
|
};
|
|
@@ -20634,6 +20690,21 @@ var mcpReportOutputSchema = external_exports2.object({
|
|
|
20634
20690
|
noGo: scanNoGoResultSchema.optional(),
|
|
20635
20691
|
value: external_exports2.string().optional()
|
|
20636
20692
|
}).passthrough();
|
|
20693
|
+
var mcpScanBundleOutputSchema = external_exports2.object({
|
|
20694
|
+
type: external_exports2.literal("certscore_scan_bundle"),
|
|
20695
|
+
scanId: external_exports2.string(),
|
|
20696
|
+
status: apiV2ScanStatusSchema,
|
|
20697
|
+
resultDisposition: scanResultDispositionSchema.nullable().optional(),
|
|
20698
|
+
noGo: scanNoGoResultSchema.nullable().optional(),
|
|
20699
|
+
scan: apiV2ScanResourceSchema,
|
|
20700
|
+
summary: external_exports2.unknown().nullable(),
|
|
20701
|
+
findings: external_exports2.array(external_exports2.unknown()),
|
|
20702
|
+
evidenceSummary: external_exports2.unknown().nullable(),
|
|
20703
|
+
preConsentCookiesTrackers: external_exports2.unknown().nullable(),
|
|
20704
|
+
links: external_exports2.record(external_exports2.string()).optional(),
|
|
20705
|
+
recommendedNextTool: external_exports2.string().nullable(),
|
|
20706
|
+
disclaimer: external_exports2.string().nullable()
|
|
20707
|
+
}).passthrough();
|
|
20637
20708
|
var mcpFindingsExportOutputSchema = external_exports2.object({
|
|
20638
20709
|
type: external_exports2.literal("certscore_mcp_findings_export"),
|
|
20639
20710
|
scanId: external_exports2.string().nullable(),
|
|
@@ -20676,7 +20747,7 @@ var mcpPreConsentCookiesTrackersOutputSchema = apiV2PreConsentCookiesTrackersSch
|
|
|
20676
20747
|
var certScoreMcpToolContracts = [
|
|
20677
20748
|
{
|
|
20678
20749
|
name: "create_scan",
|
|
20679
|
-
title: "Create CertScore Pulse scan",
|
|
20750
|
+
title: "Deprecated \u2014 Create CertScore Pulse scan",
|
|
20680
20751
|
description: "Deprecated compatibility alias of scan_site. Use scan_site for new integrations. Returns completed-limited no-go disposition and reason-specific guidance when applicable.",
|
|
20681
20752
|
inputSchema: mcpCreateScanInputSchema,
|
|
20682
20753
|
outputSchema: mcpCreateScanOutputSchema,
|
|
@@ -20685,8 +20756,8 @@ var certScoreMcpToolContracts = [
|
|
|
20685
20756
|
{
|
|
20686
20757
|
name: "scan_site",
|
|
20687
20758
|
title: "Scan site",
|
|
20688
|
-
description: "
|
|
20689
|
-
inputSchema:
|
|
20759
|
+
description: "Recommended first call. Starts or reuses a public-web scan, reports freshness and anonymous quota decisions, and waits up to 45 seconds by default. If still running, use get_scan_status; otherwise use get_scan_bundle. Completed no-go scans retain completed_limited status and reason-specific guidance.",
|
|
20760
|
+
inputSchema: mcpScanSiteInputSchema,
|
|
20690
20761
|
outputSchema: mcpScanSiteOutputSchema,
|
|
20691
20762
|
annotations: scanCreationAnnotations
|
|
20692
20763
|
},
|
|
@@ -20722,6 +20793,14 @@ var certScoreMcpToolContracts = [
|
|
|
20722
20793
|
outputSchema: pulseResponseSchema,
|
|
20723
20794
|
annotations: readOnlyOpenWorldAnnotations
|
|
20724
20795
|
},
|
|
20796
|
+
{
|
|
20797
|
+
name: "get_scan_bundle",
|
|
20798
|
+
title: "Get compact CertScore scan bundle",
|
|
20799
|
+
description: "Recommended second call after scan_site. Returns the canonical scan state, compact report summary, findings, bounded evidence summary, and pre-consent inventory in one agent-friendly response.",
|
|
20800
|
+
inputSchema: mcpGetScanBundleInputSchema,
|
|
20801
|
+
outputSchema: mcpScanBundleOutputSchema,
|
|
20802
|
+
annotations: readOnlyOpenWorldAnnotations
|
|
20803
|
+
},
|
|
20725
20804
|
{
|
|
20726
20805
|
name: "export_findings",
|
|
20727
20806
|
title: "Export CertScore findings",
|
|
@@ -32934,6 +33013,71 @@ function limitPreConsentRows(payload, options = {}) {
|
|
|
32934
33013
|
}
|
|
32935
33014
|
};
|
|
32936
33015
|
}
|
|
33016
|
+
function buildScanBundle(input) {
|
|
33017
|
+
const maxFindings = Math.min(50, Math.max(1, input.maxFindings ?? 20));
|
|
33018
|
+
const maxPreConsentRows = Math.min(50, Math.max(1, input.maxPreConsentRows ?? 20));
|
|
33019
|
+
const evidence = input.evidence;
|
|
33020
|
+
const report = input.report;
|
|
33021
|
+
const findings = Array.isArray(input.findings.findings) ? input.findings.findings.slice(0, maxFindings) : [];
|
|
33022
|
+
const preConsentRows = Array.isArray(input.preConsentCookiesTrackers.rows) ? input.preConsentCookiesTrackers.rows.slice(0, maxPreConsentRows) : [];
|
|
33023
|
+
const links = {
|
|
33024
|
+
...input.scan.links ?? {},
|
|
33025
|
+
...report.links && typeof report.links === "object" && !Array.isArray(report.links) ? report.links : {}
|
|
33026
|
+
};
|
|
33027
|
+
return {
|
|
33028
|
+
type: "certscore_scan_bundle",
|
|
33029
|
+
scanId: input.scan.scanId,
|
|
33030
|
+
status: input.scan.status,
|
|
33031
|
+
resultDisposition: input.scan.resultDisposition ?? null,
|
|
33032
|
+
noGo: input.scan.noGo ?? null,
|
|
33033
|
+
scan: input.scan,
|
|
33034
|
+
summary: {
|
|
33035
|
+
summary: report.summary ?? null,
|
|
33036
|
+
executiveSummary: report.executiveSummary ?? null,
|
|
33037
|
+
counts: report.counts ?? null,
|
|
33038
|
+
coverage: report.coverage ?? input.scan.coverage ?? null,
|
|
33039
|
+
agentInterpretation: report.agentInterpretation ?? null
|
|
33040
|
+
},
|
|
33041
|
+
findings,
|
|
33042
|
+
findingsMetadata: {
|
|
33043
|
+
shown: findings.length,
|
|
33044
|
+
total: Array.isArray(input.findings.findings) ? input.findings.findings.length : 0,
|
|
33045
|
+
truncated: Array.isArray(input.findings.findings) && input.findings.findings.length > findings.length
|
|
33046
|
+
},
|
|
33047
|
+
evidenceSummary: {
|
|
33048
|
+
evidenceSafetyNotes: evidence.evidenceSafetyNotes ?? null,
|
|
33049
|
+
projectionDiagnostics: evidence.projectionDiagnostics ?? null,
|
|
33050
|
+
projectedFindings: compactEvidenceValue(evidence.projectedFindings ?? [], {
|
|
33051
|
+
arrayItems: maxFindings,
|
|
33052
|
+
depth: 5,
|
|
33053
|
+
objectKeys: 40,
|
|
33054
|
+
stringChars: 1e3
|
|
33055
|
+
}),
|
|
33056
|
+
coverageDiagnostics: compactEvidenceValue(evidence.coverageDiagnostics ?? null, {
|
|
33057
|
+
arrayItems: 20,
|
|
33058
|
+
depth: 5,
|
|
33059
|
+
objectKeys: 40,
|
|
33060
|
+
stringChars: 1e3
|
|
33061
|
+
}),
|
|
33062
|
+
policySurfaceCoverage: compactEvidenceValue(evidence.policySurfaceCoverage ?? null, {
|
|
33063
|
+
arrayItems: 20,
|
|
33064
|
+
depth: 5,
|
|
33065
|
+
objectKeys: 40,
|
|
33066
|
+
stringChars: 1e3
|
|
33067
|
+
})
|
|
33068
|
+
},
|
|
33069
|
+
preConsentCookiesTrackers: {
|
|
33070
|
+
summary: input.preConsentCookiesTrackers.summary,
|
|
33071
|
+
rows: preConsentRows,
|
|
33072
|
+
shown: preConsentRows.length,
|
|
33073
|
+
total: Array.isArray(input.preConsentCookiesTrackers.rows) ? input.preConsentCookiesTrackers.rows.length : 0,
|
|
33074
|
+
truncated: Array.isArray(input.preConsentCookiesTrackers.rows) && input.preConsentCookiesTrackers.rows.length > preConsentRows.length
|
|
33075
|
+
},
|
|
33076
|
+
links,
|
|
33077
|
+
recommendedNextTool: findings.length > 0 ? "explain_finding" : null,
|
|
33078
|
+
disclaimer: input.scan.disclaimer ?? input.report.disclaimer ?? null
|
|
33079
|
+
};
|
|
33080
|
+
}
|
|
32937
33081
|
function explainFinding(report, findingId) {
|
|
32938
33082
|
const finding = findingsFromReport(report).find((candidate) => candidate.id === findingId);
|
|
32939
33083
|
if (!finding) {
|
|
@@ -32971,6 +33115,21 @@ function explainFinding(report, findingId) {
|
|
|
32971
33115
|
|
|
32972
33116
|
// src/server.ts
|
|
32973
33117
|
var createScanDeprecationWarningPrinted = false;
|
|
33118
|
+
var DEFAULT_MCP_SCAN_WAIT_MS = 45e3;
|
|
33119
|
+
function scanCreationMetadata(value) {
|
|
33120
|
+
return {
|
|
33121
|
+
executionMode: value.executionMode,
|
|
33122
|
+
reused: value.reused,
|
|
33123
|
+
reusedScanAgeSeconds: value.reusedScanAgeSeconds,
|
|
33124
|
+
freshnessDecision: value.freshnessDecision,
|
|
33125
|
+
quotaConsumed: value.quotaConsumed,
|
|
33126
|
+
anonymousQuotaLimit: value.anonymousQuotaLimit,
|
|
33127
|
+
anonymousQuotaRemaining: value.anonymousQuotaRemaining,
|
|
33128
|
+
anonymousQuotaResetAt: value.anonymousQuotaResetAt,
|
|
33129
|
+
upgradeSupportEmail: value.upgradeSupportEmail,
|
|
33130
|
+
upgradeMessage: value.upgradeMessage
|
|
33131
|
+
};
|
|
33132
|
+
}
|
|
32974
33133
|
function toolContract(name) {
|
|
32975
33134
|
const contract = certScoreMcpToolContracts.find((candidate) => candidate.name === name);
|
|
32976
33135
|
if (!contract) {
|
|
@@ -32988,13 +33147,23 @@ function createCertScoreMcpServer(options = {}) {
|
|
|
32988
33147
|
const client = new CertScoreClient({
|
|
32989
33148
|
apiKey: options.apiKey,
|
|
32990
33149
|
baseUrl: options.baseUrl,
|
|
33150
|
+
clientName: "mcp",
|
|
33151
|
+
forwardedClientIp: options.forwardedClientIp,
|
|
33152
|
+
anonymousRequesterSecret: options.anonymousRequesterSecret,
|
|
32991
33153
|
timeout: options.timeout
|
|
32992
33154
|
});
|
|
32993
33155
|
const server = new McpServer({
|
|
32994
33156
|
name: "certscore",
|
|
32995
33157
|
version: CERTSCORE_MCP_VERSION
|
|
32996
33158
|
});
|
|
32997
|
-
const
|
|
33159
|
+
const lightTools = /* @__PURE__ */ new Set(["scan_site", "get_scan_status", "get_scan_bundle"]);
|
|
33160
|
+
const registerMcpTool = server.registerTool.bind(server);
|
|
33161
|
+
const registerTool = (name, contract, handler) => {
|
|
33162
|
+
if (options.toolProfile === "light" && !lightTools.has(name)) {
|
|
33163
|
+
return;
|
|
33164
|
+
}
|
|
33165
|
+
registerMcpTool(name, contract, handler);
|
|
33166
|
+
};
|
|
32998
33167
|
async function createPulseScanTool(input) {
|
|
32999
33168
|
const result = await client.submitScan(input.url, {
|
|
33000
33169
|
detail: normalizeDetail2(input.detail),
|
|
@@ -33023,7 +33192,7 @@ function createCertScoreMcpServer(options = {}) {
|
|
|
33023
33192
|
try {
|
|
33024
33193
|
if (!createScanDeprecationWarningPrinted) {
|
|
33025
33194
|
createScanDeprecationWarningPrinted = true;
|
|
33026
|
-
console.error("[certscore-mcp] create_scan is deprecated
|
|
33195
|
+
console.error("[certscore-mcp] create_scan is deprecated in the 0.2.x line. Use scan_site for new integrations.");
|
|
33027
33196
|
}
|
|
33028
33197
|
return toToolResult(await createPulseScanTool(input));
|
|
33029
33198
|
} catch (error2) {
|
|
@@ -33036,12 +33205,36 @@ function createCertScoreMcpServer(options = {}) {
|
|
|
33036
33205
|
toolContract("scan_site"),
|
|
33037
33206
|
async (input) => {
|
|
33038
33207
|
try {
|
|
33039
|
-
|
|
33040
|
-
|
|
33041
|
-
|
|
33042
|
-
|
|
33043
|
-
|
|
33044
|
-
|
|
33208
|
+
const created = await client.scans.create(input.url, {
|
|
33209
|
+
freshness: input.freshness ?? "latest",
|
|
33210
|
+
scanFrom: input.scanFrom
|
|
33211
|
+
});
|
|
33212
|
+
if (input.waitForCompletion === false || created.type === "certscore_scan") {
|
|
33213
|
+
return toToolResult(created);
|
|
33214
|
+
}
|
|
33215
|
+
try {
|
|
33216
|
+
const completed = await client.scans.wait(created, {
|
|
33217
|
+
maxWaitMs: Math.min(input.maxWaitSeconds ? input.maxWaitSeconds * 1e3 : DEFAULT_MCP_SCAN_WAIT_MS, DEFAULT_MCP_SCAN_WAIT_MS)
|
|
33218
|
+
});
|
|
33219
|
+
return toToolResult({
|
|
33220
|
+
...completed,
|
|
33221
|
+
...scanCreationMetadata(created),
|
|
33222
|
+
recommendedNextTool: "get_scan_bundle"
|
|
33223
|
+
});
|
|
33224
|
+
} catch (error2) {
|
|
33225
|
+
if (!(error2 instanceof CertScoreTimeoutError)) {
|
|
33226
|
+
throw error2;
|
|
33227
|
+
}
|
|
33228
|
+
const scanId = created.scanId ?? created.scan_id;
|
|
33229
|
+
if (scanId) {
|
|
33230
|
+
return toToolResult({
|
|
33231
|
+
...await client.scans.status(scanId),
|
|
33232
|
+
...scanCreationMetadata(created),
|
|
33233
|
+
recommendedNextTool: "get_scan_status"
|
|
33234
|
+
});
|
|
33235
|
+
}
|
|
33236
|
+
return toToolResult(created);
|
|
33237
|
+
}
|
|
33045
33238
|
} catch (error2) {
|
|
33046
33239
|
return toToolError(error2);
|
|
33047
33240
|
}
|
|
@@ -33064,7 +33257,27 @@ function createCertScoreMcpServer(options = {}) {
|
|
|
33064
33257
|
async ({ jobId, scanId }) => {
|
|
33065
33258
|
try {
|
|
33066
33259
|
if (scanId) {
|
|
33067
|
-
|
|
33260
|
+
const status2 = await client.scans.status(scanId);
|
|
33261
|
+
const needsTerminalHydration = status2.status === "completed" || status2.status === "completed_limited";
|
|
33262
|
+
if (needsTerminalHydration) {
|
|
33263
|
+
try {
|
|
33264
|
+
const scan = await client.scans.get(scanId);
|
|
33265
|
+
return toToolResult({
|
|
33266
|
+
...status2,
|
|
33267
|
+
type: "certscore_scan_job",
|
|
33268
|
+
status: scan.status,
|
|
33269
|
+
domain: scan.domain,
|
|
33270
|
+
resultDisposition: scan.resultDisposition,
|
|
33271
|
+
noGo: scan.noGo,
|
|
33272
|
+
startedAt: scan.startedAt,
|
|
33273
|
+
completedAt: scan.completedAt,
|
|
33274
|
+
scanTimeSeconds: scan.scanTimeSeconds,
|
|
33275
|
+
recommendedNextTool: "get_scan_bundle"
|
|
33276
|
+
});
|
|
33277
|
+
} catch {
|
|
33278
|
+
}
|
|
33279
|
+
}
|
|
33280
|
+
return toToolResult(status2);
|
|
33068
33281
|
}
|
|
33069
33282
|
if (!jobId) {
|
|
33070
33283
|
return toToolResult({
|
|
@@ -33114,6 +33327,32 @@ function createCertScoreMcpServer(options = {}) {
|
|
|
33114
33327
|
}
|
|
33115
33328
|
}
|
|
33116
33329
|
);
|
|
33330
|
+
registerTool(
|
|
33331
|
+
"get_scan_bundle",
|
|
33332
|
+
toolContract("get_scan_bundle"),
|
|
33333
|
+
async ({ scanId, maxFindings, maxPreConsentRows }) => {
|
|
33334
|
+
try {
|
|
33335
|
+
const [scan, report, evidence, findings, preConsentCookiesTrackers] = await Promise.all([
|
|
33336
|
+
client.scans.get(scanId),
|
|
33337
|
+
client.getScan(scanId, { detail: "summary", format: "json" }),
|
|
33338
|
+
client.getScan(scanId, { detail: "evidence", format: "json" }),
|
|
33339
|
+
client.findings.list(scanId),
|
|
33340
|
+
client.scans.preConsentCookiesTrackers(scanId)
|
|
33341
|
+
]);
|
|
33342
|
+
return toToolResult(buildScanBundle({
|
|
33343
|
+
evidence,
|
|
33344
|
+
findings,
|
|
33345
|
+
maxFindings,
|
|
33346
|
+
maxPreConsentRows,
|
|
33347
|
+
preConsentCookiesTrackers,
|
|
33348
|
+
report,
|
|
33349
|
+
scan
|
|
33350
|
+
}));
|
|
33351
|
+
} catch (error2) {
|
|
33352
|
+
return toToolError(error2);
|
|
33353
|
+
}
|
|
33354
|
+
}
|
|
33355
|
+
);
|
|
33117
33356
|
registerTool(
|
|
33118
33357
|
"export_findings",
|
|
33119
33358
|
toolContract("export_findings"),
|
|
@@ -33213,12 +33452,13 @@ async function main() {
|
|
|
33213
33452
|
"",
|
|
33214
33453
|
"Usage:",
|
|
33215
33454
|
" certscore-mcp",
|
|
33216
|
-
" certscore-mcp doctor"
|
|
33455
|
+
" certscore-mcp doctor",
|
|
33456
|
+
" certscore-mcp doctor --check-auth"
|
|
33217
33457
|
].join("\n"));
|
|
33218
33458
|
return;
|
|
33219
33459
|
}
|
|
33220
33460
|
if (process.argv.includes("doctor")) {
|
|
33221
|
-
const result = await getCertScoreMcpDoctorReport();
|
|
33461
|
+
const result = await getCertScoreMcpDoctorReport({ checkAuth: process.argv.includes("--check-auth") });
|
|
33222
33462
|
console.log(result.lines.join("\n"));
|
|
33223
33463
|
process.exitCode = result.exitCode;
|
|
33224
33464
|
return;
|
|
@@ -33274,10 +33514,41 @@ async function getCertScoreMcpDoctorReport(options = {}) {
|
|
|
33274
33514
|
}
|
|
33275
33515
|
if (env.CERTSCORE_API_KEY?.trim()) {
|
|
33276
33516
|
lines.push("[ok] CERTSCORE_API_KEY is present");
|
|
33277
|
-
|
|
33517
|
+
if (options.checkAuth) {
|
|
33518
|
+
if (!healthUrl) {
|
|
33519
|
+
lines.push("[error] Cannot check credentials until CERTSCORE_BASE_URL is valid");
|
|
33520
|
+
exitCode = 1;
|
|
33521
|
+
} else {
|
|
33522
|
+
const authUrl = new URL("/api/v2/auth/check", healthUrl.origin);
|
|
33523
|
+
try {
|
|
33524
|
+
const response = await fetchImpl(authUrl, {
|
|
33525
|
+
headers: {
|
|
33526
|
+
accept: "application/json",
|
|
33527
|
+
authorization: `Bearer ${env.CERTSCORE_API_KEY.trim()}`
|
|
33528
|
+
},
|
|
33529
|
+
signal: AbortSignal.timeout(1e4)
|
|
33530
|
+
});
|
|
33531
|
+
if (response.ok) {
|
|
33532
|
+
lines.push(`[ok] API key authenticated at ${authUrl.href}`);
|
|
33533
|
+
} else {
|
|
33534
|
+
lines.push(`[error] API key rejected with HTTP ${response.status} at ${authUrl.href}`);
|
|
33535
|
+
exitCode = 1;
|
|
33536
|
+
}
|
|
33537
|
+
} catch (error2) {
|
|
33538
|
+
const message = error2 instanceof Error && error2.message ? error2.message : "request failed";
|
|
33539
|
+
lines.push(`[error] API key check failed at ${authUrl.href}: ${message}`);
|
|
33540
|
+
exitCode = 1;
|
|
33541
|
+
}
|
|
33542
|
+
}
|
|
33543
|
+
} else {
|
|
33544
|
+
lines.push("[info] Run certscore-mcp doctor --check-auth to verify the credential without creating a scan.");
|
|
33545
|
+
}
|
|
33278
33546
|
} else {
|
|
33279
33547
|
lines.push("[warn] CERTSCORE_API_KEY is not set");
|
|
33280
|
-
lines.push("[info] Set CERTSCORE_API_KEY before connecting an MCP client or calling authenticated tools.");
|
|
33548
|
+
lines.push(options.checkAuth ? "[error] --check-auth requires CERTSCORE_API_KEY." : "[info] Set CERTSCORE_API_KEY before connecting an MCP client or calling authenticated tools.");
|
|
33549
|
+
if (options.checkAuth) {
|
|
33550
|
+
exitCode = 1;
|
|
33551
|
+
}
|
|
33281
33552
|
}
|
|
33282
33553
|
lines.push("CertScore outputs are automated public-web observations for review, not legal advice, certification, or a compliance determination.");
|
|
33283
33554
|
return { exitCode, lines };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { createCertScoreMcpServer } from "./server.js";
|
|
|
2
2
|
export { explainFinding, exportFindings, findingsFromReport } from "./tools.js";
|
|
3
3
|
export { CERTSCORE_MCP_VERSION } from "./version.js";
|
|
4
4
|
export interface CertScoreMcpDoctorOptions {
|
|
5
|
+
checkAuth?: boolean;
|
|
5
6
|
env?: Record<string, string | undefined>;
|
|
6
7
|
fetch?: typeof fetch;
|
|
7
8
|
nodeVersion?: string;
|
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,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;
|
|
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"}
|
package/dist/index.js
CHANGED
|
@@ -34,12 +34,13 @@ async function main() {
|
|
|
34
34
|
"",
|
|
35
35
|
"Usage:",
|
|
36
36
|
" certscore-mcp",
|
|
37
|
-
" certscore-mcp doctor"
|
|
37
|
+
" certscore-mcp doctor",
|
|
38
|
+
" certscore-mcp doctor --check-auth"
|
|
38
39
|
].join("\n"));
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
42
|
if (process.argv.includes("doctor")) {
|
|
42
|
-
const result = await getCertScoreMcpDoctorReport();
|
|
43
|
+
const result = await getCertScoreMcpDoctorReport({ checkAuth: process.argv.includes("--check-auth") });
|
|
43
44
|
console.log(result.lines.join("\n"));
|
|
44
45
|
process.exitCode = result.exitCode;
|
|
45
46
|
return;
|
|
@@ -99,11 +100,46 @@ export async function getCertScoreMcpDoctorReport(options = {}) {
|
|
|
99
100
|
}
|
|
100
101
|
if (env.CERTSCORE_API_KEY?.trim()) {
|
|
101
102
|
lines.push("[ok] CERTSCORE_API_KEY is present");
|
|
102
|
-
|
|
103
|
+
if (options.checkAuth) {
|
|
104
|
+
if (!healthUrl) {
|
|
105
|
+
lines.push("[error] Cannot check credentials until CERTSCORE_BASE_URL is valid");
|
|
106
|
+
exitCode = 1;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const authUrl = new URL("/api/v2/auth/check", healthUrl.origin);
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetchImpl(authUrl, {
|
|
112
|
+
headers: {
|
|
113
|
+
accept: "application/json",
|
|
114
|
+
authorization: `Bearer ${env.CERTSCORE_API_KEY.trim()}`
|
|
115
|
+
},
|
|
116
|
+
signal: AbortSignal.timeout(10_000)
|
|
117
|
+
});
|
|
118
|
+
if (response.ok) {
|
|
119
|
+
lines.push(`[ok] API key authenticated at ${authUrl.href}`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
lines.push(`[error] API key rejected with HTTP ${response.status} at ${authUrl.href}`);
|
|
123
|
+
exitCode = 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
const message = error instanceof Error && error.message ? error.message : "request failed";
|
|
128
|
+
lines.push(`[error] API key check failed at ${authUrl.href}: ${message}`);
|
|
129
|
+
exitCode = 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
lines.push("[info] Run certscore-mcp doctor --check-auth to verify the credential without creating a scan.");
|
|
135
|
+
}
|
|
103
136
|
}
|
|
104
137
|
else {
|
|
105
138
|
lines.push("[warn] CERTSCORE_API_KEY is not set");
|
|
106
|
-
lines.push("[info] Set CERTSCORE_API_KEY before connecting an MCP client or calling authenticated tools.");
|
|
139
|
+
lines.push(options.checkAuth ? "[error] --check-auth requires CERTSCORE_API_KEY." : "[info] Set CERTSCORE_API_KEY before connecting an MCP client or calling authenticated tools.");
|
|
140
|
+
if (options.checkAuth) {
|
|
141
|
+
exitCode = 1;
|
|
142
|
+
}
|
|
107
143
|
}
|
|
108
144
|
lines.push("CertScore outputs are automated public-web observations for review, not legal advice, certification, or a compliance determination.");
|
|
109
145
|
return { exitCode, lines };
|
package/dist/server.d.ts
CHANGED
|
@@ -2,7 +2,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
export interface CertScoreMcpOptions {
|
|
3
3
|
apiKey?: string;
|
|
4
4
|
baseUrl?: string;
|
|
5
|
+
forwardedClientIp?: string | null;
|
|
6
|
+
anonymousRequesterSecret?: string | null;
|
|
5
7
|
timeout?: number;
|
|
8
|
+
toolProfile?: "full" | "light";
|
|
6
9
|
}
|
|
7
10
|
export declare function createCertScoreMcpServer(options?: CertScoreMcpOptions): McpServer;
|
|
8
11
|
//# sourceMappingURL=server.d.ts.map
|
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;AAIpE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,wBAAwB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAChC;AAwDD,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,mBAAwB,aA2SzE"}
|
package/dist/server.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
import { CertScoreClient } from "@certscore/sdk";
|
|
1
|
+
import { CertScoreClient, CertScoreTimeoutError } from "@certscore/sdk";
|
|
2
2
|
import { certScoreMcpToolContracts } from "@certscore/api-contracts";
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
4
|
import { CERTSCORE_MCP_VERSION } from "./version.js";
|
|
5
|
-
import { boundEvidencePacket, exportFindings, limitPreConsentRows, normalizeDetail, normalizeFormat, paginateFindingList, scanIdFromStatus, toToolError, toToolResult } from "./tools.js";
|
|
5
|
+
import { boundEvidencePacket, buildScanBundle, exportFindings, limitPreConsentRows, normalizeDetail, normalizeFormat, paginateFindingList, scanIdFromStatus, toToolError, toToolResult } from "./tools.js";
|
|
6
6
|
let createScanDeprecationWarningPrinted = false;
|
|
7
|
+
const DEFAULT_MCP_SCAN_WAIT_MS = 45_000;
|
|
8
|
+
function scanCreationMetadata(value) {
|
|
9
|
+
return {
|
|
10
|
+
executionMode: value.executionMode,
|
|
11
|
+
reused: value.reused,
|
|
12
|
+
reusedScanAgeSeconds: value.reusedScanAgeSeconds,
|
|
13
|
+
freshnessDecision: value.freshnessDecision,
|
|
14
|
+
quotaConsumed: value.quotaConsumed,
|
|
15
|
+
anonymousQuotaLimit: value.anonymousQuotaLimit,
|
|
16
|
+
anonymousQuotaRemaining: value.anonymousQuotaRemaining,
|
|
17
|
+
anonymousQuotaResetAt: value.anonymousQuotaResetAt,
|
|
18
|
+
upgradeSupportEmail: value.upgradeSupportEmail,
|
|
19
|
+
upgradeMessage: value.upgradeMessage
|
|
20
|
+
};
|
|
21
|
+
}
|
|
7
22
|
function toolContract(name) {
|
|
8
23
|
const contract = certScoreMcpToolContracts.find((candidate) => candidate.name === name);
|
|
9
24
|
if (!contract) {
|
|
@@ -21,13 +36,23 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
21
36
|
const client = new CertScoreClient({
|
|
22
37
|
apiKey: options.apiKey,
|
|
23
38
|
baseUrl: options.baseUrl,
|
|
39
|
+
clientName: "mcp",
|
|
40
|
+
forwardedClientIp: options.forwardedClientIp,
|
|
41
|
+
anonymousRequesterSecret: options.anonymousRequesterSecret,
|
|
24
42
|
timeout: options.timeout
|
|
25
43
|
});
|
|
26
44
|
const server = new McpServer({
|
|
27
45
|
name: "certscore",
|
|
28
46
|
version: CERTSCORE_MCP_VERSION
|
|
29
47
|
});
|
|
30
|
-
const
|
|
48
|
+
const lightTools = new Set(["scan_site", "get_scan_status", "get_scan_bundle"]);
|
|
49
|
+
const registerMcpTool = server.registerTool.bind(server);
|
|
50
|
+
const registerTool = (name, contract, handler) => {
|
|
51
|
+
if (options.toolProfile === "light" && !lightTools.has(name)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
registerMcpTool(name, contract, handler);
|
|
55
|
+
};
|
|
31
56
|
async function createPulseScanTool(input) {
|
|
32
57
|
const result = await client.submitScan(input.url, {
|
|
33
58
|
detail: normalizeDetail(input.detail),
|
|
@@ -53,7 +78,7 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
53
78
|
try {
|
|
54
79
|
if (!createScanDeprecationWarningPrinted) {
|
|
55
80
|
createScanDeprecationWarningPrinted = true;
|
|
56
|
-
console.error("[certscore-mcp] create_scan is deprecated
|
|
81
|
+
console.error("[certscore-mcp] create_scan is deprecated in the 0.2.x line. Use scan_site for new integrations.");
|
|
57
82
|
}
|
|
58
83
|
return toToolResult(await createPulseScanTool(input));
|
|
59
84
|
}
|
|
@@ -63,10 +88,37 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
63
88
|
});
|
|
64
89
|
registerTool("scan_site", toolContract("scan_site"), async (input) => {
|
|
65
90
|
try {
|
|
66
|
-
|
|
91
|
+
const created = await client.scans.create(input.url, {
|
|
67
92
|
freshness: input.freshness ?? "latest",
|
|
68
93
|
scanFrom: input.scanFrom
|
|
69
|
-
})
|
|
94
|
+
});
|
|
95
|
+
if (input.waitForCompletion === false || created.type === "certscore_scan") {
|
|
96
|
+
return toToolResult(created);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const completed = await client.scans.wait(created, {
|
|
100
|
+
maxWaitMs: Math.min(input.maxWaitSeconds ? input.maxWaitSeconds * 1_000 : DEFAULT_MCP_SCAN_WAIT_MS, DEFAULT_MCP_SCAN_WAIT_MS)
|
|
101
|
+
});
|
|
102
|
+
return toToolResult({
|
|
103
|
+
...completed,
|
|
104
|
+
...scanCreationMetadata(created),
|
|
105
|
+
recommendedNextTool: "get_scan_bundle"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (!(error instanceof CertScoreTimeoutError)) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
const scanId = created.scanId ?? created.scan_id;
|
|
113
|
+
if (scanId) {
|
|
114
|
+
return toToolResult({
|
|
115
|
+
...(await client.scans.status(scanId)),
|
|
116
|
+
...scanCreationMetadata(created),
|
|
117
|
+
recommendedNextTool: "get_scan_status"
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return toToolResult(created);
|
|
121
|
+
}
|
|
70
122
|
}
|
|
71
123
|
catch (error) {
|
|
72
124
|
return toToolError(error);
|
|
@@ -83,7 +135,30 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
83
135
|
registerTool("get_scan_status", toolContract("get_scan_status"), async ({ jobId, scanId }) => {
|
|
84
136
|
try {
|
|
85
137
|
if (scanId) {
|
|
86
|
-
|
|
138
|
+
const status = await client.scans.status(scanId);
|
|
139
|
+
const needsTerminalHydration = status.status === "completed" || status.status === "completed_limited";
|
|
140
|
+
if (needsTerminalHydration) {
|
|
141
|
+
try {
|
|
142
|
+
const scan = await client.scans.get(scanId);
|
|
143
|
+
return toToolResult({
|
|
144
|
+
...status,
|
|
145
|
+
type: "certscore_scan_job",
|
|
146
|
+
status: scan.status,
|
|
147
|
+
domain: scan.domain,
|
|
148
|
+
resultDisposition: scan.resultDisposition,
|
|
149
|
+
noGo: scan.noGo,
|
|
150
|
+
startedAt: scan.startedAt,
|
|
151
|
+
completedAt: scan.completedAt,
|
|
152
|
+
scanTimeSeconds: scan.scanTimeSeconds,
|
|
153
|
+
recommendedNextTool: "get_scan_bundle"
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Preserve the API status response if the terminal scan resource is
|
|
158
|
+
// briefly unavailable during eventual-consistency windows.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return toToolResult(status);
|
|
87
162
|
}
|
|
88
163
|
if (!jobId) {
|
|
89
164
|
return toToolResult({
|
|
@@ -129,6 +204,29 @@ export function createCertScoreMcpServer(options = {}) {
|
|
|
129
204
|
return toToolError(error);
|
|
130
205
|
}
|
|
131
206
|
});
|
|
207
|
+
registerTool("get_scan_bundle", toolContract("get_scan_bundle"), async ({ scanId, maxFindings, maxPreConsentRows }) => {
|
|
208
|
+
try {
|
|
209
|
+
const [scan, report, evidence, findings, preConsentCookiesTrackers] = await Promise.all([
|
|
210
|
+
client.scans.get(scanId),
|
|
211
|
+
client.getScan(scanId, { detail: "summary", format: "json" }),
|
|
212
|
+
client.getScan(scanId, { detail: "evidence", format: "json" }),
|
|
213
|
+
client.findings.list(scanId),
|
|
214
|
+
client.scans.preConsentCookiesTrackers(scanId)
|
|
215
|
+
]);
|
|
216
|
+
return toToolResult(buildScanBundle({
|
|
217
|
+
evidence,
|
|
218
|
+
findings,
|
|
219
|
+
maxFindings,
|
|
220
|
+
maxPreConsentRows,
|
|
221
|
+
preConsentCookiesTrackers,
|
|
222
|
+
report,
|
|
223
|
+
scan
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
return toToolError(error);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
132
230
|
registerTool("export_findings", toolContract("export_findings"), async ({ scanId }) => {
|
|
133
231
|
try {
|
|
134
232
|
const report = await client.getScan(scanId, { detail: "full", format: "json" });
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
-
import { type JobStatus, type PulseDetail, type PulseFormat, type PulseResult, type TopFinding } from "@certscore/sdk";
|
|
2
|
+
import { type FindingList, type JobStatus, type PreConsentCookiesTrackers, type PulseDetail, type PulseFormat, type PulseResult, type ScanResource, type TopFinding } from "@certscore/sdk";
|
|
3
3
|
export declare const MAX_EVIDENCE_PACKET_CHARS = 250000;
|
|
4
4
|
export declare function toToolResult(payload: unknown): CallToolResult;
|
|
5
5
|
export declare function toToolError(error: unknown): CallToolResult;
|
|
@@ -37,6 +37,69 @@ export declare function paginateFindingList<T extends Record<string, unknown>>(p
|
|
|
37
37
|
export declare function limitPreConsentRows<T extends Record<string, unknown>>(payload: T, options?: {
|
|
38
38
|
maxRows?: number;
|
|
39
39
|
}): T;
|
|
40
|
+
export declare function buildScanBundle(input: {
|
|
41
|
+
evidence: PulseResult;
|
|
42
|
+
findings: FindingList;
|
|
43
|
+
maxFindings?: number;
|
|
44
|
+
maxPreConsentRows?: number;
|
|
45
|
+
preConsentCookiesTrackers: PreConsentCookiesTrackers;
|
|
46
|
+
report: PulseResult;
|
|
47
|
+
scan: ScanResource;
|
|
48
|
+
}): {
|
|
49
|
+
type: string;
|
|
50
|
+
scanId: string;
|
|
51
|
+
status: import("packages/certscore-sdk/dist/types.js").PulseJobStatus;
|
|
52
|
+
resultDisposition: "no_go" | null;
|
|
53
|
+
noGo: import("@certscore/sdk").ScanNoGoResult | null;
|
|
54
|
+
scan: ScanResource;
|
|
55
|
+
summary: {
|
|
56
|
+
summary: {} | null;
|
|
57
|
+
executiveSummary: {} | null;
|
|
58
|
+
counts: {} | null;
|
|
59
|
+
coverage: {} | null;
|
|
60
|
+
agentInterpretation: {} | null;
|
|
61
|
+
};
|
|
62
|
+
findings: import("packages/certscore-sdk/dist/types.js").FindingSummary[];
|
|
63
|
+
findingsMetadata: {
|
|
64
|
+
shown: number;
|
|
65
|
+
total: number;
|
|
66
|
+
truncated: boolean;
|
|
67
|
+
};
|
|
68
|
+
evidenceSummary: {
|
|
69
|
+
evidenceSafetyNotes: {} | null;
|
|
70
|
+
projectionDiagnostics: {} | null;
|
|
71
|
+
projectedFindings: unknown;
|
|
72
|
+
coverageDiagnostics: unknown;
|
|
73
|
+
policySurfaceCoverage: unknown;
|
|
74
|
+
};
|
|
75
|
+
preConsentCookiesTrackers: {
|
|
76
|
+
summary: {
|
|
77
|
+
[key: string]: unknown;
|
|
78
|
+
rowCount: number;
|
|
79
|
+
trackerCount: number;
|
|
80
|
+
cookieCount: number;
|
|
81
|
+
requestCount: number;
|
|
82
|
+
totalRowCount?: number;
|
|
83
|
+
truncated?: boolean;
|
|
84
|
+
};
|
|
85
|
+
rows: import("packages/certscore-sdk/dist/types.js").PreConsentCookiesTrackersRow[];
|
|
86
|
+
shown: number;
|
|
87
|
+
total: number;
|
|
88
|
+
truncated: boolean;
|
|
89
|
+
};
|
|
90
|
+
links: {
|
|
91
|
+
[key: string]: string | undefined;
|
|
92
|
+
self?: string;
|
|
93
|
+
status?: string;
|
|
94
|
+
findings?: string;
|
|
95
|
+
pulse?: string;
|
|
96
|
+
report?: string;
|
|
97
|
+
latestDomainScan?: string;
|
|
98
|
+
docs?: string;
|
|
99
|
+
};
|
|
100
|
+
recommendedNextTool: string | null;
|
|
101
|
+
disclaimer: string | null;
|
|
102
|
+
};
|
|
40
103
|
export declare function explainFinding(report: PulseResult, findingId: string): {
|
|
41
104
|
type: string;
|
|
42
105
|
scanId: string | null;
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAkB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAkB,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;AAG5M,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAKjD,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,CAa7D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CA0B1D;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,kBAAkB,SAA4B,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA0C9H;AA6ID,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,WAAW,CAE5E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,WAAW,CAE5E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,iBAEjD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,iBAGlD;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,EAAE,CAQpE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;EAsBjD;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,OAAO,EAAE,CAAC,EACV,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAChD,CAAC,CAoBH;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,OAAO,EAAE,CAAC,EACV,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACjC,CAAC,CAiBH;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,QAAQ,EAAE,WAAW,CAAC;IACtB,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,yBAAyB,EAAE,yBAAyB,CAAC;IACrD,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;CACpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmEA;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCpE"}
|
package/dist/tools.js
CHANGED
|
@@ -297,6 +297,73 @@ export function limitPreConsentRows(payload, options = {}) {
|
|
|
297
297
|
}
|
|
298
298
|
};
|
|
299
299
|
}
|
|
300
|
+
export function buildScanBundle(input) {
|
|
301
|
+
const maxFindings = Math.min(50, Math.max(1, input.maxFindings ?? 20));
|
|
302
|
+
const maxPreConsentRows = Math.min(50, Math.max(1, input.maxPreConsentRows ?? 20));
|
|
303
|
+
const evidence = input.evidence;
|
|
304
|
+
const report = input.report;
|
|
305
|
+
const findings = Array.isArray(input.findings.findings) ? input.findings.findings.slice(0, maxFindings) : [];
|
|
306
|
+
const preConsentRows = Array.isArray(input.preConsentCookiesTrackers.rows)
|
|
307
|
+
? input.preConsentCookiesTrackers.rows.slice(0, maxPreConsentRows)
|
|
308
|
+
: [];
|
|
309
|
+
const links = {
|
|
310
|
+
...(input.scan.links ?? {}),
|
|
311
|
+
...(report.links && typeof report.links === "object" && !Array.isArray(report.links) ? report.links : {})
|
|
312
|
+
};
|
|
313
|
+
return {
|
|
314
|
+
type: "certscore_scan_bundle",
|
|
315
|
+
scanId: input.scan.scanId,
|
|
316
|
+
status: input.scan.status,
|
|
317
|
+
resultDisposition: input.scan.resultDisposition ?? null,
|
|
318
|
+
noGo: input.scan.noGo ?? null,
|
|
319
|
+
scan: input.scan,
|
|
320
|
+
summary: {
|
|
321
|
+
summary: report.summary ?? null,
|
|
322
|
+
executiveSummary: report.executiveSummary ?? null,
|
|
323
|
+
counts: report.counts ?? null,
|
|
324
|
+
coverage: report.coverage ?? input.scan.coverage ?? null,
|
|
325
|
+
agentInterpretation: report.agentInterpretation ?? null
|
|
326
|
+
},
|
|
327
|
+
findings,
|
|
328
|
+
findingsMetadata: {
|
|
329
|
+
shown: findings.length,
|
|
330
|
+
total: Array.isArray(input.findings.findings) ? input.findings.findings.length : 0,
|
|
331
|
+
truncated: Array.isArray(input.findings.findings) && input.findings.findings.length > findings.length
|
|
332
|
+
},
|
|
333
|
+
evidenceSummary: {
|
|
334
|
+
evidenceSafetyNotes: evidence.evidenceSafetyNotes ?? null,
|
|
335
|
+
projectionDiagnostics: evidence.projectionDiagnostics ?? null,
|
|
336
|
+
projectedFindings: compactEvidenceValue(evidence.projectedFindings ?? [], {
|
|
337
|
+
arrayItems: maxFindings,
|
|
338
|
+
depth: 5,
|
|
339
|
+
objectKeys: 40,
|
|
340
|
+
stringChars: 1_000
|
|
341
|
+
}),
|
|
342
|
+
coverageDiagnostics: compactEvidenceValue(evidence.coverageDiagnostics ?? null, {
|
|
343
|
+
arrayItems: 20,
|
|
344
|
+
depth: 5,
|
|
345
|
+
objectKeys: 40,
|
|
346
|
+
stringChars: 1_000
|
|
347
|
+
}),
|
|
348
|
+
policySurfaceCoverage: compactEvidenceValue(evidence.policySurfaceCoverage ?? null, {
|
|
349
|
+
arrayItems: 20,
|
|
350
|
+
depth: 5,
|
|
351
|
+
objectKeys: 40,
|
|
352
|
+
stringChars: 1_000
|
|
353
|
+
})
|
|
354
|
+
},
|
|
355
|
+
preConsentCookiesTrackers: {
|
|
356
|
+
summary: input.preConsentCookiesTrackers.summary,
|
|
357
|
+
rows: preConsentRows,
|
|
358
|
+
shown: preConsentRows.length,
|
|
359
|
+
total: Array.isArray(input.preConsentCookiesTrackers.rows) ? input.preConsentCookiesTrackers.rows.length : 0,
|
|
360
|
+
truncated: Array.isArray(input.preConsentCookiesTrackers.rows) && input.preConsentCookiesTrackers.rows.length > preConsentRows.length
|
|
361
|
+
},
|
|
362
|
+
links,
|
|
363
|
+
recommendedNextTool: findings.length > 0 ? "explain_finding" : null,
|
|
364
|
+
disclaimer: input.scan.disclaimer ?? input.report.disclaimer ?? null
|
|
365
|
+
};
|
|
366
|
+
}
|
|
300
367
|
export function explainFinding(report, findingId) {
|
|
301
368
|
const finding = findingsFromReport(report).find((candidate) => candidate.id === findingId);
|
|
302
369
|
if (!finding) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@certscore/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"mcpName": "ai.certscore/mcp",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "MCP server for CertScore public website risk-signal workflows.",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"dist",
|
|
32
32
|
"README.md",
|
|
33
33
|
"LICENSE",
|
|
34
|
-
"server.json"
|
|
34
|
+
"server.json",
|
|
35
|
+
"server-light.json"
|
|
35
36
|
],
|
|
36
37
|
"publishConfig": {
|
|
37
38
|
"access": "public"
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@certscore/api-contracts": "0.1.0",
|
|
63
|
-
"@certscore/sdk": "0.2.
|
|
64
|
+
"@certscore/sdk": "0.2.6"
|
|
64
65
|
},
|
|
65
66
|
"scripts": {
|
|
66
67
|
"build": "tsc -p tsconfig.json && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --banner:js='#!/usr/bin/env node' --outfile=dist/certscore-mcp.mjs",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "ai.certscore/light",
|
|
4
|
+
"title": "CertScore Light",
|
|
5
|
+
"description": "No-account public website privacy risk scans with 20 new scans/day and free recent-result reuse.",
|
|
6
|
+
"status": "active",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/ergoveritas1-alt/certscore.ai",
|
|
9
|
+
"source": "github"
|
|
10
|
+
},
|
|
11
|
+
"websiteUrl": "https://certscore.ai/developers/mcp",
|
|
12
|
+
"version": "0.2.12",
|
|
13
|
+
"remotes": [
|
|
14
|
+
{
|
|
15
|
+
"type": "streamable-http",
|
|
16
|
+
"url": "https://mcp.certscore.ai/mcp/light"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
package/server.json
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "ai.certscore/mcp",
|
|
4
|
+
"title": "CertScore",
|
|
4
5
|
"description": "MCP server for CertScore public website risk-signal workflows.",
|
|
5
6
|
"status": "active",
|
|
6
7
|
"repository": {
|
|
7
8
|
"url": "https://github.com/ergoveritas1-alt/certscore.ai",
|
|
8
9
|
"source": "github"
|
|
9
10
|
},
|
|
10
|
-
"version": "0.2.
|
|
11
|
+
"version": "0.2.12",
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://mcp.certscore.ai/mcp"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
11
18
|
"packages": [
|
|
12
19
|
{
|
|
13
20
|
"registryType": "npm",
|
|
14
21
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
22
|
"identifier": "@certscore/mcp",
|
|
16
|
-
"version": "0.2.
|
|
23
|
+
"version": "0.2.12",
|
|
17
24
|
"transport": {
|
|
18
25
|
"type": "stdio"
|
|
19
26
|
},
|