@certscore/mcp 0.2.3 → 0.2.5

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 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 remote endpoint plus local stdio package. Remote clients can use `https://mcp.certscore.ai/mcp`; external macOS MCP clients can use the Homebrew-distributed stdio command. Local WC01 development uses `pnpm mcp:certscore`.
5
+ Status: public developer preview. The server is distributed for external macOS MCP clients through Homebrew. Local WC01 development uses `pnpm mcp:certscore`.
6
6
 
7
7
  Public docs:
8
8
 
@@ -13,7 +13,8 @@ Public docs:
13
13
 
14
14
  ## Tools
15
15
 
16
- - `scan_site` - Start or reuse a CertScore public-web scan for a public URL. API docs: https://certscore.ai/api/v2/openapi.json
16
+ - `create_scan` - Deprecated compatibility alias of scan_site. Removed in 0.2.0. Use scan_site. Start a CertScore Pulse scan for a public URL and return immediately with status, scan, and polling links.
17
+ - `scan_site` - Start or reuse a CertScore public-web scan for a public URL.
17
18
  - `get_scan` - Retrieve the API v2 public-safe scan resource for a stable scan ID.
18
19
  - `get_scan_status` - Pass scanId (preferred, API v2). Pass jobId only for a just-created scan that has not yet returned a scanId.
19
20
  - `get_report` - Retrieve a summary CertScore Pulse report by stable scan ID. Use get_evidence for the larger bounded evidence packet.
@@ -25,7 +26,17 @@ Public docs:
25
26
  - `get_latest_domain_scan` - Retrieve the latest eligible API v2 public-safe scan for a domain.
26
27
  - `get_latest_domain_pre_consent_cookies_trackers` - Retrieve the public-safe Cookies & Trackers (Pre-consent) table from the latest eligible scan for a domain.
27
28
 
28
- The initial MCP surface intentionally does not include account scan browsing, scan comparison tools, or the removed `create_scan` compatibility alias.
29
+ The initial MCP surface intentionally does not include account scan browsing or scan comparison tools.
30
+
31
+ ## Scan Timing Fields
32
+
33
+ MCP tools backed by API v2 scan resources return scan timing when CertScore has enough timing evidence:
34
+
35
+ - `startedAt`
36
+ - `completedAt`
37
+ - `scanTimeSeconds`
38
+
39
+ This applies to `scan_site` when it returns an API v2 scan resource or job, `get_scan`, and `get_scan_status` when called with a `scanId`. `scanTimeSeconds: null` means timing is unavailable or incomplete and should not be displayed as `0`.
29
40
 
30
41
  ## Configuration
31
42
 
@@ -60,9 +71,12 @@ Run from this monorepo for local development:
60
71
  CERTSCORE_API_KEY=... pnpm mcp:certscore
61
72
  ```
62
73
 
63
- Public API/MCP access uses self-serve read-only keys from `POST https://certscore.ai/api/v2/keys/request` or support-granted
64
- `scan:create` access. Repository-only key generation commands may exist for internal smoke tests, but they are not the public
65
- integration path.
74
+ Generate a scoped preview key after applying DB migrations:
75
+
76
+ ```bash
77
+ pnpm db:migrate
78
+ pnpm mcp:certscore:generate-key -- --name "CertScore MCP preview"
79
+ ```
66
80
 
67
81
  Run the built package directly after local build:
68
82
 
@@ -77,11 +91,11 @@ CERTSCORE_BASE_URL=https://certscore.ai
77
91
  CERTSCORE_REQUEST_TIMEOUT_MS=300000
78
92
  ```
79
93
 
80
- `CERTSCORE_API_KEY` should be a scoped CertScore API token for the workspace or integration user. The MCP server passes it to Pulse as a bearer token and does not persist it.
94
+ `CERTSCORE_API_KEY` should be a scoped CertScore API token for the workspace or preview user. The MCP server passes it to Pulse as a bearer token and does not persist it.
81
95
 
82
96
  ## API Key Access
83
97
 
84
- MCP clients usually need `scan:read` and `mcp` for read workflows. Starting scans also requires `scan:create`, which is grant-gated for launch. Request scan creation access by emailing `support@certscore.ai` with your organization, MCP client, expected workflow, expected request volume, contact email, and requested scopes.
98
+ MCP clients usually need `scan:read`, `scan:create`, and `mcp` scopes. Request developer-preview access by emailing `support@certscore.ai` with your organization, MCP client, expected workflow, expected request volume, contact email, and requested scopes.
85
99
 
86
100
  ## Verify Install
87
101
 
@@ -214,7 +228,7 @@ For the full production operator smoke, run from the WC01 repo:
214
228
  pnpm ops:smoke:mcp-production
215
229
  ```
