endoflife-mcp
v1.3.0
Published
endoflife.ai MCP server — software lifecycle intelligence for AI agents (EOL dates, EOL Risk Score, CISA KEV exposure, EOS edge devices, SBOM checks). npx endoflife-mcp runs a stdio bridge to https://mcp.endoflife.ai.
Maintainers
Readme
endoflife.ai — MCP Server
Exposes endoflife.ai's lifecycle intelligence to AI agents over the Model Context
Protocol (MCP, Streamable HTTP transport). A dependency-free Cloudflare Worker
that wraps the public api.endoflife.ai/v1 endpoints and the site's published
feeds — no data duplicated, every answer carries a source URL.
Current version: 1.1.0 (SERVER_INFO.version in src/index.js, server.json,
package.json — keep all three in step; the registry and Glama read server.json).
Tools (all read-only)
| Tool | What it does | Backed by |
|---|---|---|
| check_eol | Is product X version Y end-of-life? | GET /v1/status/:slug/:version |
| get_risk_score | EOL Risk Score (0–100) + factor breakdown | GET /v1/score/:slug[/:version] |
| scan_stack | Score a whole stack at once | POST /v1/batch |
| list_products | Search the 500+ tracked products → resolve slugs | GET /v1/products |
| get_product_lifecycle | Full version history + dates for one product | GET /v1/product/:slug |
| get_kev_exposure | Every CISA KEV entry attributed to a product (date added, due date, required action verbatim) + Exploited & Unpatchable entries | endoflife.ai/kev-products.json, exploited-and-unpatchable.json |
| get_upcoming_eol | Everything reaching EOL in the next N days (catalog-wide or a product list) | endoflife.ai/scanner-db.json |
| get_edge_device_status | EOS Edge Device feed with BOD 26-02 statuses, filter by platform / status / model | endoflife.ai/eos-edge-devices.json |
| get_upgrade_path | Supported targets, the site's recommendation, vendor-stated successor | GET /v1/product/:slug, checker-db.json, edge feed |
| check_sbom | CycloneDX / SPDX JSON → components resolved by package URL (exact, purl-map.json) or by name when no purl → scored; unmatched listed with a reason, never guessed | purl-map.json + POST /v1/batch |
Every tool advertises annotations (readOnlyHint, idempotentHint) and a
permissive outputSchema; results are returned both as JSON text and as
structuredContent. Lookup misses return "did you mean" slug suggestions
(prefix / substring / edit-distance ≤ 2 against the live product list).
Resources (resources/list / resources/read): llms.txt, the EOS Edge Device
feed, Exploited & Unpatchable, KEV by product, the edge change log.
Prompts (prompts/list / prompts/get): audit_stack, eol_calendar,
edge_device_review.
Endpoints: POST / (JSON-RPC), GET / (info page), GET /health (liveness JSON),
GET /.well-known/mcp/server-card.json (discovery card).
Usage telemetry
With the USAGE Analytics Engine binding (see wrangler.toml, dataset
endoflife_mcp_usage) each method / tool call writes one data point: tool name,
client user agent, ok/error, keyed/anon, latency in ms. No request bodies. Query
via the Cloudflare Analytics Engine SQL API, e.g.
SELECT blob1 AS tool, count() AS calls, sum(_sample_interval) AS weighted
FROM endoflife_mcp_usage WHERE timestamp > NOW() - INTERVAL '7' DAY
GROUP BY tool ORDER BY calls DESCThe binding is optional; the code no-ops without it.
Deploy
Production deploys run from GitHub Actions, never a local wrangler:
gh workflow run mcp-server-deploy.yml(.github/workflows/mcp-server-deploy.yml, pinned wrangler; needs the
CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID repository secrets.) Local
wrangler dev still works for development; the API service binding and the
USAGE dataset are declared for both the default and production environments.
Custom domain (mcp.endoflife.ai)
The route in wrangler.toml needs mcp.endoflife.ai to resolve through Cloudflare:
an AAAA record, name mcp, IPv6 100::, proxied. Already in place.
Test before deploying
A Node harness exercises every method against the live API and feeds (the module
runs unchanged in Node with env.API = { fetch }):
node scratch/mcp_test.mjs # see the session scratchpad; 22 RPC calls, expects 0 failuresConnect from an MCP client
Claude Desktop / Cursor / VS Code (mcpServers):
{
"mcpServers": {
"endoflife": { "command": "npx", "args": ["mcp-remote", "https://mcp.endoflife.ai"] }
}
}Clients with native remote-MCP support can use the URL directly:
{ "mcpServers": { "endoflife": { "url": "https://mcp.endoflife.ai" } } }Test without a client
# tools/list
curl -s https://mcp.endoflife.ai -X POST -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq
# KEV exposure for a product
curl -s https://mcp.endoflife.ai -X POST -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_kev_exposure","arguments":{"product":"ivanti-connect-secure"}}}' | jq
# health + discovery card
curl -s https://mcp.endoflife.ai/health | jq
curl -s https://mcp.endoflife.ai/.well-known/mcp/server-card.json | jqAuth & tiers
Free tier works with no key. Forward an X-API-Key header (your existing Pro keys)
to unlock Pro limits — the Worker passes it straight through to api.endoflife.ai.
Notes
- Same-zone trap:
api.endoflife.aiis a Worker on this zone; a plainfetch()to it goes to origin and fails. TheAPIservice binding is mandatory in prod. - Static feeds are fetched from the site origin with
cf.cacheTtl = 600and memoised in the isolate for 10 minutes. - Per-client rate limits: relies on the upstream API's limits. If MCP traffic
grows, add a KV rate-limiter keyed on
CF-Connecting-IPbefore the upstream call. - Discovery card is also served from the main site at
https://endoflife.ai/.well-known/mcp/server-card.json.
