CVE & exposure intelligence, inside your AI tools
The EchelonGraph MCP server puts our CVE feed — NVD + MITRE-CNA pre-NVD + CISA-KEV + EPSS + GitHub GHSA, with the EG score our published rule sets from them — and a per-CVE internet-exposure footprint (how many internet-facing services, counted as distinct ip:port, our KEV-exposure radar has on record running a version that maps to the CVE) directly into Claude, Cursor, Cline, or any MCP client. Exposure counts are derived from Shodan data. Shodan data is owned by Shodan, which holds its copyright (© Shodan). EchelonGraph claims no ownership of it or copyright in it. Free and keyless: no API key, no auth, read-only.
Install (60 seconds)
It runs via npx and needs Node.js 20 or later — no global install. Add it to your client’s MCP config, then quit the client fully and reopen it. Claude Desktop’s one-click extension, below, needs neither.
Claude Desktop
Three ways in. The first two need no Node.js: a one-click extension that runs the server on your machine, and a custom connector to the hosted endpoint. The third runs the server through npx.
1. Install in Claude Desktop (one click, no Node.js). The link downloads echelongraph-mcp.mcpb, a Claude Desktop extension. Open it, or in Claude Desktop choose Settings → Extensions → Advanced settings → Install Extension and pick it, then choose Install. Claude Desktop runs the server with the Node.js it ships and writes its own configuration: no PATH and no JSON to edit. To update, install the next release’s bundle the same way. Every new release on GitHub carries the bundle (releases up to 2.6.5 do not), built from the release’s tag by the workflow that publishes the npm package, and signed with a SLSA build-provenance attestation. To check a download: gh attestation verify echelongraph-mcp.mcpb --repo echelongraph/echelongraph-mcp.
2. Custom connector (no install). Claude Desktop uses your claude.ai account’s connectors: Customize → Connectors → + Add → Add custom connector. Name it EchelonGraph, paste https://mcp.echelongraph.io/mcp, choose No sign in, and add it. No Node.js, no PATH and no JSON to edit. A connector added on claude.ai shows up in Claude Desktop too.
3. Local server through npx.
- Check Node.js. In Terminal (macOS) or PowerShell (Windows), run
node -v. It must printv20or later. If it prints an older version orcommand not found, install the LTS release from nodejs.org, or on macOS with Homebrew runbrew install node. - Open the config file in a plain-text editor. Settings → Developer → Edit Config shows
claude_desktop_config.json(macOS:~/Library/Application Support/Claude/claude_desktop_config.json; Windows:%APPDATA%\Claude\claude_desktop_config.json). Open it in a plain-text editor such as VS Code, Notepad ornano. TextEdit on macOS turns"into curly quotes (“ ”) when Smart Quotes is on, and the JSON then breaks without an error: turn it off under Edit → Substitutions → Smart Quotes, or use another editor. - Merge, do not replace. The file usually exists already and holds Claude Desktop’s own settings (for example a
"preferences"object). Keep everything in it. Add an"mcpServers"key at the top level, next to the keys already there; if"mcpServers"is already there, add the"echelongraph"entry inside it. Mind the comma between entries. The result looks like this, with your own settings wherepreferencesis:claude_desktop_config.json, merged
{ "preferences": { "…": "the settings already in your file stay as they are" }, "mcpServers": { "echelongraph": { "command": "npx", "args": ["-y", "echelongraph-mcp"] } } }Check that the file parses, macOS (Terminal)
node -e "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'))" "$HOME/Library/Application Support/Claude/claude_desktop_config.json"Check that the file parses, Windows (PowerShell)
node -e "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'))" "$env:APPDATA\Claude\claude_desktop_config.json"No output means valid JSON. A
SyntaxErrormeans the file is broken (often curly quotes or a missing comma);ENOENTmeans there is no file at that path yet. - Quit fully, then reopen. Closing the window leaves the old config loaded. On macOS choose Claude → Quit Claude (⌘Q); on Windows right-click the Claude icon in the system tray and choose Quit. The first start can take longer while
npxdownloads the package. - Check that it worked. Settings → Developer lists
echelongraphas running, and the tools menu in the chat box lists EchelonGraph’s tools, the ones under Tools below. Then ask the question at the end of this section.
Troubleshooting Claude Desktop
spawn npx ENOENT(or the server shows as failed). On macOS, an app opened from the Dock does not get your shell’s PATH, so Claude Desktop cannot findnpxwhere Homebrew (Apple silicon:/opt/homebrew/bin; Intel:/usr/local/bin) or nvm put it. Runwhich npxin Terminal, then give Claude Desktop that absolute path as"command", and the folder it is in asPATHunder"env"(npxstartsnodefrom that same folder). With Homebrew on Apple silicon:With nvm, use the full pathspawn npx ENOENT fix (Homebrew, Apple silicon)
{ "mcpServers": { "echelongraph": { "command": "/opt/homebrew/bin/npx", "args": ["-y", "echelongraph-mcp"], "env": { "PATH": "/opt/homebrew/bin:/usr/bin:/bin" } } } }which npxprints (JSON does not expand~). Then quit fully and reopen. On Windows, install Node.js with the installer from nodejs.org, which addsnpxto the system PATH, then quit Claude Desktop fully and reopen it.- The server does not appear at all. The file is not valid JSON (a missing comma, or curly quotes), or the app was not fully quit: see steps 2 to 4.
- Logs. macOS:
~/Library/Logs/Claude/mcp-server-echelongraph.log(this server’s output) and~/Library/Logs/Claude/mcp.log(connections). Windows:%APPDATA%\Claude\logs\, with the same file names.
Cursor · Windsurf · Cline
Cursor: ~/.cursor/mcp.json or .cursor/mcp.json · Windsurf: mcp_config.json · Cline: its MCP settings. Same block; if the file already has other keys, add mcpServers next to them.
{
"mcpServers": {
"echelongraph": {
"command": "npx",
"args": ["-y", "echelongraph-mcp"]
}
}
}Claude Code
Add --scope user before the -- to use it in every project.
claude mcp add --transport stdio echelongraph -- npx -y echelongraph-mcpVS Code
.vscode/mcp.json — VS Code’s key is servers, not mcpServers.
{
"servers": {
"echelongraph": {
"command": "npx",
"args": ["-y", "echelongraph-mcp"]
}
}
}Remote (no install)
The same server over Streamable HTTP at https://mcp.echelongraph.io/mcp: keyless, no sign-in, for clients that connect to a remote MCP server.
claude.ai: Customize → Connectors → + Add → Add custom connector. Name it EchelonGraph, paste the URL, choose No sign in, and add it. On Team and Enterprise plans an Owner adds it first, then each member connects it.
Claude Code (remote)
claude mcp add --transport http echelongraph https://mcp.echelongraph.io/mcpCursor (remote)
{ "mcpServers": { "echelongraph": { "url": "https://mcp.echelongraph.io/mcp" } } }VS Code (remote)
{ "servers": { "echelongraph": { "type": "http", "url": "https://mcp.echelongraph.io/mcp" } } }Windsurf (remote)
{ "mcpServers": { "echelongraph": { "serverUrl": "https://mcp.echelongraph.io/mcp" } } }The hosted endpoint allows 120 MCP requests a minute per client address and logs no tool arguments; the package README says what each side sees and logs, and Section 11 of our privacy policy says what the hosted endpoint receives, keeps and logs.
Then ask your assistant: “Is CVE-2023-44487 actively exploited, and how many exposed services does EchelonGraph’s radar have on record for it?”
Tools
cve_summaryCounts of active CVEs by severity band, the count with no severity band from any source (not yet scored, not a rating of None), and when the feed was last updated.
Ask: “How many critical CVEs does the feed hold, and when was it last updated?”
search_cvesSearch/filter CVEs (severity, min CVSS, text, sort) with EchelonGraph scores. A CVE not yet scored comes back NOT YET SCORED (score_assessed false), not 0.
Ask: “Find recent CVEs affecting nginx, with EchelonGraph scores.”
get_cveFull detail for one CVE — description, CVSS v3/v4, EG score, EPSS, KEV + ransomware, GHSA, references. A CVE not yet scored comes back NOT YET SCORED (score_assessed false), not 0.
Ask: “Give me the full picture on CVE-2024-3094.”
cve_exposureInternet-exposure footprint for a CVE: how many internet-facing services (distinct ip:port) EchelonGraph's KEV-exposure radar has on record running a version that maps to it, with a country/product breakdown. Derived from Shodan data. Shodan data is owned by Shodan, which holds its copyright (© Shodan). EchelonGraph claims no ownership of it or copyright in it. A CVE outside the radar's tracked set comes back NOT ASSESSED (exposure_state not_assessed), not 0.
Ask: “How many exposed services does EchelonGraph's radar have on record for CVE-2023-44487?”
exposure_radarAggregate totals across the exposure radars: shadow AI; services running CISA-KEV CVEs; unauthenticated data stores and observability UIs, found through Shodan (LeakIX when Shodan query credits run low) and each confirmed by EchelonGraph's own identified check; leaked credentials; and MCP-server counts: hostnames named like an MCP server in EchelonGraph's own Certificate Transparency feed (no Shodan data), each counted once by its latest verdict from EchelonGraph's identified MCP probe, which never sends tools/call. Shodan data is owned by Shodan, which holds its copyright (© Shodan). EchelonGraph claims no ownership of it or copyright in it.
Ask: “How many unauthenticated data stores has EchelonGraph's radar confirmed?”
kev_recentThe CVEs CISA has added to its Known Exploited Vulnerabilities (KEV) catalog, newest first, with kev_added_date, kev_due_date and kev_ransomware (known ransomware-campaign use), from EchelonGraph's copy of that catalog, which polls CISA's feed every 5 minutes. Filter by since, until, ransomware and vendor; page with limit and cursor.
Ask: “Which CVEs has CISA added to its KEV catalog newest first, with known ransomware-campaign use?”
epss_historyHow one CVE's EPSS score (FIRST's exploit-prediction probability, with its percentile) has changed, as EchelonGraph recorded it: points, oldest first, and current, the record's value now. The series is change-only: a point is a recorded change, never a daily series, and before series_starts_at nothing was recorded.
Ask: “How has CVE-2023-44487's EPSS score changed, as EchelonGraph recorded it?”
check_affectedWhether a product or package at a given version is affected by known CVEs, from the same matcher as echelongraph.io/am-i-affected. The CPE path takes product and version; the registry path takes ecosystem (npm, PyPI, Maven and other OSV ecosystem names), package and version. count depends on assessed: assessed false means the lookup did not evaluate this component, and a count of 0 there is not a finding of not affected. A registry match also carries interval, the advisory interval holding it, and fixed_in, the lowest unaffected version above it, or null with fixed_in_reason; a CPE match carries no fixed_in.
Ask: “Is nginx at a given version affected by known CVEs?”
check_sbomCheck a dependency list (purls, or a CycloneDX JSON or SPDX JSON document) against EchelonGraph's advisory corpus, one verdict per component: affected, not_affected, undetermined or not_assessed. Each row the lookup ran for carries matched_package and matched_via (a deb, apk or alpine purl's upstream qualifier matches as its source package; a refused row has neither), and each match carries fixed_in (the next unaffected version) or null with fixed_in_reason. For container images and Kubernetes pods, the input is an SBOM of each image, or its purls. Only the purls are sent to the API, never in a URL. Run from npm, this server is on your machine; over the hosted endpoint (mcp.echelongraph.io) it is EchelonGraph's, and the document is the request body. A deb, apk or rpm purl without a distro qualifier naming its release is not assessed, and not_assessed is never clean.
Ask: “Check this CycloneDX SBOM against the advisory corpus.”
scan_manifestCheck a project's lockfile or pinned manifest against EchelonGraph's advisory corpus, one verdict per dependency: affected, not_affected, undetermined or not_assessed. Read: requirements.txt (== and === pins only), go.mod, package-lock.json, Cargo.lock, Gemfile.lock, composer.lock, poetry.lock and gradle.lockfile; go.sum is refused. Only the purls are sent to the API, never in a URL. Run from npm, this server is on your machine; over the hosted endpoint (mcp.echelongraph.io) it is EchelonGraph's, and the files are the request body. not_checked entries are not clean.
Ask: “Check this package-lock.json against the advisory corpus.”
cve_intelWeakness, exploit code, detection checks, affected packages and fixed versions for one CVE, from EchelonGraph's per-CVE enrichment: cwes, exploits (at most 10, verified first) with exploits_total and exploits_by_kind, affected_packages and fixed_versions. Each affected package carries fixed_branches, every affected range with its fix: fixed_version is one range's fix, not the fix for every affected range. An empty exploits list is not evidence that no public exploit exists.
Ask: “Exploit code, detection checks, affected packages and fixed versions for CVE-2021-44228?”
cve_remediationHow one CVE is fixed, as its sources state it, in one answer: cisa (for a KEV-listed CVE, CISA's required_action, verbatim; CISA's required_action is directed at US federal civilian agencies (under BOD 26-04, which replaced BOD 22-01 on 10 June 2026) and is not EchelonGraph's advice), fixed_branches (each affected package with every affected range), vendor_remediations (each vendor advisory naming the CVE, with remediation_state and items, each with kind, the vendor's own category, never read from the text), fix_references and patches. Every text is relayed as stated by its source; EchelonGraph has not tested it. An empty list or a remediation_state of none_in_source or not_parsed is not a finding that no fix exists.
Ask: “How is CVE-2021-44228 fixed, as its sources state it?”
get_cweOne CWE (weakness class) and the CVEs classified under it: name and description from the MITRE CWE catalog, total, and one page of 50 CVEs, 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.
Ask: “The CVEs classified under CWE-79, CISA-KEV-listed first.”
vendor_advisories_for_cveThe vendor-published advisories that name one CVE, newest first, at most 20. Covers the vendor feeds EchelonGraph polls, for example Microsoft MSRC, Red Hat, Cisco, Palo Alto Networks and GitHub GHSA. Each advisory carries vendor_published_at (the vendor's date), our_first_seen_at (when EchelonGraph first recorded it) and withdrawn (the vendor rescinded it).
Ask: “The vendor advisories that name CVE-2024-21412, newest first.”
get_vendor_advisoryOne vendor advisory in full, by vendor and the vendor's advisory ID: title, description, severity, cve_ids, affected_products, remediation and references, and withdrawn_at and withdrawn_reason when the vendor withdrew it.
Ask: “The remediation and references of one vendor advisory, by vendor and advisory ID.”
search_vendor_advisoriesSearch or list vendor-published advisories, newest first: a query of 3 or more characters is matched case-insensitively as a substring of each advisory's title, description, vendor name, vendor_advisory_id, affected_products and cve_ids, and a query of 1 or 2 characters matches whole words only; filter by vendor, by severity band, and by whether the advisory names any CVE ID. Advisories their vendor withdrew are left out. The query is sent in a request header, never in the URL.
Ask: “Search vendor advisories by severity band, newest first.”
Prompts
Ready-made workflows a client can show as slash commands. Each prompt names the tools to call, in order. A prompt makes no API request.
triage_cveTriage a CVETriage one CVE: get_cve (with CISA's due date when the CVE is KEV-listed), cve_intel, cve_remediation, vendor_advisories_for_cve, epss_history and cve_exposure, then a decision template: exploited, likelihood, reachable, patch, deadline. Each line cites its tool and measured_at, and not_assessed is never reported as clean.
Arguments
cve_id: a CVE ID, e.g. CVE-2024-3400
kev_weekly_briefCISA KEV briefA brief of the CVEs CISA added to its KEV catalog in the last N days (default 7): kev_recent from a since date the model computes from days, grouped by vendor, with ransomware flags and due dates, dated by EchelonGraph's last successful fetch of CISA's feed.
Arguments
days(optional): how many days back to cover, 1 to 365 (default 7)
am_i_affectedAm I affected?Whether a product (its NVD CPE product token) or a registry package (ecosystem and package) at a version is affected by known CVEs: check_affected, read assessed before count. A count of 0 under assessed false is never reported as not affected.
Arguments
product: CPE path: the NVD CPE product token, such as openssl or nginx. Leave out for the registry path.ecosystem: Registry path: the package's ecosystem, such as npm, PyPI or Maven. Give with package.package: Registry path: the package name, such as lodash. Give with ecosystem.version: The version to check, such as 3.0.0.
sbom_reviewSBOM reviewReview an SBOM (CycloneDX JSON or SPDX JSON text, up to 5,000,000 characters): it is passed to check_sbom, then get_cve and cve_intel for each affected CVE, and a prioritised fix list ordered by CISA-KEV listing, then EPSS, then score, each key stated, with each fixed version read from check_sbom's fixed_in where a match carries it. undetermined and not_assessed components are listed as not checked, never as clean.
Arguments
sbom: a CycloneDX JSON or SPDX JSON document, as JSON text, up to 5,000,000 characters; it is passed to check_sbom
workload_triageTriage my workloadsRank your own pods, CI job containers or images against EchelonGraph's advisory corpus and CISA's KEV catalog: you list the workloads and decide their exposure with your own tools, make an SBOM of each image digest, and pass check_sbom the purls alone. Rows are ordered by CISA-KEV listing, then caller-asserted exposure, then EPSS, then score; EchelonGraph measured none of the exposure, and the prompt takes no workload data.
Arguments
source(optional): where the workloads are listed: kubernetes, github_actions or images (default: all three ways are described)
Resources
What a client reads with resources/read: resources/list lists the resources, and resources/templates/list the template, which a client fills in (cve://CVE-2024-3400, for example).
echelongraph://methodologyHow results are measuredWhat each evidence-envelope field (state, measured_at, method, coverage, freshness, notes) means, the state values, and how each tool measures.
echelongraph://sourcesFeed sources and pollingThe feeds and what the API reports about their polling, read when the resource is read: the NVD poller's interval, last poll, poll_count and poll_errors, all of the one instance that answered (the two counts since that instance last started, zeroed on every restart, never the feed's reliability), and the CISA KEV catalog's last successful fetch and method.
cve://{cve_id}CVE recordResource templateOne CVE's record as get_cve returns it, as JSON: the evidence envelope with data, the API's record. Past 30,000 characters of JSON, data is cut as get_cve's first text block is, and get_cve's structured result holds the record whole with full: true. cve_id is a CVE ID such as CVE-2024-3400.
What you can ask
Once it’s connected, just ask in natural language — your assistant picks the right tool and answers from EchelonGraph’s public API. Every figure is as fresh as the last check that produced it. The CVE feed is polled from its sources on a schedule. Each radar has its own schedule and refreshes only while it runs, and exposure_radar’s freshness gives each radar’s last completed check (last_run_at) where the API serves one.
- “Is CVE-2024-3094 actively exploited, and how many services does EchelonGraph's radar have on record running a version that maps to it?”
- “Show me this week's critical CVEs affecting OpenSSL.”
- “What's the EchelonGraph score and KEV status of CVE-2023-44487?”
- “How many unauthenticated data stores has EchelonGraph confirmed, and how many leaked credentials has it found?”
- “Which CVEs has CISA added to its KEV catalog this week, and which have known ransomware-campaign use?”
- “Is openssl 3.0.0 affected by known CVEs?”
- “Check this CycloneDX SBOM against the advisory corpus.”
Why it's different
Most CVE sources tell you that a vulnerability exists. EchelonGraph also runs a KEV-exposure radar, so cve_exposure tells your AI assistant how many internet-facing services (distinct ip:port) the radar has on record running a version that maps to a CVE, with a country and product breakdown. Some other services also map CVEs to internet exposure; this one is free and keyless. It’s all aggregate and host-redacted, served under our free public API.
How it counts: exposure counts are derived from Shodan data. Shodan data is owned by Shodan, which holds its copyright (© Shodan). EchelonGraph claims no ownership of it or copyright in it. Every 12 hours, when Shodan query credits allow, the radar runs one Shodan query per tracked product, reads up to 100 ip:port services per query, and keeps a service when its banner version matches a CISA-KEV or high-EPSS CVE. A count is a banner-version inference over a sample, not an exploit test and not an internet-wide census. The unit is a service, not a machine: a machine answering on two ports counts twice. The radar only looks for its tracked set of CVEs, so for a CVE outside that set the tool answers NOT ASSESSED (exposure_state not_assessed) instead of reporting 0.
How fresh is it? — measured
Measured
Run at 2026-10-19T06:13:44Z (UTC) over the window 2026-09-19 to 2026-10-19, by scripts/measure-cve-freshness.py (cve-freshness/1), from public endpoints only: EchelonGraph’s public API, NVD’s CVE API, CISA’s KEV feed and FIRST’s EPSS API. Re-measured weekly; each figure carries its n and its method.
Before NVD: when EchelonGraph first recorded a CVE, against NVD's publication
n = 14424Of 14405 CVEs that both EchelonGraph and NVD held, EchelonGraph recorded 14252 before NVD published them (98.9%).
When EchelonGraph was first, by: median 29.6 minutes (p25 18.4, p75 45.8, p90 52.1; n = 14252). Of those, 45 were more than a day ahead: CVEs their CNA published long before NVD did.
When NVD was first, EchelonGraph followed by: median 36.9 minutes (p25 1.3, p75 127.8, p90 174.1; n = 153).
- EchelonGraph first: 14252
- NVD first: 153
- Held by EchelonGraph, not yet published by NVD at the measurement: 7
- Published by NVD in the window, not served by EchelonGraph's public API: 12
Method
For every CVE NVD's API 2.0 lists as published in the window (pubStartDate/pubEndDate), and every CVE EchelonGraph's public list returns with published in the window: EchelonGraph's created_at (the time the record was first written; an upsert never rewrites it) against NVD's published. A CVE in our window that NVD's window listing lacks is looked up in NVD by ID. ours_first: created_at earlier than NVD's published; nvd_first: not earlier; not_in_nvd: NVD returned no record by ID at measured_at; not_served_by_us: NVD published it in the window and our public API returned 404. Percentiles are nearest-rank.
CISA KEV additions: CISA's date against when EchelonGraph recorded the listing
n = 32Of 32 KEV additions with a first-seen record, 100% were recorded on CISA’s dateAdded (UTC date) and 100% on that date or the next. CISA’s dateAdded is a date without a time, so this is a count of calendar days, not a latency in hours.
- Same UTC date: 32
Method
For every entry in CISA's known_exploited_vulnerabilities.json with dateAdded in the window: dateAdded against our_first_seen_kev from GET /api/v1/public/kev/recent, compared as calendar dates in UTC. CISA's dateAdded is a date with no time and no stated timezone, so same_day means the same UTC date, not a latency in hours; earlier means our UTC date precedes CISA's date.
EPSS: EchelonGraph's score against FIRST's latest
n = 100A sample of 100 of 14362 CVEs in the window; FIRST scored 89 of them. For 89 (100% of those FIRST scored), EchelonGraph served FIRST’s value for its latest date (2026-10-18).
- Equal to FIRST's latest date: 89
- FIRST has no score yet: 11
Not measurable from the public API today: epss_score_date: the FIRST model date of the served score. Without it the API cannot say which day's EPSS it serves; this metric infers it by value.
Method
For a deterministic sample of window CVEs (smallest sha256(date|CVE)): EchelonGraph's epss_score and epss_percentile from GET /api/v1/public/cves/:id against FIRST's api.first.org/data/v1/epss (scope=time-series) to 5 decimals. matches_latest: equal to FIRST's value for its latest date; matches_older: equal only to an earlier date (lag_days = latest minus that date). The public API has no EPSS score-date field, so the match is by value; epss_updated_at changes only when the value changes and is reported separately.
Enrichment: what a CVE record carries
n = 100The same sample of 100 CVEs, with NVD’s own record of the same CVEs alongside where NVD has the field.
| Field | EchelonGraph | NVD record |
|---|---|---|
| CVSS v3 score | 87 of 100 (87%) | 83 of 98 (84.7%) |
| CVSS v4 score | 31 of 100 (31%) | 29 of 98 (29.6%) |
| CWE | 91 of 100 (91%) | 89 of 98 (90.8%) |
| Affected packages | 19 of 100 (19%) | not compared |
| Exploit references | 0 of 100 (0%) | not compared |
| Vendor advisories | 22 of 100 (22%) | not compared |
Method
For the same sample: cvss_v3 and cvss_v4 present on GET /api/v1/public/cves/:id (a cvss_v3_score of 0 with no cvss_source, or equal to cvss_v4_score, is not counted as v3: the field holds a placeholder or the v4 fallback there); cwe, affected_packages, exploit_references (exploits_total > 0) and vendor_advisories non-empty on GET /api/v1/public/cves/:id/enrichment. A section listed in failed_sections is not assessed and is left out of that field's n. NVD's own record of the same CVEs (any cvssMetricV31/V30, cvssMetricV40, CWE weakness, primary or secondary) is shown alongside.
Same CVEs, same day: keyless CVE MCP servers
Measured 2026-10-19T06:13:44Z (UTC). 20 CVEs asked of each server: the 10 most recent CISA KEV additions and the 10 CVEs NVD published most recently between 72 and 24 hours before the run. EchelonGraph was measured through the published npm package, echelongraph-mcp 3.0.2, as a user installs it. Each cell is the number of CVEs for which the server gave that field. “Errored” counts calls the server still answered with a rate limit or a temporary error after the retries: they say nothing about whether it knows the CVE.
| Server | Knows the CVE | CVSS score | KEV status | EPSS score | KEV status agrees with CISA |
|---|---|---|---|---|---|
| EchelonGraph (echelongraph-mcp from npm, stdio) npx -y echelongraph-mcp@3.0.2 | 20 of 20 | 20 of 20 | 20 of 20 | 20 of 20 | 20 of 20 |
| cve-security.com https://cve-security.com/api/mcp | 19 of 20 | 19 of 20 | 19 of 20 | 19 of 20 | 19 of 19 |
| Datakoot Security Intel https://security.datakoot.com/mcp | 20 of 20 | 18 of 20 | 20 of 20 | 19 of 20 | 20 of 20 |
| CVELens https://api.cvelens.com/mcp | Not measured. not reachable without credentials or not answering: initialize: unreachable: URLError(SSLError(1, '[SSL: TLSV1_ALERT_INTERNAL_ERROR] tlsv1 alert internal error (_ssl.c:992)')); its registry entry declares an X-API-Key header (attempted 2026-10-19T06:26:21Z) | ||||
| notCVE https://notcve.org/mcp | Not measured. not reachable without credentials or not answering: initialize: HTTP 401 application/json: {"error":"invalid_request","error_description":"Missing Authorization header.","resource_metadata":"https://notcve.org/.well-known/oauth-protected-resource"} (attempted 2026-10-19T06:26:22Z) | ||||
| OpenKrill CVE Risk Check https://cve.openkrill.app/mcp | 19 of 20 1 errored | 18 of 20 1 errored | 19 of 20 1 errored | 18 of 20 1 errored | 19 of 19 |
| Pipeworx CIRCL Vulnerability-Lookup https://gateway.pipeworx.io/circl-vulnerability-lookup/mcp | 20 of 20 | 19 of 20 | 0 of 20 | 0 of 20 | 0 of 0 |
| AIS CVE https://api.aislabs.ai/cve/mcp | Not measured. the cve_verdict tool's own description says it needs payment: "PAID — unlock with an AIS CVE key ($19 / 30 days) or campus pass (https://aislabs.ai/gateway/), or pay per call over x402 on the HTTP twin: $0.005. One CVE's pr" (attempted 2026-10-19T06:31:04Z) | ||||
| A2AWire cvewatch https://a2awire.com/mcp/data/cvewatch-cve-security-advisories-nvd-high-critical-9a16b6/http | Not measured. the data_session_query tool's own description says it needs payment: "Buy per-query access to live data listings — first taste free via data_preview. Listing: cvewatch: CVE Security Advisories (NVD, High & Critical) at 0.01 USDC p" (attempted 2026-10-19T06:31:05Z) | ||||
| Nexlab vulnerability-intel https://mcp.nexlab.net/mcp/vulnerability-intel | Not measured. not reachable without credentials or not answering: initialize: HTTP 401 application/json: {"error":{"code":"api_key_required","message":"Supply an API key or OAuth token as 'Authorization: Bearer <token>'.","get_one_at":"/account"}} (attempted 2026-10-19T06:31:05Z) | ||||
| Censys (investigate_cve) | Not measured. paid / token-gated (#2304); not called | ||||
| VulnCheck (search_target_intel) | Not measured. paid / token-gated (#2304); not called | ||||
Method and CVE set
Each MCP server was asked, over its own MCP transport (Streamable HTTP: initialize, notifications/initialized, tools/list, tools/call; EchelonGraph: the published npm package over stdio), about the same CVE set on the same run, with one configured tool per question. Answered means the response carried that field for that CVE, read by fixed rules (read_answer in the script); it is not a judgement of correctness, except kev_agrees_with_cisa, which compares a stated KEV status with CISA's catalogue fetched in the same run. A call the server answered with a rate limit or a temporary error (often its own upstream, NVD) was retried after the wait it asked for (at most 60 s, twice); if it still failed, its questions are counted as errored, not as unanswered. Calls were spaced 2 s apart. Raw responses, every attempt included, are kept.
CVE-2026-102764, CVE-2025-61624, CVE-2026-21589, CVE-2026-76519, CVE-2026-105134, CVE-2026-105133, CVE-2026-0322, CVE-2026-96951, CVE-2026-104935, CVE-2023-22894, CVE-2026-111873, CVE-2026-111872, CVE-2026-111871, CVE-2026-111870, CVE-2026-111869, CVE-2026-111997, CVE-2026-111868, CVE-2026-111866, CVE-2026-111865, CVE-2026-111934
Not measured
- Time from a CVE's publication by its CNA to EchelonGraph serving it (created_at is compared with NVD, not with CVE.org).
- KEV latency in hours: CISA's dateAdded has no time of day.
- Correctness of other servers' CVSS and EPSS values (only whether they were given).
- Servers that need an API key, OAuth or payment, and servers that did not answer (listed with the reason).
- Anything about a CVE outside the stated window and sample.
Raw data: per-CVE freshness CSV · per-server comparison CSV · the JSON this section renders
Cross-check from our database, measured
The same questions, asked of EchelonGraph’s own timestamps instead of its public API, over 2026-09-05 to 2026-10-05 (30 days): read-only SQL against production, with NVD’s CVE API, CISA’s KEV catalog versions and FIRST’s dated EPSS files. This is a dated snapshot, not re-measured weekly. Every figure here is a value in the summary JSON, which the analysis writes from the SQL outputs; the analysis log lists every slower case.
Before NVD, from our first-write time
n = 14215We held all 14215 CVEs NVD published in the window (Rejected excluded) and wrote 13771 of them before NVD published (96.9%), by median 38.6 minutes (p10 14, p25 23.2, p75 51.2, p90 57.1, max 150019.8; n = 13771). 274 were more than 24 hours ahead.
NVD was first for 444; we followed by median 0.4 minutes (p10 0.1, p25 0.2, p75 0.6, p90 1.1, max 1.7; n = 444).
Definition
For each CVE NVD's API 2.0 lists as published in the window (not Rejected): lead = NVD published minus our cves.created_at, the time we first wrote the row.
CNA publication to our first record
n = 13924median 8.8 minutes (p10 3.5, p25 5.4, p75 13.2, p90 22.9, max 1434.7; n = 13924). Within 15 minutes: 11294; within 60 minutes: 13527 (97.1%).
Slower than 60 minutes: 397.
Definition
For each CVE row first written in the window by the MITRE cvelistV5 poller (its mitre_poll history row says first_seen) whose CNA datePublished is also in the window: cves.created_at minus cves.published (the CNA's datePublished, which the upsert never rewrites).
CISA KEV additions: CISA's release against our first record
n = 39CISA’s dateAdded is a date without a time: at that resolution, 36 of 39 (92.3%) were recorded on the same UTC date and 3 two or more days later.
Against the release time of the first CISA catalog version listing the entry: median 26.2 minutes (p10 7.6, p25 20.7, p75 37.3, p90 55.3, max 4312.1; n = 36); within 60 minutes: 33 (91.7%).
Slower than 6 hours: 3. CISA listed each before we held the CVE record, and we flagged it only at CISA’s next catalog change:
| CVE | dateAdded | Our CVE record written | Hours after CISA’s release |
|---|---|---|---|
| CVE-2026-85706 | 2026-09-11 | 2026-09-12T02:53:24Z | 71.9 |
| CVE-2026-76460 | 2026-09-16 | 2026-09-16T20:29:07Z | 44.1 |
| CVE-2026-87886 | 2026-09-16 | 2026-09-17T23:04:47Z | 43.5 |
Not timed: CVE-2026-42016, CVE-2026-42018, CVE-2026-84869. CISA’s public mirror skipped the catalog version that first listed them, so their latency is only known to be between 0 and 189.9 minutes.
Definition
For each entry in CISA's catalog with dateAdded in 2026-09-05..2026-10-04: our first kev_newly_listed history row against CISA's dateReleased of the first catalog version listing it (from CISA's public mirror, github.com/cisagov/kev-data), in minutes; and, at CISA's own date resolution, our UTC date against dateAdded.
EPSS: when we applied each FIRST daily file
n = 30All 30 score dates applied, by median 2.4 hours (p10 0.2, p25 1.2, p75 6.9, p90 13.6, max 20; n = 30) after FIRST published the file. Within 6 hours: 20.
Over the window, we served FIRST’s latest day 78.6% of the time and one day behind 21.4% of it. The fetcher runs every 24 hours, so a run that falls before FIRST’s daily publication waits for the next. Slower than 6 hours (10): 2026-09-08 (8.4 h), 2026-09-09 (19.9 h), 2026-09-10 (20 h), 2026-09-14 (13.6 h), 2026-09-15 (15.6 h), 2026-09-20 (6.6 h), 2026-09-21 (6.9 h), 2026-09-25 (10.8 h), 2026-10-01 (6.2 h), 2026-10-04 (9.7 h).
Definition
For each FIRST score date D from 2026-09-05 to 2026-10-04: the first epss_batch row of the first fetcher run that applied D's values, minus the Last-Modified time of FIRST's epss_scores-D.csv.gz, in hours. A run's score date is the dated file whose values equal the run's sampled rows.
Completeness: every CVE first recorded in the window
n = 13990| Any CVSS score (v3.x or v4) | 12768 of 13990 (91.3%) |
| CVSS v4 | 4225 of 13990 (30.2%) |
| CWE | 12021 of 13990 (85.9%) |
| Affected packages | 4290 of 13990 (30.7%) |
| Exploit references | 80 of 13990 (0.6%) |
| A product vendor's own advisory | 3088 of 13990 (22.1%) |
| GitHub Advisory Database entry | 13036 of 13990 (93.2%) |
| EPSS | 13967 of 13990 (99.8%) |
| EchelonGraph score assessed | 12772 of 13990 (91.3%) |
A CVSS v3 score distinct from the v4 score: 11482 (82.1%); the stored v3 field can hold a CNA’s v4 score where no v3 exists. The GitHub Advisory Database mirrors most CVEs, so it is not counted as a vendor advisory.
Definition
As of the query time, for every CVE row first written in the window (not REJECTED/RESERVED): whether it holds each field. No sampling.
Exposure counts derived from Shodan data · © Shodan · EchelonGraph claims no copyright in Shodan data · Open source · MIT · Changelog · Public API docs · KEV-Exposure radar