EchelonGraph MCP server
MCP serverSecurityLets your agent look up software vulnerabilities and check which exposed devices are affected, free without an API key.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use EchelonGraph MCP server
About this server
CVE, KEV, EPSS, SBOM and advisory lookups; per-CVE exposure from Shodan data (© Shodan). Keyless.
Install EchelonGraph MCP server
The server’s own address, for the clients that take one directly. Or connect ahel once and every client you use reads it from one address, with the account kept on ahel rather than in each client’s config.
Claude Code
claude mcp add --transport http --scope user echelongraph-mcp-server 'https://mcp.echelongraph.io/mcp'Run it once in your project, then open /mcp to approve any sign-in the server asks for.
Claude Desktop
https://mcp.echelongraph.io/mcpAdd a custom connector in Settings, paste this address, and approve the sign-in.
Cursor
cursor://anysphere.cursor-deeplink/mcp/install?name=echelongraph-mcp-server&config=eyJ1cmwiOiJodHRwczovL21jcC5lY2hlbG9uZ3JhcGguaW8vbWNwIn0=Open the link and Cursor adds the server at that address.
ChatGPT
https://mcp.echelongraph.io/mcpIn Settings, enable Developer mode, create an MCP app, and paste this address. Your plan and workspace must allow custom apps.
Codex
codex mcp add echelongraph-mcp-server --url 'https://mcp.echelongraph.io/mcp'Run it once, then sign in with codex mcp login echelongraph-mcp-server if the server asks for an account.
From the project's README
As published by echelongraph/echelongraph-mcp in README.md.
CVE and internet-exposure data for Claude, Cursor, VS Code, Cline, and any MCP client, straight from EchelonGraph's free public feed.
It exposes EchelonGraph's CVE Pulse (NVD + MITRE-CNA pre-NVD + CISA-KEV + EPSS + GitHub GHSA, fused into one score) plus a per-CVE internet-exposure footprint: how many internet-facing services (distinct ip:port) EchelonGraph's KEV-exposure radar has on record running a version that maps to the CVE. Exposure counts are derived from Shodan data. Shodan data is owned by Shodan, which holds its copyright (© Shodan).
Its 14 tools look up one CVE (record, exploit code and fixed versions, EPSS history, vendor
advisories, exposure footprint), list CISA's newest KEV additions, check whether a product or
package version is affected, check an SBOM, and read a CWE and its CVEs. Every result says how
it was measured (state, measured_at, method, coverage, freshness, notes), so a model
cannot mistake an outage or an unassessed lookup for an all-clear.
It also serves four prompts, ready-made workflows a client can show as slash commands
(triage_cve, kev_weekly_brief, am_i_affected, sbom_review; see "Prompts"), and three
resources (echelongraph://methodology, echelongraph://sources and cve://{cve_id}; see
"Resources").
Free and keyless: no API key, no auth, read-only. The server makes no request other than the API call a tool needs to answer.
- npm:
echelongraph-mcp· Official MCP Registry:io.echelongraph/echelongraph-mcp· Docs: https://echelongraph.io/pulse/mcp - Source: https://github.com/echelongraph/echelongraph-mcp · Changes: CHANGELOG.md · Security: SECURITY.md
Quick start
The local server runs through npx and needs Node.js 20 or later; nothing is installed
globally. Add one of the blocks below to your client, restart it, then ask: "Is
CVE-2023-44487 actively exploited, and how many exposed services does EchelonGraph's radar
have on record for it?"
Claude Desktop
Settings → Developer → Edit Config opens claude_desktop_config.json
(macOS: ~/Library/Application Support/Claude/claude_desktop_config.json; Windows:
%APPDATA%\Claude\claude_desktop_config.json). Add:
{
"mcpServers": {
"echelongraph": {
"command": "npx",
"args": ["-y", "echelongraph-mcp"]
}
}
}
Claude Code
claude mcp add --transport stdio echelongraph -- npx -y echelongraph-mcp
Add --scope user before the -- to make it available in every project, or --scope project
to share it through the project's .mcp.json.
Cursor
~/.cursor/mcp.json (every project) or .cursor/mcp.json (one project):
{
"mcpServers": {
"echelongraph": {
"command": "npx",
"args": ["-y", "echelongraph-mcp"]
}
}
}
VS Code
.vscode/mcp.json in the workspace (VS Code's key is servers, not mcpServers):
{
"servers": {
"echelongraph": {
"command": "npx",
"args": ["-y", "echelongraph-mcp"]
}
}
}
or from a terminal:
code --add-mcp '{"name":"echelongraph","command":"npx","args":["-y","echelongraph-mcp"]}'
Windsurf, Cline and other clients
Clients that read an mcpServers block (Windsurf's mcp_config.json, Cline's MCP settings,
and most others) take the same block as Cursor above.
Remote (no install)
The same 14 tools, four prompts and three resources are served over Streamable HTTP at:
https://mcp.echelongraph.io/mcp
Keyless, no sign-in, stateless; both protocol eras. The hosted endpoint is being rolled out:
until https://mcp.echelongraph.io/health answers {"status":"ok",…}, use the npx setup
above.
-
claude.ai (Free, Pro, Max, Team and Enterprise plans): Customize → Connectors → + Add → Add custom connector. Name it
EchelonGraph, paste the URL, choose No sign in, and add it. On Team and Enterprise an Owner adds it for the organization first, and each member then connects it. Free plans allow one custom connector. -
Claude Code:
claude mcp add --transport http echelongraph https://mcp.echelongraph.io/mcp -
Cursor (
mcp.json):{ "mcpServers": { "echelongraph": { "url": "https://mcp.echelongraph.io/mcp" } } } -
VS Code (
.vscode/mcp.json):{ "servers": { "echelongraph": { "type": "http", "url": "https://mcp.echelongraph.io/mcp" } } } -
Windsurf (
mcp_config.json):{ "mcpServers": { "echelongraph": { "serverUrl": "https://mcp.echelongraph.io/mcp" } } }
The hosted endpoint runs this package's code (dist/http.js, below) against the same public
API, so its answers are the npm package's answers. What it sees and logs is under "Privacy",
and its per-client limit under "Rate limits".
Tools
Every tool is read-only (readOnlyHint: true) and declares a title and an outputSchema.
The example questions are ones a client can answer with that tool alone.
| Tool | Title | What it answers | Example question |
|---|---|---|---|
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? |
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. |
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? |
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? |
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? |
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? |
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? |
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? |
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. |
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? |
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? |
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? |
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. |
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. |
The three vendor-advisory tools relay vendor_published_at (the vendor's date), our_first_seen_at
(when EchelonGraph first recorded the advisory) and withdrawn (the vendor rescinded it; the note
names each one, and the search leaves them out).
Every figure is as fresh as the schedule that refreshes it. The CVE feed is polled from its sources on a schedule; each radar refreshes on its own.
How cve_summary reads its counts
summary.critical, summary.high, summary.medium and summary.low count the active CVEs by
severity band, and summary.total counts them all. summary.none counts the active CVEs with no
severity band from any source: CVEs not yet scored, not CVEs rated None. The answer sends the same
count again as summary.unscored.
summary.nvd_critical, summary.nvd_high, summary.nvd_medium, summary.nvd_low and
summary.nvd_none count the same active CVEs a second time, by NVD's CVSS severity label (v3.x,
else v4.0). Before NVD's record arrives, or where it gives none, a pre-NVD label from the CVE.org
record or a GitHub advisory can stand in. They are provenance, never EchelonGraph's severity band. summary.nvd_none counts the active CVEs with no
Critical, High, Medium or Low label there, and many of those carry an NVD CVSS v2 score instead, so
it is neither a count of CVEs rated None nor the count of CVEs with no severity, which is
summary.none.
summary.rejected counts the CVE records rejected (withdrawn) by their numbering authority.
summary.total and the other counts leave them out, and they are withdrawn records, never
vulnerabilities.
The tool relays the API's JSON as it was sent. When summary.none, summary.nvd_none or
summary.rejected is above zero, the note says what it counts, with the number from the answer.
The note says the five NVD counts add up to summary.total only when the answer's do, and says
nothing about a field the answer does not carry.
How get_cve and search_cves read the EchelonGraph score
echelongraph_score (0 to 10), its band echelongraph_severity and the risk priority
echelongraph_risk (0 to 100) are EchelonGraph's score only when the record's
score_assessed is true. A CVE EchelonGraph has not scored carries score_assessed: false,
and it is not yet scored, not scored 0. The API leaves those three fields out on it, or (an
API before that change) sends 0, NONE and 0 as placeholders, which are not a rating and do
not mean the CVE is harmless. Its score_confidence is NONE, and score_unassessed_reason
says why: no source has yet published severity data EchelonGraph can score, or the record was
rejected (withdrawn) by its numbering authority, and a rejected record is never scored.
Both tools relay the API's JSON as it was sent, and the note says what the score is, for
get_cve per CVE and for search_cves per row, naming each CVE it is about:
| API answer | What the note says |
|---|---|
score_assessed: true | Nothing more: the score is a score. |
score_assessed: false | NOT YET SCORED, tagged "(score_assessed: false)": report the CVE as not yet scored, never as a zero or low score. Each of echelongraph_score, echelongraph_severity and echelongraph_risk it carries is named as a placeholder. A rejected record is NOT SCORED instead. |
no score_assessed field | The answer does not say whether the CVE was scored (an API older than the field), and a zero echelongraph_score in it is not a rating. |
How cve_exposure counts
Every 12 hours, when Shodan query credits allow, the KEV-exposure 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 (>= 0.5) CVE. A service whose row has not been written or refreshed for 21 days is dropped. A count is therefore a banner-version inference over a sample: not an exploit test, and not an internet-wide census.
last_seen is when EchelonGraph last wrote or refreshed a service's row, not when the service
was observed, and not when its vulnerable version was last confirmed. It is set to the time of
the write when a search matches the banner, and again when a re-check finds the port still
listed by Shodan InternetDB, without re-reading the banner, so a patched service can stay
counted while its port stays open. Shodan's own time for the banner is not stored. So
last_seen dates the write, never the sighting, and it is never measured_at (see
"Structured results").
The unit is a service, not a machine. Shodan returns one banner per port and the radar keys
each observation on ip:port, so a machine answering on two ports counts twice. The API's field
names are kept (exposed_hosts here; distinct_hosts, ransomware_hosts and each ranked
row's count in exposure_radar), but every one of them counts ip:port services.
The radar only looks for its tracked set of CVEs, so a zero means different things. The
note tags each case with its exposure_state, as "(exposure_state: …)"; that tag says what the
count is, and is not the result's state:
| API answer | What the tool says |
|---|---|
tracked: true, exposed_hosts above 0 | exposure_state exposed: the count of services the radar has on record for the CVE. |
tracked: false | exposure_state not_assessed, NOT ASSESSED: outside the radar's tracked set; 0 is not a measurement. |
tracked: true, exposed_hosts: 0 | exposure_state measured_zero, a zero in the radar's sample: none of the up to 100 services it read per tracked-product query matched. Not an internet-wide zero, and undated: the answer does not say when the radar looked. The backend does not send this answer today. |
no tracked field | The API did not say whether the CVE is in the tracked set: an older API, or the radar cannot decide (for example a CISA-KEV or high-EPSS CVE in a tracked product with 0 services on record). 0 exposed services on record, with no claim either way (exposure_state tracking_unknown). |
HTTP 400, or an id that is not CVE-YYYY-NNNN… | An error result tagged invalid_input; nothing was looked up. |
Whatever the exposure_state, the result's state is not_assessed with measured_at null:
the answer does not say when any service it counts was observed (see "Structured results").
How exposure_radar counts
exposure_radar relays each radar's answer cut to the fields it can label, and its note labels
every number by what it counts. A field this version does not know is left out and the note
names it, whether it is a total or a field inside a ranked row; so is a known field in an
unexpected shape, and a list with one malformed row is left out whole, since a partial list
reads as a complete one. kev_exposure, exposed_databases and leaked_credentials also
carry generated_at, when the API computed their totals, and, when the API can tell,
last_run_at: when that radar last completed a check (each table below says what a completed
check is for that radar).
last_run_at is a timestamp, not a count. It is recorded for the radar as a whole, not for
the server instance that answered, in UTC to the second. It moves only at the end of a cycle
whose reads succeeded, so a cycle that read nothing leaves it where it was. It is not the time
of every record a radar's numbers count: those cover everything still on record, not only what
the last check found. Nor is it generated_at, which is only when the API recomputed the
totals. The API omits last_run_at when it has no completed check on record or cannot read
it; the result then carries none, and the note says nothing about it. When it is there, the
note gives it as, for example, "kev_exposure last completed check: 2026-09-27T03:12:44Z".
kev_exposure
Every count is over the services the KEV-exposure radar keeps: a Shodan banner whose version
maps to a CISA-KEV-listed CVE (see "How cve_exposure counts").
| Field | What it counts |
|---|---|
kev_exposure.distinct_hosts | Distinct ip:port services, not machines, with at least one CISA-KEV-listed CVE on record. A machine answering on two ports counts twice. |
kev_exposure.ransomware_hosts | The services among them with at least one ransomware-linked KEV CVE (one whose KEV entry notes known use in ransomware campaigns). |
kev_exposure.kev_cves_exposed | Distinct CISA-KEV-listed CVEs with at least one service on record. |
kev_exposure.ransomware_cves | The ransomware-linked CVEs among them. |
kev_exposure.correlations | Service×CVE pairs, not services: a service with three KEV CVEs counts three times. |
kev_exposure.top_products, kev_exposure.top_countries, kev_exposure.top_cves | Up to 12 products, 10 countries and 12 CVEs, ranked by distinct ip:port services. |
kev_exposure.top_cves[].cvss_v3_score, kev_exposure.top_cves[].epss_score | Scores, not counts: the highest CVSS v3 base score and EPSS probability (0 to 1) recorded on that CVE's observations. |
kev_exposure.trend | Service×CVE pairs, not services, by the week (starting Monday) in which each pair was first recorded, over the last 12 weeks. Only pairs still on record are counted, so earlier weeks read low. |
kev_exposure.newest_kev | The 15 CVEs that EchelonGraph's CVE records most recently mark as CISA-KEV-listed (by added_date), each with an exposure_state (below). |
kev_exposure.newest_kev[].exposed_hosts | Only on a row whose exposure_state is exposed (or measured_zero): distinct ip:port services on record with that CVE. |
kev_exposure.newest_kev[].cvss_v3_score, kev_exposure.newest_kev[].epss_score | Scores, not counts: the CVE record's CVSS v3 base score and EPSS probability (0 to 1), absent when the record has none. |
kev_exposure.last_run_at | A timestamp, not a count: when the radar last completed a check, a cycle in which its Shodan search answered at least one query (others may have failed) and its list of services due for a re-check was read. A cycle that skipped the search for want of Shodan query credits, or whose reads failed, does not move it. |
A newest_kev row's exposure_state says whether its count is a measurement:
exposure_state | What it means |
|---|---|
exposed | The radar has services on record with this CVE, and exposed_hosts counts them. |
not_assessed | NOT ASSESSED: the answer holds no measurement for this CVE, so no count is relayed. The API answers 0 for every CVE the radar holds no service for, whether or not the radar looks for that CVE at all, and it never marks a CVE as tracked with 0 services, so that 0 is not a measurement. cve_exposure says per CVE whether it is in the radar's tracked set. |
measured_zero | Only if the API ever marks a row tracked: true with 0 services: a zero in the radar's sample, not an internet-wide zero. If it marks a row tracked: false, that row is not_assessed whatever its count. |
exposed_databases
Shortened here. Read the whole README on GitHub.
Signals
- Last commit
- Oct 2026
- Weekly downloads
- 2k
- Weekly_downloads
- 2k weekly_downloads
Advanced
- Delivery
- echelongraph-mcp MCP server → your ahel connector (mcp.ahel.ai) → your AI.
- Item type
- mcp-server
- Key
io-echelongraph-echelongraph-mcp- Source
- github.com/echelongraph/echelongraph-mcp
- Hosted endpoint
https://mcp.echelongraph.io/mcp
github.com/echelongraph/echelongraph-mcp
More in Security
MCP server
More in Securitynotfair
MCP server · nowork-studio
More in Securityfeedmyagent
MCP server · makash
More in Securityopenwork
MCP server · different-ai
More in Securitylazaretto
MCP server · jamesdfinance-dev
More in Securityosv-advisory-mcp-server
MCP server · cyanheads
More in Security