EchelonGraph MCP server

MCP serverSecurity

Lets 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.

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/mcp

    Add 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/mcp

    In 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.

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.

ToolTitleWhat it answersExample question
cve_summaryCVE feed summaryCounts 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_cvesSearch CVEsSearch/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_cveCVE detailFull 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_exposureInternet exposure for one CVEInternet-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_radarExposure radar totalsAggregate 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_recentRecent CISA KEV additionsThe 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_historyEPSS change history for one CVEHow 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_affectedAm 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_sbomCheck an SBOM against the advisory corpusA 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_intelCVE weakness, exploits and packagesWeakness, 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_cweCWE and its CVEsOne 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_cveVendor advisories for one CVEThe 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_advisoryVendor advisory detailOne 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_advisoriesSearch vendor advisoriesSearch 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 answerWhat the note says
score_assessed: trueNothing more: the score is a score.
score_assessed: falseNOT 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 fieldThe 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 answerWhat the tool says
tracked: true, exposed_hosts above 0exposure_state exposed: the count of services the radar has on record for the CVE.
tracked: falseexposure_state not_assessed, NOT ASSESSED: outside the radar's tracked set; 0 is not a measurement.
tracked: true, exposed_hosts: 0exposure_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 fieldThe 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").

FieldWhat it counts
kev_exposure.distinct_hostsDistinct 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_hostsThe 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_exposedDistinct CISA-KEV-listed CVEs with at least one service on record.
kev_exposure.ransomware_cvesThe ransomware-linked CVEs among them.
kev_exposure.correlationsService×CVE pairs, not services: a service with three KEV CVEs counts three times.
kev_exposure.top_products, kev_exposure.top_countries, kev_exposure.top_cvesUp 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_scoreScores, not counts: the highest CVSS v3 base score and EPSS probability (0 to 1) recorded on that CVE's observations.
kev_exposure.trendService×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_kevThe 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_hostsOnly 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_scoreScores, 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_atA 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_stateWhat it means
exposedThe radar has services on record with this CVE, and exposed_hosts counts them.
not_assessedNOT 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_zeroOnly 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