echelongraph-mcp 2.7.2 → 3.0.0

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/CHANGELOG.md CHANGED
@@ -6,6 +6,130 @@ with a provenance attestation by the release workflow of
6
6
  [github.com/echelongraph/echelongraph-mcp](https://github.com/echelongraph/echelongraph-mcp),
7
7
  from the commit tagged `v<version>`.
8
8
 
9
+ ## 3.0.0 — 2026-10-11
10
+
11
+ A major version (README, Versioning): the structured result is now held to 30,000 characters by
12
+ default (the first entry below), so a program that read `structuredContent.data` whole gets it cut
13
+ on a large answer unless it passes `full: true`. A client that hands the model the text or the
14
+ structured result needs no change; it gets an answer where 2.7.x gave Claude Code a file path.
15
+
16
+ - **`fixed_in` is never a version the same CVE still affects** (#3074). `fixed_in`, a match's in
17
+ check_sbom, scan_manifest and check_affected and cve_remediation's for an installed version, is
18
+ the first version above the installed one that no affected range of the advisory holds: the
19
+ fixed bound of the range holding it, moved past any overlapping range of the same record.
20
+ CVE-2026-4800 for lodash 4.17.15 answered `fixed_in` 4.17.21, the bound of `[0, 4.17.21)`, while
21
+ `[4.0.0, 4.18.0)` on the same row still holds 4.17.21; it now answers 4.18.0, and after the #3075
22
+ re-import the row holds only `[4.0.0, 4.18.0)`, CVE-2026-4800's own range. When the bound moved, the API's
23
+ `fixed_in_detail` names the ranges it moved past. The API applies the rule; the server relays
24
+ it. cve_intel carries no `fixed_in`: its description, its `fixed_branches` schema description
25
+ and its note now state the same walk for `fixed_branches` (an affected version's fix is the
26
+ first `fixed` above it that no range of its row holds, none past a range without one), where
27
+ its description had said the `fixed` of the range holding the version is its fix.
28
+ - **A GitHub advisory's ranges go to the CVEs GitHub names for them** (#3075). Where GitHub's
29
+ attribution can be read (its own advisory record, then its one NVD advisory reference, then a
30
+ single CVE alias), a GHSA that OSV merged into a group of several CVEs no longer lends its ranges
31
+ to the others. A record none of these settles keeps its alias set, as before, so no CVE loses a
32
+ range it owns. Matches that came only from a range another CVE owned leave
33
+ check_sbom's, scan_manifest's and check_affected's answers.
34
+ - `cve_remediation` carries `fix_status`, the API's fix-status rule that CVE Pulse pages show
35
+ (`available`, `fix_referenced`, `fix_merged`, `none_confirmed` or `unknown`, with its `basis`),
36
+ and get_cve's `remediation` summary carries it first. Both relay it; the server derives nothing
37
+ (#2841). The rule reads the CVE record's own fix statement (#3076): a fix the record's solutions
38
+ state is `available` with `basis` `cna_solution` and `solution_by` (the sentence, `statement`,
39
+ and the CNA that wrote it, `cna`); a fix `patch_evidence` counts from the record with no sentence
40
+ to quote is `available` with `basis` `patch_evidence_statement` and `stated_in_record`; and
41
+ `unread_inputs` can name `cna_record`.
42
+ - `patch_evidence` has three more labels (#3076, #2518; core-backend): `vendor_advisory_fix` (a
43
+ vendor advisory listing a fix for the CVE under the vendor's own structured category: Red Hat's
44
+ CSAF `vendor_fix`, Microsoft's Vendor Fix), `cna_solution` (a sentence of the CVE record's
45
+ solutions that names a fixed version) and `description_fix` (a sentence of the CVE description
46
+ that does). get_cve's outputSchema names all eight.
47
+ - get_cve relays what the CVE record's CNA wrote (#3070, #3076; core-backend): `cna_solution`, the
48
+ sentence of its solutions that names the fixed release, verbatim; `date_public`, when the
49
+ vulnerability was made public as the CNA states it (`date_public_source` `cna`), which is not the
50
+ record's own `published`; and `cna` (`short_name`, `name`). The note names each as the CNA's
51
+ where the record carries it.
52
+ - get_cve's note gives `score_factors.bod_26_04` where the scorer wrote it (algorithm v3.4, #2880):
53
+ CISA BOD 26-04's remediation timeline for the CVE if the affected system is publicly exposed and
54
+ if it is not, with each input and its source. Its outputSchema describes
55
+ `echelongraph_ssvc_decision` and `score_factors`.
56
+ - CISA's required action is attributed to BOD 26-04, which replaced BOD 22-01 on 10 June 2026
57
+ (#3084): get_cve's note and its `kev_required_action` description, and cve_remediation's note and
58
+ description.
59
+ - `cve_remediation` takes the installed version: `ecosystem`, `package` and `version`, all three
60
+ or none, sent in the `X-EG-Ecosystem`, `X-EG-Package` and `X-EG-Version` request headers and
61
+ never in the URL. It then answers `installed` (that version's verdict against the CVE),
62
+ `fixed_in` and, when it is null, `fixed_in_reason`; that answer is private and `no-store`.
63
+ Fix tags on references match in either case (#2841, #3073).
64
+ - The `sbom_review` prompt reads a fixed version from `fixed_in` alone, and names the source
65
+ package where a row matched upstream (`matched_via` upstream, `matched_package`) (#2834, #2836).
66
+ - README, Claude Desktop: the "check that the file parses" step prints one command per OS with the
67
+ config path filled in (`"$HOME/Library/Application Support/…"` in Terminal,
68
+ `"$env:APPDATA\Claude\…"` in PowerShell). Through 2.7.2 it took a quoted `"<path to the file>"`,
69
+ and the macOS path pasted into it (`~/…`) is not expanded inside quotes, so a valid file answered
70
+ `ENOENT` (#3096).
71
+ - README: fits npm's 64 KiB with its Privacy and License sections; the long reference moved to
72
+ `docs/tools.md`, `docs/exposure-radar.md`, `docs/structured-results.md` and
73
+ `docs/rate-limits.md`, linked from it. Every count it states is held by a test (#3012, #2922).
74
+
75
+ - **Contract change: the structured result is held to 30,000 characters by default; pass
76
+ `full: true` for its data whole.** Claude Code hands the model `structuredContent`, not the text
77
+ blocks, and refuses one past `MAX_MCP_OUTPUT_TOKENS` (25,000 by default), saving it to a file:
78
+ `get_cve` for CVE-2021-44228 was 65,540 characters of `structuredContent` on production beside a
79
+ first text block of 23,759, so the model got a file path, not an answer. Through 2.7.x
80
+ `structuredContent.data` was always the answer whole. Now, when the whole structured result
81
+ (envelope, notes and data, as compact JSON) would pass 30,000 characters, its data is cut, in an
82
+ order each tool states. Where one row is large, every row is first thinned to some of its fields,
83
+ all rows kept, before any row leaves: check_sbom's and scan_manifest's rows to `index`, `purl`,
84
+ `verdict`, `not_assessed_reason`, `cve_ids`, `matched_package` and `matched_via`, each match to
85
+ `cve_id`, `kev_listed`, `ransomware`, `epss_score`, `effective_score`, `score_assessed`,
86
+ `fixed_in` and `fixed_in_reason`, every field the prompts read from a match (all 50 rows of the
87
+ Juice Shop 11.1.2 SBOM stay); check_affected's matches to their scores, flags, criteria and
88
+ `fixed_in`, then fewer; cve_remediation's advisories without each item's `product_ids`, then
89
+ without its text, the note then naming `get_vendor_advisory` as where each text is;
90
+ search_cves' rows to their description and scores, then the scores; the vendor-advisory tools' rows
91
+ without `affected_products`, then without `summary`. Then lists leave entries: get_cve's
92
+ `cpe_match` and `references` first; search_cves', kev_recent's, get_cwe's and the vendor-advisory
93
+ tools' rows from the end; check_affected's `excluded` and `undetermined` samples, then its
94
+ `matches`, `cve_ids` last; check_sbom's and scan_manifest's `not_sent_purls` and `not_checked`, then
95
+ their `results`, the `not_affected` rows leaving first, then the `not_assessed` ones, and only then
96
+ affected rows, from the end; cve_remediation's `vendor_remediations`, then `patches`;
97
+ exposure_radar's daily and weekly series, each keeping its newest entries, then its rankings,
98
+ then `kev_exposure.newest_kev`; then any other list at the top of data. The room left once a
99
+ list fits goes back to the lists emptied before it, as many of their entries as fit, their first
100
+ or a series' newest (scan_manifest's `not_checked` beside 64 thinned rows). No string is shortened and no value changes (a thinned row keeps
101
+ some of its fields, each optional in the outputSchema), so data still validates against the
102
+ outputSchema. Every success now carries `data_truncated` (false when data is whole), and, when it
103
+ is true, `truncated`: each list cut, by its path in data, with `total`, `returned` and, where its
104
+ entries are thinned, `thinned: true`; the note ends with sentences starting `DATA CUT` that say what
105
+ was cut and to which fields, how to read the rest (the offset, limit or page size, where the tool
106
+ pages; a page size said to fit data whole is counted on the rows whole) and that the same call
107
+ with `full: true` returns data whole. Every tool takes `full`
108
+ (boolean, optional). **A program that relied on data whole must pass `full: true`.** The first
109
+ text block is cut from the whole answer exactly as before (TEXT CUT); beside a cut data its TEXT CUT
110
+ sentence says the answer is whole in `structuredContent.data` with `full: true`, and so does each
111
+ sentence that points to another tool's data (#3013).
112
+ - check_sbom's and scan_manifest's first text block keeps each match's `fixed_in_reason` beside
113
+ `fixed_in` at every cut level that keeps matches. Their note, and the `sbom_review` and
114
+ `workload_triage` prompts, say `data.not_sent_purls` lists the purls not sent whole with
115
+ `full: true`, since a DATA CUT can cut that list; the position the note gives still rebuilds it.
116
+ scan_manifest's note says the same of `data.not_checked` (#3013).
117
+ - The `triage_cve` and `sbom_review` prompts read a vendor's workaround or mitigation step from
118
+ `get_vendor_advisory` where cve_remediation's note says its data or text leaves that item's text
119
+ out or shortens it (#3013).
120
+ - scan_manifest answers a file of very many entries: it read each file's entries into its lists as
121
+ the arguments of one call, and a `requirements.txt` of some 170,000 unpinned lines (4.9 million
122
+ characters) failed with "Maximum call stack size exceeded" (#3013).
123
+ - The `cve://` resource reads get_cve's record whole (`full: true`) and cuts it as before; its
124
+ DATA CUT note says get_cve's `structuredContent.data` is whole with `full: true`, and, where it
125
+ cuts, its envelope says `data_truncated: true` with `truncated` naming each list it cut (#3013).
126
+ - README: Claude Code's setup names `MAX_MCP_OUTPUT_TOKENS`; "Long answers" says which part of a
127
+ result each kind of client reads and how each is cut; the Structured results table gains
128
+ `data_truncated` and `truncated` (#3013).
129
+ - `test/text-bound.mjs` records each production-shaped case's structured size beside its text
130
+ size, `text-bound-live.mjs` measures and records it, and the suite fails when any default answer's
131
+ structured result is past the budget (#3013).
132
+
9
133
  ## 2.7.2 — 2026-10-11
10
134
 
11
135
  - exposure_radar flags every radar whose last completed check is past its threshold, not only the