216
230
 
217
- This verifies the Homebrew-installed `certscore-mcp` command against live `https://certscore.ai`. It creates a short-lived internal smoke key, stores only the hash in production through the approved ECS/Fargate path, checks required tools, requests a fresh EU-IE scan with `freshness: "refresh"` and `scanFrom: "eu_ie"`, requires non-empty findings and pre-consent cookies/trackers rows, runs `explain_finding`, and revokes the temporary key afterward. It exercises existing public-safe API/MCP projections only.
231
+ This verifies the Homebrew-installed `certscore-mcp` command against live `https://certscore.ai`. It creates a short-lived preview key, stores only the hash in production through the approved ECS/Fargate path, checks required tools, requests a fresh EU-IR scan with `freshness: "refresh"` and `scanFrom: "eu_ie"`, requires non-empty findings and pre-consent cookies/trackers rows, runs `explain_finding`, and revokes the temporary key afterward. It exercises existing public-safe API/MCP projections only.
218
232
 
219
233
  ## Troubleshooting
220
234
 
@@ -17846,7 +17846,7 @@ var pulseErrorSchema = external_exports.object({
17846
17846
  var apiV2Disclaimer = "CertScore outputs are automated public-web observations for review. They are not legal advice, certification, or a compliance determination.";
17847
17847
  var apiV2ScanStatusSchema = external_exports.enum(["queued", "running", "finalizing", "completed", "completed_limited", "failed", "expired", "rate_limited"]);
17848
17848
  var apiV2ScanFreshnessSchema = external_exports.enum(["latest", "refresh"]);
17849
- var apiV2ScanFromSchema = external_exports.enum(["eu_ie", "eu_de", "california"]);
17849
+ var apiV2ScanFromSchema = external_exports.enum(["eu_ie"]);
17850
17850
  var apiV2FindingCriticalitySchema = external_exports.enum(["critical", "high", "medium", "low", "info", "unknown"]);
17851
17851
  var apiV2FindingConfidenceSchema = external_exports.enum(["strong", "good", "moderate", "weak", "unknown"]);
17852
17852
  var apiV2EvidenceBasisSchema = external_exports.enum(["runtime_observation", "policy_surface_detection", "accessibility_check", "public_report_projection"]);
@@ -17888,6 +17888,9 @@ var apiV2ScanJobSchema = external_exports.object({
17888
17888
  status: apiV2ScanStatusSchema,
17889
17889
  phase: external_exports.string().optional(),
17890
17890
  createdAt: external_exports.string().optional(),
17891
+ startedAt: external_exports.string().nullable().optional(),
17892
+ completedAt: external_exports.string().nullable().optional(),
17893
+ scanTimeSeconds: external_exports.number().nullable().optional(),
17891
17894
  lastUpdatedAt: external_exports.string().optional(),
17892
17895
  retryAfterSeconds: external_exports.number().int().nullable().optional(),
17893
17896
  links: apiV2LinksSchema.optional(),
@@ -17903,6 +17906,7 @@ var apiV2ScanResourceSchema = external_exports.object({
17903
17906
  createdAt: external_exports.string().nullable().optional(),
17904
17907
  startedAt: external_exports.string().nullable().optional(),
17905
17908
  completedAt: external_exports.string().nullable().optional(),
17909
+ scanTimeSeconds: external_exports.number().nullable().optional(),
17906
17910
  score: external_exports.number().int().min(0).max(100).nullable().optional(),
17907
17911
  riskLevel: external_exports.string().nullable().optional(),
17908
17912
  coverage: external_exports.object({
@@ -18017,7 +18021,7 @@ var mcpPulseDetailSchema = external_exports.enum(["tiny", "quick", "standard", "
18017
18021
  var mcpGptSafePulseDetailSchema = external_exports.enum(["tiny", "standard", "summary"]);
18018
18022
  var mcpPulseFormatSchema = external_exports.enum(["json", "markdown"]);
18019
18023
  var mcpPulseFreshnessSchema = external_exports.enum(["latest", "refresh"]);
18020
- var mcpScanFromSchema = external_exports.enum(["eu_ie", "eu_de", "california"]);
18024
+ var mcpScanFromSchema = external_exports.enum(["eu_ie"]);
18021
18025
  var mcpCreateScanInputSchema = {
18022
18026
  url: external_exports.string().min(1).describe("Public URL or domain to scan."),
18023
18027
  detail: mcpPulseDetailSchema.optional().describe("Pulse detail level. Defaults to summary."),
@@ -18067,7 +18071,7 @@ var mcpGetLatestDomainPreConsentCookiesTrackersInputSchema = {
18067
18071
  };
18068
18072
  var scanCreationAnnotations = {
18069
18073
  readOnlyHint: false,
18070
- destructiveHint: true,
18074
+ destructiveHint: false,
18071
18075
  idempotentHint: false,
18072
18076
  openWorldHint: true
18073
18077
  };
@@ -18102,7 +18106,10 @@ var mcpScanSiteOutputSchema = external_exports.object({
18102
18106
  jobId: external_exports.string().optional(),
18103
18107
  domain: external_exports.string().nullable().optional(),
18104
18108
  status: apiV2ScanStatusSchema,
18105
- scanFrom: apiV2ScanFromSchema.optional()
18109
+ scanFrom: apiV2ScanFromSchema.optional(),
18110
+ startedAt: external_exports.string().nullable().optional(),
18111
+ completedAt: external_exports.string().nullable().optional(),
18112
+ scanTimeSeconds: external_exports.number().nullable().optional()
18106
18113
  }).passthrough();
18107
18114
  var mcpScanStatusOutputSchema = external_exports.object({
18108
18115
  type: external_exports.enum(["certscore_scan_job", "certscore_pulse_status"]).optional(),
@@ -18111,6 +18118,9 @@ var mcpScanStatusOutputSchema = external_exports.object({
18111
18118
  scan_id: external_exports.string().nullable().optional(),
18112
18119
  domain: external_exports.string().nullable().optional(),
18113
18120
  status: external_exports.union([apiV2ScanStatusSchema, pulseStatusSchema.shape.status]).optional(),
18121
+ startedAt: external_exports.string().nullable().optional(),
18122
+ completedAt: external_exports.string().nullable().optional(),
18123
+ scanTimeSeconds: external_exports.number().nullable().optional(),
18114
18124
  error: mcpToolErrorPayloadSchema.shape.error.optional()
18115
18125
  }).passthrough();
18116
18126
  var mcpReportOutputSchema = external_exports.object({
@@ -18157,10 +18167,18 @@ var mcpPreConsentCookiesTrackersOutputSchema = apiV2PreConsentCookiesTrackersSch
18157
18167
  })
18158
18168
  });
18159
18169
  var certScoreMcpToolContracts = [
18170
+ {
18171
+ name: "create_scan",
18172
+ title: "Create CertScore Pulse scan",
18173
+ description: "Deprecated compatibility alias of scan_site. Removed in 0.2.0. Use scan_site. Start a CertScore Pulse scan for a public URL and return immediately with status, scan, and polling links.",
18174
+ inputSchema: mcpCreateScanInputSchema,
18175
+ outputSchema: mcpCreateScanOutputSchema,
18176
+ annotations: scanCreationAnnotations
18177
+ },
18160
18178
  {
18161
18179
  name: "scan_site",
18162
18180
  title: "Scan site",
18163
- description: "Start or reuse a CertScore public-web scan for a public URL. API docs: https://certscore.ai/api/v2/openapi.json",
18181
+ description: "Start or reuse a CertScore public-web scan for a public URL. API v2 responses may include startedAt, completedAt, and scanTimeSeconds.",
18164
18182
  inputSchema: mcpCreateScanInputSchema,
18165
18183
  outputSchema: mcpScanSiteOutputSchema,
18166
18184
  annotations: scanCreationAnnotations
@@ -18168,22 +18186,22 @@ var certScoreMcpToolContracts = [
18168
18186
  {
18169
18187
  name: "get_scan",
18170
18188
  title: "Get CertScore scan",
18171
- description: "Retrieve the API v2 public-safe scan resource for a stable scan ID.",
18189
+ description: "Retrieve the API v2 public-safe scan resource for a stable scan ID, including startedAt, completedAt, and scanTimeSeconds when available.",
18172
18190
  inputSchema: mcpGetScanInputSchema,
18173
18191
  outputSchema: apiV2ScanResourceSchema,
18174
18192
  annotations: readOnlyOpenWorldAnnotations
18175
18193
  },
18176
18194
  {
18177
18195
  name: "get_scan_status",
18178
- title: "Get CertScore scan status",
18179
- description: "Pass scanId (preferred, API v2). Pass jobId only for a just-created scan that has not yet returned a scanId.",
18196
+ title: "Get CertScore Pulse scan status",
18197
+ description: "Pass scanId (preferred, API v2) to retrieve status and scan timing fields. Pass jobId only for a just-created scan that has not yet returned a scanId.",
18180
18198
  inputSchema: mcpGetScanStatusInputSchema,
18181
18199
  outputSchema: mcpScanStatusOutputSchema,
18182
18200
  annotations: readOnlyOpenWorldAnnotations
18183
18201
  },
18184
18202
  {
18185
18203
  name: "get_report",
18186
- title: "Get CertScore report",
18204
+ title: "Get CertScore Pulse report",
18187
18205
  description: "Retrieve a summary CertScore Pulse report by stable scan ID. Use get_evidence for the larger bounded evidence packet.",
18188
18206
  inputSchema: mcpGetReportInputSchema,
18189
18207
  outputSchema: mcpReportOutputSchema,
@@ -18191,7 +18209,7 @@ var certScoreMcpToolContracts = [
18191
18209
  },
18192
18210
  {
18193
18211
  name: "get_evidence",
18194
- title: "Get CertScore evidence",
18212
+ title: "Get CertScore Pulse evidence",
18195
18213
  description: "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.",
18196
18214
  inputSchema: mcpGetEvidenceInputSchema,
18197
18215
  outputSchema: pulseResponseSchema,
@@ -18215,7 +18233,7 @@ var certScoreMcpToolContracts = [
18215
18233
  },
18216
18234
  {
18217
18235
  name: "get_pre_consent_cookies_trackers",
18218
- title: "Get pre-consent cookies & trackers",
18236
+ title: "Get pre-consent cookies and trackers",
18219
18237
  description: "Retrieve the public-safe Cookies & Trackers (Pre-consent) report table as compact JSON for a scan.",
18220
18238
  inputSchema: mcpGetPreConsentCookiesTrackersInputSchema,
18221
18239
  outputSchema: mcpPreConsentCookiesTrackersOutputSchema,
@@ -18239,7 +18257,7 @@ var certScoreMcpToolContracts = [
18239
18257
  },
18240
18258
  {
18241
18259
  name: "get_latest_domain_pre_consent_cookies_trackers",
18242
- title: "Get latest pre-consent cookies & trackers",
18260
+ title: "Get latest domain pre-consent cookies and trackers",
18243
18261
  description: "Retrieve the public-safe Cookies & Trackers (Pre-consent) table from the latest eligible scan for a domain.",
18244
18262
  inputSchema: mcpGetLatestDomainPreConsentCookiesTrackersInputSchema,
18245
18263
  outputSchema: mcpPreConsentCookiesTrackersOutputSchema,
@@ -22696,6 +22714,7 @@ function explainFinding(report, findingId) {
22696
22714
  }
22697
22715
 
22698
22716
  // src/server.ts
22717
+ var createScanDeprecationWarningPrinted = false;
22699
22718
  function toolContract(name) {
22700
22719
  const contract = certScoreMcpToolContracts.find((candidate) => candidate.name === name);
22701
22720
  if (!contract) {
@@ -22720,6 +22739,40 @@ function createCertScoreMcpServer(options = {}) {
22720
22739
  version: CERTSCORE_MCP_VERSION
22721
22740
  });
22722
22741
  const registerTool = server.registerTool.bind(server);
22742
+ async function createPulseScanTool(input) {
22743
+ const result = await client.submitScan(input.url, {
22744
+ detail: normalizeDetail2(input.detail),
22745
+ format: normalizeFormat2(input.format),
22746
+ freshness: input.freshness ?? "latest",
22747
+ scanFrom: input.scanFrom
22748
+ });
22749
+ return {
22750
+ type: "certscore_mcp_scan_created",
22751
+ status: result.status,
22752
+ jobId: result.jobId ?? null,
22753
+ scanId: result.scanId ?? result.scan_id ?? null,
22754
+ completed: result.completed ?? false,
22755
+ statusUrl: result.statusUrl ?? result.nextCheckUrl ?? null,
22756
+ resultUrl: result.resultUrl ?? null,
22757
+ reportUrl: result.reportUrl ?? null,
22758
+ pulse: result.pulse ?? null
22759
+ };
22760
+ }
22761
+ registerTool(
22762
+ "create_scan",
22763
+ toolContract("create_scan"),
22764
+ async (input) => {
22765
+ try {
22766
+ if (!createScanDeprecationWarningPrinted) {
22767
+ createScanDeprecationWarningPrinted = true;
22768
+ console.error("[certscore-mcp] create_scan is deprecated and will be removed in 0.2.0. Use scan_site.");
22769
+ }
22770
+ return toToolResult(await createPulseScanTool(input));
22771
+ } catch (error2) {
22772
+ return toToolError(error2);
22773
+ }
22774
+ }
22775
+ );
22723
22776
  registerTool(
22724
22777
  "scan_site",
22725
22778
  toolContract("scan_site"),
@@ -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;CAClB;AAmCD,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,mBAAwB,aAkLzE"}
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;CAClB;AAqCD,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,mBAAwB,aAsNzE"}
package/dist/server.js CHANGED
@@ -3,6 +3,7 @@ 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
5
  import { boundEvidencePacket, exportFindings, limitPreConsentRows, normalizeDetail, normalizeFormat, paginateFindingList, scanIdFromStatus, toToolError, toToolResult } from "./tools.js";
6
+ let createScanDeprecationWarningPrinted = false;
6
7
  function toolContract(name) {
7
8
  const contract = certScoreMcpToolContracts.find((candidate) => candidate.name === name);
8
9
  if (!contract) {
@@ -27,6 +28,37 @@ export function createCertScoreMcpServer(options = {}) {
27
28
  version: CERTSCORE_MCP_VERSION
28
29
  });
29
30
  const registerTool = server.registerTool.bind(server);
31
+ async function createPulseScanTool(input) {
32
+ const result = await client.submitScan(input.url, {
33
+ detail: normalizeDetail(input.detail),
34
+ format: normalizeFormat(input.format),
35
+ freshness: input.freshness ?? "latest",
36
+ scanFrom: input.scanFrom
37
+ });
38
+ return {
39
+ type: "certscore_mcp_scan_created",
40
+ status: result.status,
41
+ jobId: result.jobId ?? null,
42
+ scanId: result.scanId ?? result.scan_id ?? null,
43
+ completed: result.completed ?? false,
44
+ statusUrl: result.statusUrl ?? result.nextCheckUrl ?? null,
45
+ resultUrl: result.resultUrl ?? null,
46
+ reportUrl: result.reportUrl ?? null,
47
+ pulse: result.pulse ?? null
48
+ };
49
+ }
50
+ registerTool("create_scan", toolContract("create_scan"), async (input) => {
51
+ try {
52
+ if (!createScanDeprecationWarningPrinted) {
53
+ createScanDeprecationWarningPrinted = true;
54
+ console.error("[certscore-mcp] create_scan is deprecated and will be removed in 0.2.0. Use scan_site.");
55
+ }
56
+ return toToolResult(await createPulseScanTool(input));
57
+ }
58
+ catch (error) {
59
+ return toToolError(error);
60
+ }
61
+ });
30
62
  registerTool("scan_site", toolContract("scan_site"), async (input) => {
31
63
  try {
32
64
  return toToolResult(await client.scans.create(input.url, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@certscore/mcp",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "mcpName": "ai.certscore/mcp",
5
5
  "private": false,
6
6
  "description": "MCP server for CertScore public website risk-signal workflows.",
@@ -60,7 +60,7 @@
60
60
  },
61
61
  "devDependencies": {
62
62
  "@certscore/api-contracts": "0.1.0",
63
- "@certscore/sdk": "0.1.0"
63
+ "@certscore/sdk": "0.2.3"
64
64
  },
65
65
  "scripts": {
66
66
  "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",
package/server.json CHANGED
@@ -1,37 +1,23 @@
1
1
  {
2
- "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
3
3
  "name": "ai.certscore/mcp",
4
- "title": "CertScore MCP",
5
- "description": "CertScore website risk-signal tools for scans, findings, evidence, and latest-domain lookups.",
4
+ "description": "MCP server for CertScore public website risk-signal workflows.",
5
+ "status": "active",
6
6
  "repository": {
7
7
  "url": "https://github.com/ergoveritas1-alt/certscore.ai",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.2.3",
10
+ "version": "0.2.5",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
+ "registryBaseUrl": "https://registry.npmjs.org",
14
15
  "identifier": "@certscore/mcp",
15
- "version": "0.2.3",
16
+ "version": "0.2.5",
16
17
  "transport": {
17
18
  "type": "stdio"
18
19
  },
19
- "environmentVariables": [
20
- {
21
- "name": "CERTSCORE_API_KEY",
22
- "description": "CertScore API key with scan:read, scan:create, and mcp scopes.",
23
- "isRequired": true,
24
- "format": "string",
25
- "isSecret": true
26
- },
27
- {
28
- "name": "CERTSCORE_BASE_URL",
29
- "description": "Optional CertScore API base URL. Defaults to https://certscore.ai.",
30
- "isRequired": false,
31
- "format": "string",
32
- "isSecret": false
33
- }
34
- ]
20
+ "runtimeHint": "node"
35
21
  }
36
22
  ]
37
23
  }