echelongraph-mcp 2.3.1 → 2.3.3
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 +1 -1
- package/dist/index.js +31 -5
- package/package.json +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ API call a tool needs to answer.
|
|
|
17
17
|
|
|
18
18
|
| Tool | What it does |
|
|
19
19
|
|---|---|
|
|
20
|
-
| `cve_summary` | Counts of active CVEs by severity, and when the feed was last updated. |
|
|
20
|
+
| `cve_summary` | Counts of active CVEs by severity band, the count with no severity band from any source (`summary.none`, sent again as `summary.unscored`: CVEs not yet scored, not a rating of None), and when the feed was last updated. |
|
|
21
21
|
| `search_cves` | Search/filter CVEs (severity, min CVSS, text, sort) with EchelonGraph scores and `score_assessed`; the note names each row not yet scored. |
|
|
22
22
|
| `get_cve` | Full record for one CVE: CVSS v3 and (when scored) v4, the EchelonGraph score and its confidence, whether EchelonGraph has scored it (`score_assessed`), EPSS, CISA-KEV status and known ransomware use, the GitHub GHSA id, references, and its published, modified and `updated_at` times. |
|
|
23
23
|
| `cve_exposure` | Internet-exposure footprint for a CVE: exposed service count (distinct ip:port, the `exposed_hosts` field) + country/product breakdown, from the KEV-exposure radar. |
|
package/dist/index.js
CHANGED
|
@@ -386,6 +386,26 @@ function rowsScoreNote(d) {
|
|
|
386
386
|
}
|
|
387
387
|
return out.map((s) => ` ${s}`).join("");
|
|
388
388
|
}
|
|
389
|
+
// ── summary.none, the not-yet-scored bucket (#2610) ──
|
|
390
|
+
//
|
|
391
|
+
// core-backend cve/store.go Summary buckets the active CVEs on effectiveSeverityExpr: the
|
|
392
|
+
// EchelonGraph band, else NVD's, else the CVSS v2 band, else NONE, where a band of NONE or UNKNOWN
|
|
393
|
+
// counts as none. So summary.none counts the CVEs with no severity band from any source, and the
|
|
394
|
+
// store sends the same number again as summary.unscored (CVESummary: "the residual band-less
|
|
395
|
+
// bucket … numerically identical to None"; Critical+High+Medium+Low+Unscored == Total). It is not
|
|
396
|
+
// a count of CVEs rated severity None, but a model handed "counts by severity … none" reports
|
|
397
|
+
// "3,381 CVEs rated None" (production on 2026-09-30: none 3,381, unscored 3,381). The JSON is
|
|
398
|
+
// relayed as sent; the note says what the count is whenever it is above zero, and names
|
|
399
|
+
// summary.unscored as the same count when the answer carries it equal. A zero is not labelled:
|
|
400
|
+
// there is no CVE in it to misreport.
|
|
401
|
+
function summaryNoneNote(d) {
|
|
402
|
+
const none = numAt(d, "summary", "none");
|
|
403
|
+
if (none === undefined || none <= 0)
|
|
404
|
+
return "";
|
|
405
|
+
const unscored = numAt(d, "summary", "unscored");
|
|
406
|
+
const same = unscored === none ? ` summary.unscored (${unscored}) is the same count under its own name.` : "";
|
|
407
|
+
return ` summary.none (${none}) is not a severity rating of None: it counts the active CVEs with no severity band from any source, that is, CVEs not yet scored.${same} Report them as not yet scored, not as CVEs rated None.`;
|
|
408
|
+
}
|
|
389
409
|
async function cveSummary() {
|
|
390
410
|
const tool = "cve_summary";
|
|
391
411
|
try {
|
|
@@ -408,14 +428,15 @@ async function cveSummary() {
|
|
|
408
428
|
],
|
|
409
429
|
};
|
|
410
430
|
const total = numAt(r.data, "summary", "total");
|
|
431
|
+
const none = summaryNoneNote(r.data);
|
|
411
432
|
if (total === undefined)
|
|
412
|
-
return succeeded(r.data, head
|
|
433
|
+
return succeeded(r.data, `${head}${none}`, env);
|
|
413
434
|
if (total === 0) {
|
|
414
|
-
return succeeded(r.data, `${head} The feed reports 0 active CVEs — a measured empty result (we looked and found nothing), not a lookup failure
|
|
435
|
+
return succeeded(r.data, `${head} The feed reports 0 active CVEs — a measured empty result (we looked and found nothing), not a lookup failure.${none}`, env);
|
|
415
436
|
}
|
|
416
437
|
// A stamp that is not a real instant stays in the JSON and is not repeated as prose.
|
|
417
438
|
const stamp = realInstant(updated) ? ` (last updated ${updated})` : "";
|
|
418
|
-
return succeeded(r.data, `${head} The feed holds ${total} active CVEs${stamp}
|
|
439
|
+
return succeeded(r.data, `${head} The feed holds ${total} active CVEs${stamp}.${none}`, env);
|
|
419
440
|
}
|
|
420
441
|
catch (e) {
|
|
421
442
|
return crashed(tool, e);
|
|
@@ -1652,7 +1673,9 @@ const CVE_SUMMARY_OUTPUT = envelopeSchema({
|
|
|
1652
1673
|
high: opt(z.number()),
|
|
1653
1674
|
medium: opt(z.number()),
|
|
1654
1675
|
low: opt(z.number()),
|
|
1655
|
-
|
|
1676
|
+
// #2610: the not-yet-scored bucket, not a rating (see summaryNoneNote).
|
|
1677
|
+
none: opt(z.number()).describe("The active CVEs with no severity band from any source: CVEs not yet scored, not a severity rating of None."),
|
|
1678
|
+
unscored: opt(z.number()).describe("The same count as none, under its own name."),
|
|
1656
1679
|
last_updated: opt(z.string()),
|
|
1657
1680
|
})
|
|
1658
1681
|
.optional(),
|
|
@@ -1818,6 +1841,9 @@ const ANNOTATIONS = { readOnlyHint: true, destructiveHint: false, idempotentHint
|
|
|
1818
1841
|
const CVE_ID_ARG = z.string().describe("a CVE ID, e.g. CVE-2023-44487");
|
|
1819
1842
|
// What each description says about its structured result, naming only fields its schema holds.
|
|
1820
1843
|
const FEED_ENVELOPE = "Its structured result carries state (measured), measured_at, method, coverage, freshness (null: the feed serves no poll-completion time) and notes, with data equal to the API's JSON; the result's last text block repeats it without data (the first text block) and without the note's sentences (the text block before it), with which notes ends.";
|
|
1844
|
+
// #2610: what cve_summary says summary.none is (summaryNoneNote), one constant string for the same
|
|
1845
|
+
// reason as SCORE_ASSESSED_DESCRIPTION below.
|
|
1846
|
+
const SUMMARY_NONE_DESCRIPTION = "summary.none is not a severity rating of None: it counts the active CVEs with no severity band from any source, that is, CVEs not yet scored, and the answer may carry the same count again as summary.unscored. Whenever summary.none is above zero the note says so: report those CVEs as not yet scored, not as CVEs rated None.";
|
|
1821
1847
|
// #2535: what search_cves and get_cve say about score_assessed, one constant string, since the
|
|
1822
1848
|
// site's tool-claims check (marketing-site lib/mcpToolClaims.test.ts) folds only constant strings.
|
|
1823
1849
|
const SCORE_ASSESSED_DESCRIPTION = "echelongraph_score, echelongraph_severity and echelongraph_risk are EchelonGraph's score only when score_assessed is true. With score_assessed false the CVE is NOT YET SCORED, not scored 0: any of those three it carries (0, NONE, 0) is a placeholder, not a rating, and does not mean the CVE is harmless; the API may leave them out instead, score_confidence is NONE, and score_unassessed_reason says why (a rejected record, withdrawn by its numbering authority, is never scored, and the note says NOT SCORED). The note labels each such CVE NOT YET SCORED: report it that way, never as a score of 0. An answer with no score_assessed (an API older than that field) does not say whether the CVE was scored, the note says so, and a 0 there is not a rating either.";
|
|
@@ -1827,7 +1853,7 @@ function createServer() {
|
|
|
1827
1853
|
const server = new McpServer({ name: NAME, version: VERSION }, { instructions: INSTRUCTIONS, capabilities: { tools: { listChanged: false } } });
|
|
1828
1854
|
server.registerTool("cve_summary", {
|
|
1829
1855
|
title: "CVE feed summary",
|
|
1830
|
-
description: `Summary of EchelonGraph's CVE Pulse feed: summary.total active CVEs
|
|
1856
|
+
description: `Summary of EchelonGraph's CVE Pulse feed: summary.total active CVEs, their counts by severity band (summary.critical, summary.high, summary.medium, summary.low), the count with no band (summary.none), and summary.last_updated, the newest modification time among those records. ${SUMMARY_NONE_DESCRIPTION} The feed is polled from its sources on a schedule, so this is the state as of that update. ${FEED_ENVELOPE}`,
|
|
1831
1857
|
outputSchema: CVE_SUMMARY_OUTPUT,
|
|
1832
1858
|
annotations: ANNOTATIONS,
|
|
1833
1859
|
}, async () => checked("cve_summary", CVE_SUMMARY_OUTPUT, await cveSummary()));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "echelongraph-mcp",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"mcpName": "io.echelongraph/echelongraph-mcp",
|
|
5
5
|
"description": "EchelonGraph CVE and internet-exposure data as an MCP server: CVEs with EchelonGraph multi-source scores, CISA-KEV and EPSS, plus a per-CVE internet-exposure footprint derived from Shodan data. Shodan data is owned by Shodan, which holds its copyright (© Shodan). Free and keyless, for Claude, Cursor and any MCP client.",
|
|
6
6
|
"type": "module",
|
package/server.json
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
"url": "https://github.com/echelongraph/echelongraph-mcp",
|
|
9
9
|
"source": "github"
|
|
10
10
|
},
|
|
11
|
-
"version": "2.3.
|
|
11
|
+
"version": "2.3.3",
|
|
12
12
|
"packages": [
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "echelongraph-mcp",
|
|
16
|
-
"version": "2.3.
|
|
16
|
+
"version": "2.3.3",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
},
|