echelongraph-mcp 2.3.4 → 2.5.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 ADDED
@@ -0,0 +1,136 @@
1
+ # Changelog
2
+
3
+ Every version of `echelongraph-mcp` published to npm, newest first. Dates are the npm publish
4
+ dates (UTC). Issue numbers refer to EchelonGraph's tracker. Since 2.3.3 each version is published
5
+ with a provenance attestation by the release workflow of
6
+ [github.com/echelongraph/echelongraph-mcp](https://github.com/echelongraph/echelongraph-mcp),
7
+ from the commit tagged `v<version>`.
8
+
9
+ ## 2.5.0 — 2026-10-04
10
+
11
+ - Four prompts (`triage_cve`, `kev_weekly_brief`, `am_i_affected`, `sbom_review`) and three
12
+ resources (`echelongraph://methodology`, `echelongraph://sources`, `cve://{cve_id}`), in both
13
+ protocol eras (#2722).
14
+ - README: a quick start for Claude Desktop, Claude Code, Cursor, VS Code, Windsurf and Cline,
15
+ and for the hosted endpoint; a title and an example question per tool; what is sent where;
16
+ the API's and the hosted endpoint's rate limits; the HTTP entrypoint's settings.
17
+ - This changelog, now in the npm package.
18
+ - `server.json`: a description covering the 14 tools, and the icon.
19
+
20
+ ## 2.4.0 — 2026-10-04
21
+
22
+ Nine new tools, 14 in all (#2726):
23
+
24
+ - `kev_recent`: the CVEs CISA has added to its KEV catalog, newest first, with due date,
25
+ ransomware use, EPSS and when EchelonGraph first saw each in the catalog; filter by date
26
+ range, ransomware and vendor, and page with a cursor (#2717).
27
+ - `epss_history`: how one CVE's EPSS score has changed, as EchelonGraph recorded it, change-only
28
+ and never a daily series (#2718).
29
+ - `check_affected`: whether a product (CPE path) or a registry package at a version is affected
30
+ by known CVEs; `assessed` false is never "not affected" (#2716).
31
+ - `check_sbom`: up to 200 purls, or a CycloneDX or SPDX JSON document read on your machine,
32
+ checked against the advisory corpus, one verdict per component (#2721).
33
+ - `cve_intel`: weakness (CWE), public exploit code, affected packages and fixed versions for one
34
+ CVE (#2720).
35
+ - `get_cwe`: one CWE and the CVEs classified under it, CISA-KEV-listed first (#2720).
36
+ - `vendor_advisories_for_cve`, `get_vendor_advisory`, `search_vendor_advisories`: the
37
+ vendor-published advisories that name a CVE, one advisory in full, and search (#2719).
38
+
39
+ Also:
40
+
41
+ - An HTTP entrypoint, `dist/http.js` (`npm run start:http`): the same tools over stateless
42
+ Streamable HTTP on `POST /mcp`, in both protocol eras, with an Origin policy, a per-client limit
43
+ and a body cap. It is what EchelonGraph's hosted endpoint, `https://mcp.echelongraph.io/mcp`,
44
+ runs (#2316).
45
+ - `ECHELONGRAPH_MCP_UA`: one product token put ahead of the package's User-Agent, so an
46
+ automated caller such as a monitor is told apart from people using the server (#2724).
47
+ - Inputs a user types for `check_affected`, `kev_recent` and `search_vendor_advisories` travel in
48
+ `X-EG-*` request headers, never in the URL (#1983).
49
+ - `get_cve` relays NVD's configuration tree (`cpe_configurations`) when the API serves it, and
50
+ says when it does not; a missing tree does not mean no product is affected (#2720).
51
+ - A failure message names the HTTP method of a request that is not a GET (`check_sbom`'s POST).
52
+ - Listing files for directories (`glama.json`, a `Dockerfile`, an MCPB manifest); none of them is
53
+ in the npm package (#2723).
54
+
55
+ ## 2.3.4 — 2026-09-30
56
+
57
+ - `cve_summary` says what `summary.nvd_critical` … `summary.nvd_none` count (the same active
58
+ CVEs by NVD's severity label, as provenance, never EchelonGraph's severity) and what
59
+ `summary.rejected` counts (withdrawn records, outside the total) (#2641).
60
+
61
+ ## 2.3.3 — 2026-09-30
62
+
63
+ - The first release published by the public repo's release workflow, with npm provenance. No
64
+ change to the server beyond the version (#2317).
65
+
66
+ ## 2.3.2 — 2026-09-30
67
+
68
+ - `cve_summary`: `summary.none` is labelled as CVEs not yet scored, never as a severity rating of
69
+ None (#2610).
70
+
71
+ ## 2.3.1 — 2026-09-30
72
+
73
+ - `get_cve` and `search_cves` label a CVE EchelonGraph has not scored NOT YET SCORED (a rejected
74
+ record NOT SCORED), never as an EchelonGraph score of 0 (#2535).
75
+
76
+ ## 2.3.0 — 2026-09-30
77
+
78
+ - `exposure_radar` reads a fifth radar, `mcp_servers`: hostnames named like an MCP server in
79
+ EchelonGraph's own Certificate Transparency feed, by their latest verdict on record (RFC 9728
80
+ protected, pending re-adjudication, or not assessed by reason), with the protocol era and
81
+ transport splits. An answer that does not say `service` `mcp` is refused, never relayed (#2315).
82
+
83
+ ## 2.2.0 — 2026-09-28
84
+
85
+ - `not_assessed` no longer says its numbers are not findings: a count it relays is what the
86
+ source holds on record, undated (#2465).
87
+ - The last text block stops repeating what an earlier block already says (#2467).
88
+
89
+ ## 2.1.0 — 2026-09-27
90
+
91
+ - `cve_exposure` answers `not_assessed`, with `measured_at` null: its `last_seen` is when
92
+ EchelonGraph last wrote a row, not when a service was observed (#2439).
93
+ - Every result's last text block carries the structured result, for clients that pass only
94
+ `content` to the model (#2440).
95
+
96
+ ## 2.0.0 — 2026-09-27
97
+
98
+ - Both MCP protocol eras on stdio: `server/discover` (2026-07-28) and `initialize` (2025-11-25
99
+ and earlier) (#2311).
100
+ - Every tool has a title, annotations (`readOnlyHint` and the rest) and an `outputSchema`; every
101
+ result carries `structuredContent` with `state`, `measured_at`, `method`, `coverage`,
102
+ `freshness` and `notes` (#2313).
103
+ - `@modelcontextprotocol/server` 2.1.0 replaces `@modelcontextprotocol/sdk` 1.x; Node.js 20 or
104
+ later (Node.js 18 dropped).
105
+
106
+ ## 1.0.4 — 2026-09-27
107
+
108
+ - `exposure_radar` labels every number of every radar by what it counts, leaves out and names a
109
+ field it cannot label, and never relays a CVE the radar does not track as "0 exposed" (#2313).
110
+ - `exposure_radar` relays each radar's `last_run_at`, its last completed check (#2335).
111
+ - The MIT `LICENSE` file ships in the package.
112
+
113
+ ## 1.0.3 — 2026-09-27
114
+
115
+ - `exposure_radar` regroups the shadow-AI radar's numbers by what each counts
116
+ (`confirmed_exposed`, `observed`, `authentication`) and relays no number unlabelled (#2307).
117
+
118
+ ## 1.0.2 — 2026-09-27
119
+
120
+ - Freshness is the radar's last completed cycle, not a follower instance's zero time (#2307).
121
+ - Every Shodan-derived field states Shodan's ownership and copyright (#2306).
122
+
123
+ ## 1.0.1 — 2026-09-27
124
+
125
+ - An API failure is an error result, never a success with empty data (#1874).
126
+ - Wording: no claim the code does not support; exposure counts are internet-facing services
127
+ (distinct ip:port), attributed as derived from Shodan data (#2306).
128
+ - `cve_exposure` refuses an id that is not a CVE ID, and reports a CVE outside the radar's
129
+ tracked set as not assessed, not 0 (#2313).
130
+ - `mcpName` and `server.json` for the official MCP Registry (#1879); the handshake and the
131
+ User-Agent carry the package version.
132
+
133
+ ## 1.0.0 — 2026-06-25
134
+
135
+ - First release: `cve_summary`, `search_cves`, `get_cve`, `cve_exposure` and `exposure_radar`
136
+ over stdio.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # EchelonGraph MCP server
2
2
 
3
- **CVE and internet-exposure data** for Claude, Cursor, Cline, and any
3
+ **CVE and internet-exposure data** for Claude, Cursor, VS Code, Cline, and any
4
4
  [MCP](https://modelcontextprotocol.io) client, straight from
5
5
  [EchelonGraph](https://echelongraph.io)'s free public feed.
6
6
 
@@ -10,18 +10,168 @@ internet-facing services (distinct ip:port) EchelonGraph's KEV-exposure radar ha
10
10
  running a version that maps to the CVE. Exposure counts are derived from Shodan data.
11
11
  Shodan data is owned by Shodan, which holds its copyright (© Shodan).
12
12
 
13
+ Its 14 tools look up one CVE (record, exploit code and fixed versions, EPSS history, vendor
14
+ advisories, exposure footprint), list CISA's newest KEV additions, check whether a product or
15
+ package version is affected, check an SBOM, and read a CWE and its CVEs. Every result says how
16
+ it was measured (`state`, `measured_at`, `method`, `coverage`, `freshness`, `notes`), so a model
17
+ cannot mistake an outage or an unassessed lookup for an all-clear.
18
+
19
+ It also serves four prompts, ready-made workflows a client can show as slash commands
20
+ (`triage_cve`, `kev_weekly_brief`, `am_i_affected`, `sbom_review`; see "Prompts"), and three
21
+ resources (`echelongraph://methodology`, `echelongraph://sources` and `cve://{cve_id}`; see
22
+ "Resources").
23
+
13
24
  Free and keyless: no API key, no auth, read-only. The server makes no request other than the
14
25
  API call a tool needs to answer.
15
26
 
27
+ - npm: [`echelongraph-mcp`](https://www.npmjs.com/package/echelongraph-mcp) · Official MCP
28
+ Registry: `io.echelongraph/echelongraph-mcp` · Docs: <https://echelongraph.io/pulse/mcp>
29
+ - Source: <https://github.com/echelongraph/echelongraph-mcp> · Changes:
30
+ [CHANGELOG.md](CHANGELOG.md) · Security: [SECURITY.md](SECURITY.md)
31
+
32
+ ## Quick start
33
+
34
+ The local server runs through `npx` and needs Node.js 20 or later; nothing is installed
35
+ globally. Add one of the blocks below to your client, restart it, then ask: *"Is
36
+ CVE-2023-44487 actively exploited, and how many exposed services does EchelonGraph's radar
37
+ have on record for it?"*
38
+
39
+ ### Claude Desktop
40
+
41
+ Settings → Developer → Edit Config opens `claude_desktop_config.json`
42
+ (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`; Windows:
43
+ `%APPDATA%\Claude\claude_desktop_config.json`). Add:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "echelongraph": {
49
+ "command": "npx",
50
+ "args": ["-y", "echelongraph-mcp"]
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ### Claude Code
57
+
58
+ ```bash
59
+ claude mcp add --transport stdio echelongraph -- npx -y echelongraph-mcp
60
+ ```
61
+
62
+ Add `--scope user` before the `--` to make it available in every project, or `--scope project`
63
+ to share it through the project's `.mcp.json`.
64
+
65
+ ### Cursor
66
+
67
+ `~/.cursor/mcp.json` (every project) or `.cursor/mcp.json` (one project):
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "echelongraph": {
73
+ "command": "npx",
74
+ "args": ["-y", "echelongraph-mcp"]
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### VS Code
81
+
82
+ `.vscode/mcp.json` in the workspace (VS Code's key is `servers`, not `mcpServers`):
83
+
84
+ ```json
85
+ {
86
+ "servers": {
87
+ "echelongraph": {
88
+ "command": "npx",
89
+ "args": ["-y", "echelongraph-mcp"]
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ or from a terminal:
96
+
97
+ ```bash
98
+ code --add-mcp '{"name":"echelongraph","command":"npx","args":["-y","echelongraph-mcp"]}'
99
+ ```
100
+
101
+ ### Windsurf, Cline and other clients
102
+
103
+ Clients that read an `mcpServers` block (Windsurf's `mcp_config.json`, Cline's MCP settings,
104
+ and most others) take the same block as Cursor above.
105
+
106
+ ### Remote (no install)
107
+
108
+ The same 14 tools, four prompts and three resources are served over Streamable HTTP at:
109
+
110
+ ```text
111
+ https://mcp.echelongraph.io/mcp
112
+ ```
113
+
114
+ Keyless, no sign-in, stateless; both protocol eras. The hosted endpoint is being rolled out:
115
+ until `https://mcp.echelongraph.io/health` answers `{"status":"ok",…}`, use the `npx` setup
116
+ above.
117
+
118
+ - **claude.ai** (Free, Pro, Max, Team and Enterprise plans):
119
+ Customize → Connectors → **+ Add** → **Add custom connector**. Name it `EchelonGraph`, paste
120
+ the URL, choose **No sign in**, and add it. On Team and Enterprise an Owner adds it for the
121
+ organization first, and each member then connects it. Free plans allow one custom connector.
122
+ - **Claude Code**:
123
+
124
+ ```bash
125
+ claude mcp add --transport http echelongraph https://mcp.echelongraph.io/mcp
126
+ ```
127
+
128
+ - **Cursor** (`mcp.json`):
129
+
130
+ ```json
131
+ { "mcpServers": { "echelongraph": { "url": "https://mcp.echelongraph.io/mcp" } } }
132
+ ```
133
+
134
+ - **VS Code** (`.vscode/mcp.json`):
135
+
136
+ ```json
137
+ { "servers": { "echelongraph": { "type": "http", "url": "https://mcp.echelongraph.io/mcp" } } }
138
+ ```
139
+
140
+ - **Windsurf** (`mcp_config.json`):
141
+
142
+ ```json
143
+ { "mcpServers": { "echelongraph": { "serverUrl": "https://mcp.echelongraph.io/mcp" } } }
144
+ ```
145
+
146
+ The hosted endpoint runs this package's code (`dist/http.js`, below) against the same public
147
+ API, so its answers are the npm package's answers. What it sees and logs is under "Privacy",
148
+ and its per-client limit under "Rate limits".
149
+
16
150
  ## Tools
17
151
 
18
- | Tool | What it does |
19
- |---|---|
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), the same CVEs counted by NVD's severity label, as provenance (`summary.nvd_critical` to `summary.nvd_none`), the rejected (withdrawn) records outside the total (`summary.rejected`), and when the feed was last updated. |
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
- | `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
- | `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. |
24
- | `exposure_radar` | Aggregate totals across the exposure radars: services running CISA-KEV CVEs; unauthenticated data stores and observability UIs, found through Shodan (LeakIX when Shodan query credits run low) and then confirmed by EchelonGraph's own identified check, which is not a pure read (on Redis it names its client; on ClickHouse its query lands in the server's query log); leaked credentials; shadow AI; and MCP servers found in EchelonGraph's own Certificate Transparency feed, by RFC 9728 verdict, protocol era and transport. Every number is labelled by what it counts, and a field the tool cannot label is left out and named. |
152
+ Every tool is read-only (`readOnlyHint: true`) and declares a title and an `outputSchema`.
153
+ The example questions are ones a client can answer with that tool alone.
154
+
155
+ | Tool | Title | What it answers | Example question |
156
+ |---|---|---|---|
157
+ | `cve_summary` | CVE feed 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), the same CVEs counted by NVD's severity label, as provenance (`summary.nvd_critical` to `summary.nvd_none`), the rejected (withdrawn) records outside the total (`summary.rejected`), and when the feed was last updated. | *How many critical CVEs does the feed hold, and when was it last updated?* |
158
+ | `search_cves` | Search CVEs | Search/filter CVEs (severity, min CVSS, text, sort) with EchelonGraph scores and `score_assessed`; the note names each row not yet scored. | *Find critical CVEs that mention Tomcat with a CVSS of 9 or more.* |
159
+ | `get_cve` | CVE detail | 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. | *What are CVE-2024-3094's CVSS, EPSS and CISA-KEV status?* |
160
+ | `cve_exposure` | Internet exposure for one CVE | Internet-exposure footprint for a CVE: exposed service count (distinct ip:port, the `exposed_hosts` field) + country/product breakdown, from the KEV-exposure radar. | *How many exposed services does EchelonGraph's radar have on record for CVE-2023-44487?* |
161
+ | `exposure_radar` | Exposure radar totals | Aggregate totals across the exposure radars: services running CISA-KEV CVEs; unauthenticated data stores and observability UIs, found through Shodan (LeakIX when Shodan query credits run low) and then confirmed by EchelonGraph's own identified check, which is not a pure read (on Redis it names its client; on ClickHouse its query lands in the server's query log); leaked credentials; shadow AI; and MCP servers found in EchelonGraph's own Certificate Transparency feed, by RFC 9728 verdict, protocol era and transport. Every number is labelled by what it counts, and a field the tool cannot label is left out and named. | *How many unauthenticated data stores has EchelonGraph's radar confirmed?* |
162
+ | `kev_recent` | Recent CISA KEV additions | The CVEs CISA has added to its Known Exploited Vulnerabilities catalog, newest first (`kev_added_date`), from EchelonGraph's copy of the catalog, polled from CISA every 5 minutes: due date, vendor, product, known ransomware use, EchelonGraph's severity, CVSS, EPSS and `eg_kev_tier`, and `our_first_seen_kev`. Filter by date range, ransomware and vendor; page with `limit` and `next_cursor`. Dated by `last_successful_fetch_at`, our last successful fetch of CISA's feed; the filters travel as request headers, never in the URL. | *Which CVEs has CISA added to KEV since 1 September, and which have known ransomware use?* |
163
+ | `epss_history` | EPSS change history for one CVE | How one CVE's EPSS score has changed, as EchelonGraph recorded it: one point per recorded change (`series_kind` `change_only`), never a daily series, with the value now and `series_starts_at`, when recording began; before it a missing point means not recorded, not unchanged. | *How has CVE-2023-44487's EPSS score changed, as EchelonGraph recorded it?* |
164
+ | `check_affected` | Am I affected? (product or package at a version) | Whether a product (its NVD CPE product token) or a registry package (`ecosystem` and `package`) at a given version is affected by known CVEs, from the matcher behind echelongraph.io/am-i-affected: `assessed` first (false: not evaluated, with `not_assessed_reason`, and a count of 0 then is not "not affected"), the matching CVEs with `kev_listed`, `ransomware`, `epss_score`, `effective_score` and `score_assessed`, and advisories it cannot decide counted as `undetermined_count`, never as safe. What you look up travels in request headers, never in the URL. | *Is openssl 3.0.0 affected by known CVEs? Is lodash 4.17.15 on npm?* |
165
+ | `check_sbom` | Check an SBOM against the advisory corpus | A dependency list checked against EchelonGraph's advisory corpus (OSV.dev records), one verdict per component: `affected`, `not_affected`, `undetermined` or `not_assessed`, each with its `not_assessed_reason`. Pass up to 200 purls, or a CycloneDX JSON or SPDX JSON document: the purls are read from it on your machine and only they are sent, in a POST body; the document is not. A deb, apk or rpm purl without a `distro` qualifier naming its release is not assessed (`distro_release_unknown`): EchelonGraph does not guess a release. Only `not_affected` is clean. No ranking, no score. | *Check this CycloneDX SBOM against the advisory corpus.* |
166
+ | `cve_intel` | CVE weakness, exploits and packages | Weakness, public exploit code, affected packages and fixed versions for one CVE, from EchelonGraph's per-CVE enrichment: `cwes`, `exploits` (at most 10, verified first) with `exploits_total`, `exploits_capped`, `exploits_by_kind` and `exploits_by_status`, `affected_packages`, `fixed_versions` and `timeline`. `verified_status` is the label stored with each reference, not a guarantee that the exploit works. An empty `exploits` list is not evidence that no public exploit exists; a section the API could not read is named in `coverage.sections_failed`, never relayed as an empty list. | *Is there public exploit code for CVE-2021-44228, and which versions fix it?* |
167
+ | `get_cwe` | CWE and its CVEs | One CWE (weakness class) and the CVEs classified under it: `name` and `description` from the MITRE CWE catalog EchelonGraph embeds, `total`, and one page of 50 `cves`, ordered as `order` states (CISA-KEV-listed first, then EchelonGraph score). A `total` of 0 says that no CVE in EchelonGraph's feed is classified under that CWE, not that none exists. | *Which CVEs are classified under CWE-79, CISA-KEV-listed first?* |
168
+ | `vendor_advisories_for_cve` | Vendor advisories for one CVE | The vendor-published advisories (Microsoft MSRC, Red Hat, Cisco, Palo Alto Networks, GitHub GHSA and the other feeds EchelonGraph polls) that name one CVE, newest first, at most 20. | *Which vendor advisories name CVE-2024-3400?* |
169
+ | `get_vendor_advisory` | Vendor advisory detail | One vendor advisory in full: description, severity, `cve_ids` and the subset with a CVE record here (`known_cve_ids`), `affected_products`, `remediation` and `references`. | *Show one of those advisories in full: affected products, remediation and references.* |
170
+ | `search_vendor_advisories` | Search vendor advisories | Search vendor advisories by text (title, description, vendor, advisory ID, products, CVE IDs), vendor, severity and whether they name a CVE. The search text is sent in a request header, never in the URL. | *Search vendor advisories for FortiOS, critical severity only.* |
171
+
172
+ The three vendor-advisory tools relay `vendor_published_at` (the vendor's date), `our_first_seen_at`
173
+ (when EchelonGraph first recorded the advisory) and `withdrawn` (the vendor rescinded it; the note
174
+ names each one, and the search leaves them out).
25
175
 
26
176
  Every figure is as fresh as the schedule that refreshes it. The CVE feed is polled from its
27
177
  sources on a schedule; each radar refreshes on its own.
@@ -276,6 +426,60 @@ above, each a whole number adding up to the count it divides, is left out whole
276
426
  field this version does not know is left out, and named only when its name is shaped like a
277
427
  field name.
278
428
 
429
+ ### How `check_affected` decides
430
+
431
+ `check_affected` asks the matcher behind
432
+ [echelongraph.io/am-i-affected](https://echelongraph.io/am-i-affected) whether a version is
433
+ affected, by one of two lookup paths:
434
+
435
+ - **CPE path** — `product` and `version`. `product` is the NVD CPE product token (`openssl`,
436
+ `nginx`), which can differ from a package name. It returns the CVEs whose NVD CPE match
437
+ criteria name that product with a version range that includes the version. It matches the
438
+ token across vendors, so each match carries `cpe_vendor` and `vendor_unknown`: a match with
439
+ `vendor_unknown` true is real, but its vendor is not verified to be yours.
440
+ - **Registry path** — `ecosystem` (`npm`, `PyPI`, `Maven`, …), `package` and `version`. Each OSV
441
+ advisory record EchelonGraph holds for the package is decided against the version as
442
+ affected (a match), not affected (`not_affected_count`) or undetermined.
443
+
444
+ Read `assessed` before `count`. `assessed` false means the lookup did not evaluate the
445
+ component, and `not_assessed_reason` says why: `product_not_in_cpe_corpus`,
446
+ `package_not_cpe_nameable`, `candidate_load_pending` (with `degraded` true),
447
+ `candidate_window_truncated`, `package_not_in_advisory_corpus` or `no_decidable_advisory`. The
448
+ result's `state` is then `not_assessed`, and its `count` of 0 never means "not affected". An
449
+ advisory whose version range cannot be decided at this version is counted in
450
+ `undetermined_count` (up to 50 listed in `undetermined`) and is never reported as safe. `capped`
451
+ says the match list stopped at its cap, and `candidates_capped` that not every candidate CVE was
452
+ loaded. A failed advisory lookup (`advisory_lookup_failed`) comes back as a failure. Each match
453
+ keeps `kev_listed`, `ransomware`, `epss_score`, `effective_score`, `effective_severity` and
454
+ `score_assessed` as the API sent them.
455
+
456
+ `product`, `version`, `ecosystem` and `package` travel in the `X-EG-Product`, `X-EG-Version`,
457
+ `X-EG-Ecosystem` and `X-EG-Package` request headers, never in the URL, so request logs and
458
+ trace spans that record URLs do not hold what was looked up. The endpoint reads a CPE vendor
459
+ from the URL only, so this tool takes none.
460
+
461
+ ## Prompts
462
+
463
+ Ready-made workflows a client can show as slash commands. Each prompt names the tools to call, in
464
+ order, and tells the model to read every result's `state`, `measured_at`, `method`, `coverage`
465
+ and `notes`: `not_assessed` is never reported as clean, a count of 0 under `assessed: false` is
466
+ never "not affected", and each figure cites its `measured_at`. A prompt makes no API request.
467
+
468
+ | Prompt | Arguments | What it asks the model to do |
469
+ |---|---|---|
470
+ | `triage_cve` | `cve_id` | `get_cve`, `cve_intel`, `vendor_advisories_for_cve`, `epss_history` and `cve_exposure` (and `kev_recent` for the due date when the CVE is KEV-listed), then a decision template: exploited, likelihood, reachable, patch, deadline, decision. |
471
+ | `kev_weekly_brief` | `days` (1 to 365, default 7) | Work out the start date from `days`, page through `kev_recent` from it, and write a brief grouped by vendor with ransomware flags and due dates. |
472
+ | `am_i_affected` | `product`, or `ecosystem` and `package`; `version` | `check_affected`, read `assessed` before `count`, with the not-assessed wording. |
473
+ | `sbom_review` | `sbom` (CycloneDX or SPDX JSON text, up to 5,000,000 characters) | Pass the document to `check_sbom`, look up each affected CVE with `get_cve`, and write a fix list ordered by CISA-KEV listing, then EPSS, then score, each key stated. |
474
+
475
+ ## Resources
476
+
477
+ | Resource | What it holds |
478
+ |---|---|
479
+ | `echelongraph://methodology` | What each envelope field and `state` value means, and how each tool measures (Markdown). |
480
+ | `echelongraph://sources` | What the API reports about its feeds when the resource is read: the NVD poller's interval and last poll (of the instance that answered, from `/api/v1/public/cves/summary`) and the CISA KEV catalog's last successful fetch and method (from `/api/v1/public/kev/recent`). It names the feeds no endpoint reports a schedule for, and states none for them (JSON). |
481
+ | `cve://{cve_id}` | One CVE as `get_cve` returns it: the structured result, envelope and record, as JSON. An ID that is not a CVE ID is refused before any request; a CVE the API has no record of is not found. |
482
+
279
483
  ## What a result means
280
484
 
281
485
  Every tool answers in one of two shapes, so a model reading the result cannot mistake an
@@ -323,7 +527,7 @@ declares its shape as an `outputSchema` in `tools/list`, with a title and the an
323
527
  | `state` | `measured`, `not_assessed`, `failed` or `invalid_input` (below). |
324
528
  | `measured_at` | When the underlying observation was made, as the API states it. `null` when the answer does not say or holds no observation; never the Go zero time. |
325
529
  | `method` | How the numbers were produced. `null` on a failure. |
326
- | `coverage` | What the answer covers, where the tool can say: `in_scope` for `cve_exposure`, the list's own account of its count for `search_cves`, and the radars that answered for `exposure_radar`. |
530
+ | `coverage` | What the answer covers, where the tool can say: `in_scope` for `cve_exposure`, the list's own account of its count for `search_cves`, the radars that answered for `exposure_radar`, and `assessed`, `not_assessed_reason`, the lookup path and the match counts for `check_affected`. |
327
531
  | `freshness` | The producing radar's last completed check (`last_run_at`), where the API serves one; `null` where it serves none. |
328
532
  | `notes` | The caveats, one sentence each: what the envelope itself needs saying, then the note from the text block. |
329
533
  | `data` | On a success only: the same JSON as the first text block. |
@@ -372,21 +576,26 @@ a note says so. Per tool:
372
576
  to `mcp_servers.window.to`, over checks made at different times, which is not one observation
373
577
  time. Its numbers keep the labels above. `freshness` holds each radar's `last_run_at` where the
374
578
  API serves one, for `shadow_ai` also `running`, and for `mcp_servers` also `enabled`.
579
+ - `check_affected` is `measured` only when the answer says `assessed` true, and `not_assessed`
580
+ when it says false or does not say. Its `measured_at` and `freshness` are `null`: a match answer
581
+ carries no observation time. Its `coverage` gives `assessed` and `not_assessed_reason` first,
582
+ then `lookup` (`cpe` or `registry`), `match_layer`, `count`, `capped`, `candidates_capped`,
583
+ `excluded_count`, `undetermined_count`, `not_affected_count` and `degraded`.
375
584
 
376
585
  The CVE feed tools' `freshness` is `null`: the feed's answers carry no time at which its
377
586
  pollers last completed a poll for the feed as a whole.
378
587
 
379
588
  A success whose fields do not fit the tool's `outputSchema` (a field of a type the schema does
380
589
  not allow) is returned as a failure with `error.kind` `unexpected_shape`, never relayed. A
381
- field the API adds later is still relayed by the four tools that relay the API's JSON, and
382
- `exposure_radar` leaves it out and names it, as above.
590
+ field the API adds later is still relayed by every tool that relays the API's JSON as data, and
591
+ `exposure_radar` and `cve_intel`, which relay a selection, leave it out and name what they leave out.
383
592
 
384
593
  ## Protocol versions
385
594
 
386
595
  The server answers both eras of the Model Context Protocol on stdio:
387
596
 
388
597
  - **2026-07-28**: a client that opens with `server/discover` receives a DiscoverResult listing
389
- `2026-07-28`, the tools capability and the server instructions, and then sends each request
598
+ `2026-07-28`, the tools, prompts and resources capabilities and the server instructions, and then sends each request
390
599
  with the per-request `_meta` envelope.
391
600
  - **2025 and earlier**: a client that opens with `initialize`, as every 1.x SDK client does,
392
601
  negotiates `2025-11-25`, `2025-06-18`, `2025-03-26` or `2024-11-05`; a version the server does
@@ -402,50 +611,86 @@ Both handshakes also carry the server instructions: the data is public; what `st
402
611
  numbers are aggregate counts of ip:port services, not an internet-wide census; and that Shodan
403
612
  data is Shodan's.
404
613
 
405
- ## Install
406
-
407
- Requires Node.js 20 or later. Add it to your MCP client's config. It runs via `npx` — no global
408
- install needed.
614
+ ## Privacy: what is sent where
409
615
 
410
- ### Claude Desktop
616
+ **The npm package (stdio).** The server runs on your machine and sends HTTPS requests to one
617
+ host, `ECHELONGRAPH_API_BASE` (default `https://app.echelongraph.io`), one per API call a tool
618
+ makes (`exposure_radar` makes five; every other tool makes one). No telemetry: it sends nothing
619
+ else, to EchelonGraph or anyone. Every request carries the User-Agent
620
+ `echelongraph-mcp/<version> (+https://echelongraph.io/pulse/mcp)`, with the
621
+ `ECHELONGRAPH_MCP_UA` token ahead of it when you set one, so the API's access log can count use
622
+ per version. Like any HTTPS request, it also reaches the API from your network address.
411
623
 
412
- `claude_desktop_config.json` → `mcpServers`:
624
+ Where each tool's input travels:
413
625
 
414
- ```json
415
- {
416
- "mcpServers": {
417
- "echelongraph": {
418
- "command": "npx",
419
- "args": ["-y", "echelongraph-mcp"]
420
- }
421
- }
422
- }
423
- ```
626
+ | Input | Where it is sent |
627
+ |---|---|
628
+ | A CVE ID (`get_cve`, `cve_exposure`, `epss_history`, `cve_intel`, `vendor_advisories_for_cve`), a CWE ID and page (`get_cwe`), a vendor and advisory ID (`get_vendor_advisory`) | The URL path. |
629
+ | `search_cves`: the search text, severity, minimum CVSS, sort and limit | The URL query string. |
630
+ | `search_vendor_advisories`: `query` | The `X-EG-Advisory-Search` request header, never the URL. Its vendor, severity, CVE filter, limit and offset go in the query string. |
631
+ | `check_affected`: `product`, `version`, `ecosystem`, `package` | The `X-EG-Product`, `X-EG-Version`, `X-EG-Ecosystem` and `X-EG-Package` request headers, never the URL. |
632
+ | `kev_recent`: `since`, `until`, `ransomware`, `vendor`, `limit`, `cursor` | The `X-EG-Since`, `X-EG-Until`, `X-EG-Ransomware`, `X-EG-Vendor`, `X-EG-Limit` and `X-EG-Cursor` request headers, never the URL. |
633
+ | `check_sbom`: `purls`, or an `sbom` document | The purls, in a POST body. A CycloneDX or SPDX document is read on your machine and only the purls in it are sent; the document is not. |
634
+ | `cve_summary`, `exposure_radar` | No input. |
635
+
636
+ The API marks its answers to header-carried lookups `no-store`, so no shared cache keeps them,
637
+ and request logs and trace spans that record URLs do not hold what was looked up there.
638
+
639
+ **The hosted endpoint.** A request to `https://mcp.echelongraph.io/mcp` reaches EchelonGraph's
640
+ server first, which then calls the same API as above. Per request it logs the HTTP method and
641
+ path, the status, the duration, the body size, the JSON-RPC method and (for `tools/call`) the
642
+ tool name, the protocol version, whether an `Origin` header was sent, and the family of your
643
+ client's User-Agent. It never logs tool arguments or a query string. When a client passes the
644
+ per-client limit it logs that client's address key. It passes your client's public address to
645
+ the API, in a header the API trusts only with a token it checks, so the API's rate limit counts
646
+ you and not the hosted service.
647
+
648
+ EchelonGraph's privacy policy: <https://echelongraph.io/privacy>.
649
+
650
+ ## Rate limits
651
+
652
+ No API key, so the API limits each caller, one IPv4 address or one IPv6 /64 network as its
653
+ platform records it. Its WAF counts all your requests together in a 60-second window and
654
+ refuses one once the count passes the ceiling for its path:
655
+
656
+ | Path | Ceiling per minute | Tools |
657
+ |---|---|---|
658
+ | `/api/v1/public/cves` and every path under it | 12,000 | `cve_summary`, `search_cves`, `get_cve`, `epss_history`, `cve_intel`, `check_affected`, `check_sbom` |
659
+ | `/api/v1/public/cwes`, `/api/v1/public/vendor-advisories`, `/api/v1/public/kev/recent`, `/api/v1/public/shadow-ai-radar` | 6,000 | `get_cwe`, the three vendor-advisory tools, `kev_recent`, and one of `exposure_radar`'s five calls |
660
+ | any other `/api/v1/public` path | 600 | `cve_exposure`, and the rest of `exposure_radar`'s calls |
661
+
662
+ Because the count is shared, heavy use of one path also uses up the lower ceilings. On top of
663
+ that, 500 requests per second per caller is a burst cap on each serving instance, and
664
+ `check_sbom`'s batch route is charged per component: 1,200 components a minute per caller.
665
+ Over a limit the API answers 429 with `Retry-After`, and the tool returns a failure
666
+ (`state` `failed`, `error.status` 429), never empty data. The API's own documentation of these
667
+ limits: <https://echelongraph.io/pulse/api>.
668
+
669
+ The hosted endpoint adds its own limit before the API's: 120 MCP requests a minute per client
670
+ address, counted by each server instance, answered over it with HTTP 429, `Retry-After` and a
671
+ JSON-RPC error (code `-32029`) that names the limit and the seconds to wait. A request body over
672
+ 64 KiB is refused.
424
673
 
425
- ### Cursor / Cline / Windsurf
674
+ ## Configuration
426
675
 
427
- `~/.cursor/mcp.json` (or the client's MCP settings):
676
+ The npm package (stdio):
428
677
 
429
- ```json
430
- {
431
- "mcpServers": {
432
- "echelongraph": {
433
- "command": "npx",
434
- "args": ["-y", "echelongraph-mcp"]
435
- }
436
- }
437
- }
438
- ```
678
+ | Env var | Default | Purpose |
679
+ |---|---|---|
680
+ | `ECHELONGRAPH_API_BASE` | `https://app.echelongraph.io` | Override the API base (self-host / proxy). A credential in the URL is masked wherever a result quotes the base. |
681
+ | `ECHELONGRAPH_API_TIMEOUT_MS` | `15000` | Per-request timeout. A slower answer is reported as a failed lookup, not as empty data. |
682
+ | `ECHELONGRAPH_MCP_UA` | unset | One product token (for example `my-monitor/1.0`) put ahead of this package's own User-Agent, so automated callers such as monitors are told apart from people using the server. A value that is not a single token is ignored. |
439
683
 
440
- Restart the client, then ask: *"Is CVE-2023-44487 actively exploited, and how many exposed
441
- services does EchelonGraph's radar have on record for it?"*
442
-
443
- ## Configuration
684
+ The HTTP entrypoint, `dist/http.js` (`npm run start:http`), serves the same tools over
685
+ Streamable HTTP on `POST /mcp`, with a liveness check on `GET /health`. It reads the three
686
+ variables above and:
444
687
 
445
688
  | Env var | Default | Purpose |
446
689
  |---|---|---|
447
- | `ECHELONGRAPH_API_BASE` | `https://app.echelongraph.io` | Override the API base (self-host / proxy). |
448
- | `ECHELONGRAPH_API_TIMEOUT_MS` | `15000` | Per-request timeout. A slower answer is reported as a failed lookup, not as empty data. |
690
+ | `PORT` | `8080` | The port it listens on. |
691
+ | `MCP_RATE_LIMIT_PER_MIN` | `120` | MCP requests a minute per client address, per instance. |
692
+ | `MCP_MAX_BODY_BYTES` | `65536` | The largest request body it reads. |
693
+ | `ECHELONGRAPH_FORWARD_TOKEN`, `ECHELONGRAPH_FORWARD_HOST`, `MCP_REQUIRE_FORWARD_TOKEN` | unset | EchelonGraph's hosted deployment only: the token with which it names each client to the API. Leave them unset when you run it yourself; the API then counts every request as coming from your server. |
449
694
 
450
695
  ## Develop
451
696
 
@@ -464,6 +709,19 @@ counted as external MCP adoption. `npm test` never leaves the machine.
464
709
  set `ECHELONGRAPH_MCP_BIN` to that package's `echelongraph-mcp` bin: the tests then run the bin
465
710
  directly, as `npx` does, and read that package's own files.
466
711
 
712
+ ## Versioning
713
+
714
+ Semantic versioning: a major version changes protocol behaviour or drops a Node.js version, a
715
+ minor version adds tools or fields, a patch fixes wording or behaviour without changing a tool's
716
+ shape. Every release is published to npm with a provenance attestation by the public repo's
717
+ release workflow, from the commit tagged `v<version>`. Changes per version:
718
+ [CHANGELOG.md](CHANGELOG.md).
719
+
720
+ ## Security
721
+
722
+ Report a vulnerability privately to **support@echelongraph.io**, not in a public issue. The
723
+ policy, the supported versions and the scope are in [SECURITY.md](SECURITY.md).
724
+
467
725
  ## License
468
726
 
469
727
  The code is MIT-licensed.